From 7962488f5f8c6a50eee71c637b5126bbdbb70237 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 18 May 2024 13:55:50 -0700 Subject: [PATCH 01/20] add tests --- .../fourslash/{completionsTuple.ts => completionsTuple1.ts} | 0 tests/cases/fourslash/completionsTuple2.ts | 5 +++++ tests/cases/fourslash/completionsTuple3.ts | 5 +++++ tests/cases/fourslash/completionsTupleUnion1.ts | 6 ++++++ tests/cases/fourslash/completionsTupleUnion2.ts | 6 ++++++ 5 files changed, 22 insertions(+) rename tests/cases/fourslash/{completionsTuple.ts => completionsTuple1.ts} (100%) create mode 100644 tests/cases/fourslash/completionsTuple2.ts create mode 100644 tests/cases/fourslash/completionsTuple3.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion1.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion2.ts 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/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 From 620a6e3c74fe0e89be590762659eff7a86fb324b Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 18 May 2024 14:19:18 -0700 Subject: [PATCH 02/20] small refactor --- src/services/stringCompletions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 06f488ed47bcb..062b215ef4e40 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,11 @@ 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 { From bbac8a9f2261863b4ef27dacc4b65bda9e05f01f Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 18 May 2024 22:07:14 -0700 Subject: [PATCH 03/20] another test --- tests/cases/fourslash/completionsTupleUnion3.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion3.ts 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 From 0fca93ccab46059cf8690104edad58ea717a658d Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 18 May 2024 22:11:11 -0700 Subject: [PATCH 04/20] messy solution --- src/services/utilities.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2d57952373316..56acda08978ab 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -4,6 +4,7 @@ import { addSyntheticLeadingComment, addSyntheticTrailingComment, AnyImportOrRequireStatement, + ArrayLiteralExpression, assertType, AssignmentDeclarationKind, BinaryExpression, @@ -384,6 +385,7 @@ import { TypeOfExpression, TypeQueryNode, unescapeLeadingUnderscores, + UnionType, UserPreferences, VariableDeclaration, visitEachChild, @@ -3412,6 +3414,39 @@ export function getContextualTypeFromParent(node: Expression, checker: TypeCheck } case SyntaxKind.CaseClause: return getSwitchedType(parent as CaseClause, checker); + case SyntaxKind.ArrayLiteralExpression: + const typedParent = parent as ArrayLiteralExpression + const parentType = checker.getContextualType(typedParent, contextFlags); + if (parentType && parentType.flags & TypeFlags.Union) { + // If there is a union of tuples, filter down to only valid + // variants of the union based on previous elements in the tuple + const unionType = parentType as UnionType; + const nodeNdx = indexOfNode(typedParent.elements, node); + + // Fiilter down to only types that could have a valid element at this index + let filteredTypes = unionType.types.filter(t => { + const typeArguments = checker.getTypeArguments(t as any); + return typeArguments && typeArguments.length > nodeNdx; + }); + + for(let i = 0; i < nodeNdx; i++) { + const element = typedParent.elements[i]; + const typeToFilterBy = checker.getTypeAtLocation(element); + // Filter down to only types that match the preceding elements + filteredTypes = filteredTypes.filter(t => { + const typeArguments = checker.getTypeArguments(t as any); + if (typeArguments && typeArguments.length > i) { + const elementType = typeArguments[i]; + return checker.isTypeAssignableTo(typeToFilterBy, elementType); + } + return true; + }); + } + const resolvedTypes = filteredTypes.map(t => checker.getTypeArguments(t as any)[nodeNdx]); + const filteredUnionType = checker.getUnionType(resolvedTypes); + return filteredUnionType; + } + return checker.getContextualType(node, contextFlags); default: return checker.getContextualType(node, contextFlags); } From f2e9f322ca31cd29a245c3813731e8e8851ac0ea Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 18 May 2024 22:25:18 -0700 Subject: [PATCH 05/20] formatting --- src/services/stringCompletions.ts | 3 ++- src/services/utilities.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 062b215ef4e40..39516382d1d62 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -555,7 +555,8 @@ function getStringLiteralTypes(type: Type | undefined, uniques = new Map getStringLiteralTypes(t, uniques)); - } else { + } + else { return type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 56acda08978ab..cdc29a240189a 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3415,7 +3415,7 @@ export function getContextualTypeFromParent(node: Expression, checker: TypeCheck case SyntaxKind.CaseClause: return getSwitchedType(parent as CaseClause, checker); case SyntaxKind.ArrayLiteralExpression: - const typedParent = parent as ArrayLiteralExpression + const typedParent = parent as ArrayLiteralExpression; const parentType = checker.getContextualType(typedParent, contextFlags); if (parentType && parentType.flags & TypeFlags.Union) { // If there is a union of tuples, filter down to only valid @@ -3429,7 +3429,7 @@ export function getContextualTypeFromParent(node: Expression, checker: TypeCheck return typeArguments && typeArguments.length > nodeNdx; }); - for(let i = 0; i < nodeNdx; i++) { + for (let i = 0; i < nodeNdx; i++) { const element = typedParent.elements[i]; const typeToFilterBy = checker.getTypeAtLocation(element); // Filter down to only types that match the preceding elements From 660fa9f1e1c9feda14cab23ea8e75fefb75e8e31 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 16:00:19 -0700 Subject: [PATCH 06/20] more tests --- tests/cases/fourslash/completionsTupleUnion4.ts | 6 ++++++ tests/cases/fourslash/completionsTupleUnion5.ts | 5 +++++ tests/cases/fourslash/completionsTupleUnion6.ts | 5 +++++ 3 files changed, 16 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion4.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion5.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion6.ts diff --git a/tests/cases/fourslash/completionsTupleUnion4.ts b/tests/cases/fourslash/completionsTupleUnion4.ts new file mode 100644 index 0000000000000..b94b4e2a956ba --- /dev/null +++ b/tests/cases/fourslash/completionsTupleUnion4.ts @@ -0,0 +1,6 @@ +/// + +////declare const a: "a"; +////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 From eee12823cdd354050a2a4bf90baa1ea22495b272 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 16:01:11 -0700 Subject: [PATCH 07/20] revert previous fix --- src/services/utilities.ts | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index cdc29a240189a..2d57952373316 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -4,7 +4,6 @@ import { addSyntheticLeadingComment, addSyntheticTrailingComment, AnyImportOrRequireStatement, - ArrayLiteralExpression, assertType, AssignmentDeclarationKind, BinaryExpression, @@ -385,7 +384,6 @@ import { TypeOfExpression, TypeQueryNode, unescapeLeadingUnderscores, - UnionType, UserPreferences, VariableDeclaration, visitEachChild, @@ -3414,39 +3412,6 @@ export function getContextualTypeFromParent(node: Expression, checker: TypeCheck } case SyntaxKind.CaseClause: return getSwitchedType(parent as CaseClause, checker); - case SyntaxKind.ArrayLiteralExpression: - const typedParent = parent as ArrayLiteralExpression; - const parentType = checker.getContextualType(typedParent, contextFlags); - if (parentType && parentType.flags & TypeFlags.Union) { - // If there is a union of tuples, filter down to only valid - // variants of the union based on previous elements in the tuple - const unionType = parentType as UnionType; - const nodeNdx = indexOfNode(typedParent.elements, node); - - // Fiilter down to only types that could have a valid element at this index - let filteredTypes = unionType.types.filter(t => { - const typeArguments = checker.getTypeArguments(t as any); - return typeArguments && typeArguments.length > nodeNdx; - }); - - for (let i = 0; i < nodeNdx; i++) { - const element = typedParent.elements[i]; - const typeToFilterBy = checker.getTypeAtLocation(element); - // Filter down to only types that match the preceding elements - filteredTypes = filteredTypes.filter(t => { - const typeArguments = checker.getTypeArguments(t as any); - if (typeArguments && typeArguments.length > i) { - const elementType = typeArguments[i]; - return checker.isTypeAssignableTo(typeToFilterBy, elementType); - } - return true; - }); - } - const resolvedTypes = filteredTypes.map(t => checker.getTypeArguments(t as any)[nodeNdx]); - const filteredUnionType = checker.getUnionType(resolvedTypes); - return filteredUnionType; - } - return checker.getContextualType(node, contextFlags); default: return checker.getContextualType(node, contextFlags); } From 1d36c55a8b4e21f6f2fa9d7095d3bb733bbb77c5 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 16:01:31 -0700 Subject: [PATCH 08/20] use discriminateContextualTypeByElements --- src/compiler/checker.ts | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4020504f9c07..029cf30b61b8c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -31361,6 +31361,27 @@ 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 __String] as const, + ); + + return setCachedType( + key, + discriminateTypeByDiscriminableItems( + contextualType, + discriminators, + isTypeAssignableTo, + ), + ); + } + // 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 (isUnion && isArrayLiteralExpression(node)) { + return discriminateContextualTypeByElements(node, apparentType as UnionType); + } + else { + return apparentType; + } } } From df5b937b4c2cc775932fb6d14590d545a96100c0 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 17:10:52 -0700 Subject: [PATCH 09/20] getMatchingUnionConstituentForArrayLiteral --- src/compiler/checker.ts | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 029cf30b61b8c..570c120c24953 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26900,6 +26900,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return propType && getConstituentTypeForKeyType(unionType, propType); } + function getMatchingUnionConstituentForArrayLiteral(unionType: UnionType, node: ArrayLiteralExpression) { + const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el)); + for (const type of unionType.types) { + if (isTupleType(type)) { + const unionElements = getElementTypes(type); + const typesMatch = resolvedElements.every( + (el, ndx) => isTypeAssignableTo(el, unionElements[ndx]), + ); + if (typesMatch) { + return type; + } + } + } + return undefined; + } + function isOrContainsMatchingReference(source: Node, target: Node) { return isMatchingReference(source, target) || containsMatchingReference(source, target); } @@ -31366,20 +31382,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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 __String] as const, - ); - - return setCachedType( - key, - discriminateTypeByDiscriminableItems( + let discriminatedType: Type | undefined = getMatchingUnionConstituentForArrayLiteral(contextualType, node); + if (!discriminatedType) { + // Create a discriminator for each element in the list + const discriminators = map( + node.elements, + (e, ndx) => [() => getContextFreeTypeOfExpression(e), "" + ndx as __String] as 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 From ecf39c72a6b511a94e36f7324eebd2fc213327c4 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 17:23:45 -0700 Subject: [PATCH 10/20] clean up --- src/compiler/checker.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 570c120c24953..a80275110586a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26903,15 +26903,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getMatchingUnionConstituentForArrayLiteral(unionType: UnionType, node: ArrayLiteralExpression) { const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el)); for (const type of unionType.types) { - if (isTupleType(type)) { - const unionElements = getElementTypes(type); - const typesMatch = resolvedElements.every( - (el, ndx) => isTypeAssignableTo(el, unionElements[ndx]), - ); - if (typesMatch) { - return type; - } - } + if (!isTupleType(type)) continue; + const unionElements = getElementTypes(type); + if (unionElements.length !== resolvedElements.length) continue; + const typesMatch = resolvedElements.every( + (el, ndx) => isTypeAssignableTo(el, unionElements[ndx]), + ); + if (typesMatch) return type; } return undefined; } From 8c31d7e6db6f04d9e8b3ed609811f8ef00a64ef2 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 19 May 2024 21:08:24 -0700 Subject: [PATCH 11/20] try turning on only for completions --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a80275110586a..33de8afb1936f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -31421,7 +31421,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (isUnion && isJsxAttributes(node)) { return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); } - else if (isUnion && isArrayLiteralExpression(node)) { + else if (contextFlags && contextFlags & ContextFlags.Completions && isUnion && isArrayLiteralExpression(node)) { return discriminateContextualTypeByElements(node, apparentType as UnionType); } else { From 4967819480c1dc9906f54ca049fb4a9f8c064aa5 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 25 May 2024 21:14:01 -0700 Subject: [PATCH 12/20] add variadic tuple test --- tests/cases/fourslash/completionsTupleUnion4.ts | 3 +-- tests/cases/fourslash/completionsTupleUnion7.ts | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/completionsTupleUnion7.ts diff --git a/tests/cases/fourslash/completionsTupleUnion4.ts b/tests/cases/fourslash/completionsTupleUnion4.ts index b94b4e2a956ba..ad4b3e0db6b13 100644 --- a/tests/cases/fourslash/completionsTupleUnion4.ts +++ b/tests/cases/fourslash/completionsTupleUnion4.ts @@ -1,6 +1,5 @@ /// -////declare const a: "a"; -////declare const x: ['a', 'b', 'f', 'g'] | ['a', 'b'] = [a, 'b', 'f', "/**/"]; +////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/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 From 79a4a79d1b3cb2567642d7e5bc40e276c1bfc23a Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 25 May 2024 21:17:23 -0700 Subject: [PATCH 13/20] support variadic tuples --- src/compiler/checker.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 33de8afb1936f..cca92131470cf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26904,10 +26904,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el)); for (const type of unionType.types) { if (!isTupleType(type)) continue; - const unionElements = getElementTypes(type); - if (unionElements.length !== resolvedElements.length) continue; const typesMatch = resolvedElements.every( - (el, ndx) => isTypeAssignableTo(el, unionElements[ndx]), + (el, ndx) => { + const elType = getContextualTypeForElementExpression(type, ndx); + return elType && isTypeAssignableTo(el, elType); + }, ); if (typesMatch) return type; } @@ -31422,7 +31423,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); } else if (contextFlags && contextFlags & ContextFlags.Completions && isUnion && isArrayLiteralExpression(node)) { - return discriminateContextualTypeByElements(node, apparentType as UnionType); + return discriminateContextualTypeByElements(node, instantiatedType as UnionType); } else { return apparentType; From dcb9b350af8dac1d16ecd795a3065394f58cfa46 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sat, 25 May 2024 21:26:08 -0700 Subject: [PATCH 14/20] add more variadic tuple tests --- tests/cases/fourslash/completionsTupleUnion8.ts | 6 ++++++ tests/cases/fourslash/completionsTupleUnion9.ts | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion8.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion9.ts 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 From baf60365c15fe378c72bef783de858144b2bb121 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 10:32:31 -0700 Subject: [PATCH 15/20] disable getMatchingUnionConstituentForArrayLiteral for now --- src/compiler/checker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cca92131470cf..9d1bcb364f5dd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26901,6 +26901,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getMatchingUnionConstituentForArrayLiteral(unionType: UnionType, node: ArrayLiteralExpression) { + // TODO: Remove this function if it's not actually needed? + return undefined; const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el)); for (const type of unionType.types) { if (!isTupleType(type)) continue; From d24f618053ea80f9cb670b1c237b7a48afe54290 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 10:32:47 -0700 Subject: [PATCH 16/20] add structurally typed tuple test --- tests/cases/fourslash/completionsTupleUnion10.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion10.ts 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 From 6c935e848afe73a02ba0c6f50f23010174312457 Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 15:24:53 -0700 Subject: [PATCH 17/20] add another variadic tuple test --- tests/cases/fourslash/completionsTupleUnion11.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion11.ts 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 From 002e5c4df17411ad2da7c87a17b511db4d9bf50e Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 15:32:39 -0700 Subject: [PATCH 18/20] use getContextualTypeForElementExpression for discriminating --- src/compiler/checker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d1bcb364f5dd..7361f9e825cbf 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; } @@ -31388,7 +31390,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Create a discriminator for each element in the list const discriminators = map( node.elements, - (e, ndx) => [() => getContextFreeTypeOfExpression(e), "" + ndx as __String] as const, + (e, ndx) => [() => getContextFreeTypeOfExpression(e), ndx] as const, ); discriminatedType = discriminateTypeByDiscriminableItems( contextualType, From e127de7ac4a251fde0e651f96969f0e8f63f05da Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 15:40:39 -0700 Subject: [PATCH 19/20] remove getMatchingUnionConstituentsForArrayLiteral --- src/compiler/checker.ts | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7361f9e825cbf..253561ae81b66 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26902,23 +26902,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return propType && getConstituentTypeForKeyType(unionType, propType); } - function getMatchingUnionConstituentForArrayLiteral(unionType: UnionType, node: ArrayLiteralExpression) { - // TODO: Remove this function if it's not actually needed? - return undefined; - const resolvedElements = node.elements.map(el => getContextFreeTypeOfExpression(el)); - for (const type of unionType.types) { - if (!isTupleType(type)) continue; - const typesMatch = resolvedElements.every( - (el, ndx) => { - const elType = getContextualTypeForElementExpression(type, ndx); - return elType && isTypeAssignableTo(el, elType); - }, - ); - if (typesMatch) return type; - } - return undefined; - } - function isOrContainsMatchingReference(source: Node, target: Node) { return isMatchingReference(source, target) || containsMatchingReference(source, target); } @@ -31385,19 +31368,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const cached = getCachedType(key); if (cached) return cached; - let discriminatedType: Type | undefined = getMatchingUnionConstituentForArrayLiteral(contextualType, node); - if (!discriminatedType) { - // Create a discriminator for each element in the list - const discriminators = map( - node.elements, - (e, ndx) => [() => getContextFreeTypeOfExpression(e), ndx] as const, - ); - discriminatedType = discriminateTypeByDiscriminableItems( - contextualType, - discriminators, - isTypeAssignableTo, - ); - } + // 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); } From 4272086ee10c72bea532dddbd570927284dca32f Mon Sep 17 00:00:00 2001 From: Ryan Baxley Date: Sun, 26 May 2024 15:42:21 -0700 Subject: [PATCH 20/20] add tests for contextually typed functions --- tests/cases/fourslash/completionsTupleUnion12.ts | 4 ++++ tests/cases/fourslash/completionsTupleUnion13.ts | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 tests/cases/fourslash/completionsTupleUnion12.ts create mode 100644 tests/cases/fourslash/completionsTupleUnion13.ts 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