Don't add completions from a discriminated union type when the discriminant doesn't match (#24770)
* Don't add completions from a discriminated union type when the discriminant doesn't match * Move code to checker * Update API (#24966) * Use isTypeIdenticalTo
This commit is contained in:
parent
eb7ff43f95
commit
204b70d7af
|
@ -109,6 +109,7 @@ namespace ts {
|
|||
getDeclaredTypeOfSymbol,
|
||||
getPropertiesOfType,
|
||||
getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)),
|
||||
getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)),
|
||||
getIndexInfoOfType,
|
||||
getSignaturesOfType,
|
||||
getIndexTypeOfType,
|
||||
|
@ -290,6 +291,7 @@ namespace ts {
|
|||
getNeverType: () => neverType,
|
||||
isSymbolAccessible,
|
||||
isArrayLikeType,
|
||||
isTypeInvalidDueToUnionDiscriminant,
|
||||
getAllPossiblePropertiesOfTypes,
|
||||
getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type),
|
||||
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
|
||||
|
@ -6687,6 +6689,18 @@ namespace ts {
|
|||
getPropertiesOfObjectType(type);
|
||||
}
|
||||
|
||||
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression): boolean {
|
||||
return obj.properties.some(property => {
|
||||
const name = property.name && getTextOfPropertyName(property.name);
|
||||
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
|
||||
if (expected && typeIsLiteralType(expected)) {
|
||||
const actual = getTypeOfNode(property);
|
||||
return !!actual && !isTypeIdenticalTo(actual, expected);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function getAllPossiblePropertiesOfTypes(types: ReadonlyArray<Type>): Symbol[] {
|
||||
const unionType = getUnionType(types);
|
||||
if (!(unionType.flags & TypeFlags.Union)) {
|
||||
|
@ -29082,4 +29096,8 @@ namespace ts {
|
|||
export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String;
|
||||
// tslint:enable variable-name
|
||||
}
|
||||
|
||||
function typeIsLiteralType(type: Type): type is LiteralType {
|
||||
return !!(type.flags & TypeFlags.Literal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2883,6 +2883,7 @@ namespace ts {
|
|||
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
|
||||
getPropertiesOfType(type: Type): Symbol[];
|
||||
getPropertyOfType(type: Type, propertyName: string): Symbol | undefined;
|
||||
/* @internal */ getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined;
|
||||
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
|
||||
getSignaturesOfType(type: Type, kind: SignatureKind): ReadonlyArray<Signature>;
|
||||
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;
|
||||
|
@ -3045,6 +3046,11 @@ namespace ts {
|
|||
/* @internal */ getTypeCount(): number;
|
||||
|
||||
/* @internal */ isArrayLikeType(type: Type): boolean;
|
||||
/**
|
||||
* True if `contextualType` should not be considered for completions because
|
||||
* e.g. it specifies `kind: "a"` and obj has `kind: "b"`.
|
||||
*/
|
||||
/* @internal */ isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression): boolean;
|
||||
/**
|
||||
* For a union, will include a property if it's defined in *any* of the member types.
|
||||
* So for `{ a } | { b }`, this will include both `a` and `b`.
|
||||
|
|
|
@ -1101,7 +1101,7 @@ namespace ts.Completions {
|
|||
// each individual type has. This is because we're going to add all identifiers
|
||||
// anyways. So we might as well elevate the members that were at least part
|
||||
// of the individual types to a higher status since we know what they are.
|
||||
symbols.push(...getPropertiesForCompletion(type, typeChecker, /*isForAccess*/ true));
|
||||
symbols.push(...getPropertiesForCompletion(type, typeChecker));
|
||||
}
|
||||
else {
|
||||
for (const symbol of type.getApparentProperties()) {
|
||||
|
@ -1221,7 +1221,7 @@ namespace ts.Completions {
|
|||
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
|
||||
const thisType = typeChecker.tryGetThisTypeAt(scopeNode);
|
||||
if (thisType) {
|
||||
for (const symbol of getPropertiesForCompletion(thisType, typeChecker, /*isForAccess*/ true)) {
|
||||
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
|
||||
symbolToOriginInfoMap[getSymbolId(symbol)] = { type: "this-type" };
|
||||
symbols.push(symbol);
|
||||
}
|
||||
|
@ -1538,7 +1538,7 @@ namespace ts.Completions {
|
|||
const typeForObject = typeChecker.getContextualType(objectLikeContainer);
|
||||
if (!typeForObject) return GlobalsSearch.Fail;
|
||||
isNewIdentifierLocation = hasIndexSignature(typeForObject);
|
||||
typeMembers = getPropertiesForCompletion(typeForObject, typeChecker, /*isForAccess*/ false);
|
||||
typeMembers = getPropertiesForObjectExpression(typeForObject, objectLikeContainer, typeChecker);
|
||||
existingMembers = objectLikeContainer.properties;
|
||||
}
|
||||
else {
|
||||
|
@ -2159,19 +2159,25 @@ namespace ts.Completions {
|
|||
return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined);
|
||||
}
|
||||
|
||||
function getPropertiesForObjectExpression(contextualType: Type, obj: ObjectLiteralExpression, checker: TypeChecker): Symbol[] {
|
||||
return contextualType.isUnion()
|
||||
? checker.getAllPossiblePropertiesOfTypes(contextualType.types.filter(memberType =>
|
||||
// If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals.
|
||||
!(memberType.flags & TypeFlags.Primitive ||
|
||||
checker.isArrayLikeType(memberType) ||
|
||||
typeHasCallOrConstructSignatures(memberType, checker) ||
|
||||
checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj))))
|
||||
: contextualType.getApparentProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all properties on a type, but if that type is a union of several types,
|
||||
* excludes array-like types or callable/constructable types.
|
||||
*/
|
||||
function getPropertiesForCompletion(type: Type, checker: TypeChecker, isForAccess: boolean): Symbol[] {
|
||||
if (!(type.isUnion())) {
|
||||
return Debug.assertEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined");
|
||||
}
|
||||
|
||||
// If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals.
|
||||
const filteredTypes = isForAccess ? type.types : type.types.filter(memberType =>
|
||||
!(memberType.flags & TypeFlags.Primitive || checker.isArrayLikeType(memberType) || typeHasCallOrConstructSignatures(memberType, checker)));
|
||||
return Debug.assertEachDefined(checker.getAllPossiblePropertiesOfTypes(filteredTypes), "getAllPossiblePropertiesOfTypes() should all be defined");
|
||||
function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] {
|
||||
return type.isUnion()
|
||||
? Debug.assertEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined")
|
||||
: Debug.assertEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2570,6 +2570,7 @@ declare namespace ts {
|
|||
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
|
||||
getPropertiesOfType(type: Type): Symbol[];
|
||||
getPropertyOfType(type: Type, propertyName: string): Symbol | undefined;
|
||||
getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined;
|
||||
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
|
||||
getSignaturesOfType(type: Type, kind: SignatureKind): ReadonlyArray<Signature>;
|
||||
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;
|
||||
|
@ -2707,6 +2708,11 @@ declare namespace ts {
|
|||
getSymbolCount(): number;
|
||||
getTypeCount(): number;
|
||||
isArrayLikeType(type: Type): boolean;
|
||||
/**
|
||||
* True if `contextualType` should not be considered for completions because
|
||||
* e.g. it specifies `kind: "a"` and obj has `kind: "b"`.
|
||||
*/
|
||||
isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression): boolean;
|
||||
/**
|
||||
* For a union, will include a property if it's defined in *any* of the member types.
|
||||
* So for `{ a } | { b }`, this will include both `a` and `b`.
|
||||
|
|
7
tests/cases/fourslash/completionsDiscriminatedUnion.ts
Normal file
7
tests/cases/fourslash/completionsDiscriminatedUnion.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////interface A { kind: "a"; a: number; }
|
||||
////interface B { kind: "b"; b: number; }
|
||||
////const c: A | B = { kind: "a", /**/ };
|
||||
|
||||
verify.completions({ marker: "", exact: ["a"] });
|
Loading…
Reference in a new issue