diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4020504f9c07..253561ae81b66 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23832,7 +23832,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { findMostOverlappyType(source, target); } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) { + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String | number])[], related: (source: Type, target: Type) => boolean | Ternary) { const types = target.types; const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); for (const [getDiscriminatingType, propertyName] of discriminators) { @@ -23842,7 +23842,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let matched = false; for (let i = 0; i < types.length; i++) { if (include[i]) { - const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); + const targetType = typeof propertyName === "number" + ? getContextualTypeForElementExpression(types[i], propertyName) + : getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); if (targetType && related(getDiscriminatingType(), targetType)) { matched = true; } @@ -31361,6 +31363,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } + function discriminateContextualTypeByElements(node: ArrayLiteralExpression, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + const cached = getCachedType(key); + if (cached) return cached; + + // Create a discriminator for each element in the list + const discriminators = map( + node.elements, + (e, ndx) => [() => getContextFreeTypeOfExpression(e), ndx] as const, + ); + const discriminatedType = discriminateTypeByDiscriminableItems( + contextualType, + discriminators, + isTypeAssignableTo, + ); + + return setCachedType(key, discriminatedType); + } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily // be "pushed" onto a node using the contextualType property. function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { @@ -31378,9 +31399,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), /*noReductions*/ true, ); - return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : - apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : - apparentType; + const isUnion = apparentType.flags & TypeFlags.Union; + if (isUnion && isObjectLiteralExpression(node)) { + return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType); + } + else if (isUnion && isJsxAttributes(node)) { + return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); + } + else if (contextFlags && contextFlags & ContextFlags.Completions && isUnion && isArrayLiteralExpression(node)) { + return discriminateContextualTypeByElements(node, instantiatedType as UnionType); + } + else { + return apparentType; + } } } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 06f488ed47bcb..39516382d1d62 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -479,7 +479,8 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" - const types = getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags)); + const contextualType = getContextualTypeFromParent(node, typeChecker, contextFlags); + const types = getStringLiteralTypes(contextualType); if (!types.length) { return; } @@ -552,8 +553,12 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { if (!type) return emptyArray; type = skipConstraint(type); - return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : - type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; + if (type.isUnion()) { + return flatMap(type.types, t => getStringLiteralTypes(t, uniques)); + } + else { + return type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; + } } interface NameAndKind { diff --git a/tests/cases/fourslash/completionsTuple.ts b/tests/cases/fourslash/completionsTuple1.ts similarity index 100% rename from tests/cases/fourslash/completionsTuple.ts rename to tests/cases/fourslash/completionsTuple1.ts diff --git a/tests/cases/fourslash/completionsTuple2.ts b/tests/cases/fourslash/completionsTuple2.ts new file mode 100644 index 0000000000000..45448a2c799fe --- /dev/null +++ b/tests/cases/fourslash/completionsTuple2.ts @@ -0,0 +1,5 @@ +/// + +////declare const x: ["a", "b"] = ["x", "/**/"] + +verify.completions({ marker: "", exact: ["b"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTuple3.ts b/tests/cases/fourslash/completionsTuple3.ts new file mode 100644 index 0000000000000..73ae138ef310c --- /dev/null +++ b/tests/cases/fourslash/completionsTuple3.ts @@ -0,0 +1,5 @@ +/// + +////declare const x: ["a", ["b"], "x", ["y"]] = ["a", ["/**/"]] + +verify.completions({ marker: "", exact: ["b"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion1.ts b/tests/cases/fourslash/completionsTupleUnion1.ts new file mode 100644 index 0000000000000..9d2fc2d536b86 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion1.ts @@ -0,0 +1,6 @@ +/// + +////declare const a: "a"; +////declare const x: ['a', 'b'] | ['a', 'c'] | ['d', 'e'] = [a, "/**/"]; + +verify.completions({ marker: "", exact: ["b", "c"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion10.ts b/tests/cases/fourslash/completionsTupleUnion10.ts new file mode 100644 index 0000000000000..a882034881d93 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion10.ts @@ -0,0 +1,4 @@ +/// +////declare const x: ['a', 'b'] | { '0': 'x', '1': 'y' } | { '1': 'z' } = ['x', '/**/']; + +verify.completions({ marker: "", exact: ["y"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion11.ts b/tests/cases/fourslash/completionsTupleUnion11.ts new file mode 100644 index 0000000000000..4061546914057 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion11.ts @@ -0,0 +1,5 @@ +/// +////type ABC = "a" | "b" | "c"; +////declare const x: ['abc', ...ABC[]] | ['abc', 'abc', "f"] = ['abc', 'abc', '/**/']; + +verify.completions({ marker: "", exact: ["f"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion12.ts b/tests/cases/fourslash/completionsTupleUnion12.ts new file mode 100644 index 0000000000000..46228aadf7bfa --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion12.ts @@ -0,0 +1,4 @@ +/// +////declare const x: [(arg: number) => void, "num"] | [(arg: string) => void, "str"] | [(arg: boolean) => boolean, "val"] = [() => {}, '/**/']; + +verify.completions({ marker: "", exact: ["num", "str"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion13.ts b/tests/cases/fourslash/completionsTupleUnion13.ts new file mode 100644 index 0000000000000..93f35e9e93af6 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion13.ts @@ -0,0 +1,4 @@ +/// +////declare const x: [(arg: number) => void, "num"] | [(arg: string) => void, "str"] | [(arg: number) => number, "num2"] = [(x: number) => {}, '/**/']; + +verify.completions({ marker: "", exact: ["num"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion2.ts b/tests/cases/fourslash/completionsTupleUnion2.ts new file mode 100644 index 0000000000000..7ca2cf7b55646 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion2.ts @@ -0,0 +1,6 @@ +/// + +////declare const a: "a"; +////declare const x: [{ t: "a" }, "b"] | [{ t: "b" }, "c"] = [{ t: a }, "/**/"] + +verify.completions({ marker: "", exact: ["b"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion3.ts b/tests/cases/fourslash/completionsTupleUnion3.ts new file mode 100644 index 0000000000000..33aea75271793 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion3.ts @@ -0,0 +1,6 @@ +/// + +////declare const a: "a"; +////declare const x: ['a', 'b', 'f'] | ['a', 'b'] = [a, 'b', "/**/"]; + +verify.completions({ marker: "", exact: ["f"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion4.ts b/tests/cases/fourslash/completionsTupleUnion4.ts new file mode 100644 index 0000000000000..ad4b3e0db6b13 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion4.ts @@ -0,0 +1,5 @@ +/// + +////declare const x: ['a', 'b', 'f', 'g'] | ['a', 'b'] = ["a", 'b', 'f', "/**/"]; + +verify.completions({ marker: "", exact: ["g"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion5.ts b/tests/cases/fourslash/completionsTupleUnion5.ts new file mode 100644 index 0000000000000..ad1108b2c81f8 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion5.ts @@ -0,0 +1,5 @@ +/// + +////declare const x: ['a', 'b'] | ['a', 'c'] = ['b', "/**/"]; + +verify.completions({ marker: "", exact: ["b", "c"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion6.ts b/tests/cases/fourslash/completionsTupleUnion6.ts new file mode 100644 index 0000000000000..1e7a5bd824aba --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion6.ts @@ -0,0 +1,5 @@ +/// + +////declare const x: ['a', 'v1'] | ['b', 'v2'] = ['b', "v/**/"]; + +verify.completions({ marker: "", exact: ["v2"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion7.ts b/tests/cases/fourslash/completionsTupleUnion7.ts new file mode 100644 index 0000000000000..af426152f99d1 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion7.ts @@ -0,0 +1,6 @@ +/// +////type ABC = "a" | "b" | "c"; +////type XYZ = "x" | "y" | "z"; +////declare const x: ['abc', ABC] | ['xyz', ...XYZ[]] = ['xyz', 'x', 'x', 'x', 'x', '/**/']; + +verify.completions({ marker: "", exact: ["x", "y", "z"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion8.ts b/tests/cases/fourslash/completionsTupleUnion8.ts new file mode 100644 index 0000000000000..03039dc86ca20 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion8.ts @@ -0,0 +1,6 @@ +/// +////type ABC = "a" | "b" | "c"; +////type XYZ = "x" | "y" | "z"; +////declare const x: ['abc', ...ABC[]] | ['xyz', ...XYZ[]] = ['abc', 'a', 'b', 'c', 'a', '/**/']; + +verify.completions({ marker: "", exact: ["a", "b", "c"] }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsTupleUnion9.ts b/tests/cases/fourslash/completionsTupleUnion9.ts new file mode 100644 index 0000000000000..3951f3b4e7cc6 --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion9.ts @@ -0,0 +1,5 @@ +/// +////type ABC = "a" | "b" | "c"; +////declare const x: ['abc', ...ABC[]] | ['abc', "a", "f"] = ['abc', 'a', '/**/']; + +verify.completions({ marker: "", exact: ["a", "b", "c", "f"] }); \ No newline at end of file