diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 967ad86941b40..c10c6763369a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16208,6 +16208,46 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); } + function expandTupleShape(source: TupleTypeReference, target: TupleTypeReference): Type { + const sourceArity = getTypeReferenceArity(source); + const targetArity = getTypeReferenceArity(target); + const sourceElementTypes = getTypeArguments(source); + const targetElementTypes = getTypeArguments(target); + + let resultTypes: Type[] | undefined; + let resultFlags: ElementFlags[] | undefined; + + for (let i = 0; i < targetArity; i++) { + if (i >= sourceArity) { + if (!resultTypes) { + resultTypes = sourceElementTypes.slice(); + resultFlags = source.target.elementFlags.slice(); + } + const targetFlag = target.target.elementFlags[i]; + resultTypes.push(anyType); + resultFlags!.push(targetFlag); + } + else if (isTupleType(sourceElementTypes[i]) && isTupleType(targetElementTypes[i])) { + const result = expandTupleShape(sourceElementTypes[i] as TupleTypeReference, targetElementTypes[i] as TupleTypeReference); + if (result === sourceElementTypes[i]) { + continue; + } + if (!resultTypes) { + resultTypes = sourceElementTypes.slice(0, i); + resultFlags = source.target.elementFlags.slice(0, i); + } + resultTypes.push(result); + resultFlags!.push(source.target.elementFlags[i]); + } + else if (resultTypes) { + resultTypes.push(sourceElementTypes[i]); + resultFlags!.push(source.target.elementFlags[i]); + } + } + + return resultTypes ? createTupleType(resultTypes, resultFlags) : source; + } + function getKnownKeysOfTupleType(type: TupleTypeReference) { return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); @@ -32443,7 +32483,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // return type of 'wrap'. if (node.kind !== SyntaxKind.Decorator) { const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)); - const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None); + let contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None); if (contextualType) { const inferenceTargetType = getReturnTypeOfSignature(signature); if (couldContainTypeVariables(inferenceTargetType)) { @@ -32479,6 +32519,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Inferences made from return types have lower priority than all other inferences. inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); } + // if the contextual type is a tuple and it's is coming from a binding pattern + // then we might need to expand it to match the arity of the target type + // this allows us to infer tuples even if the binding pattern is shorter than the target type, like here: + // declare function fn(t: T) => [T, string[]]; + // const [[x]] = fn(['hi']); + else if (isTupleType(contextualType) && isTupleType(inferenceTargetType)) { + contextualType = expandTupleShape(contextualType, inferenceTargetType); + } // Create a type mapper for instantiating generic contextual types using the inferences made // from the return type. We need a separate inference pass here because (a) instantiation of // the source type uses the outer context's return mapper (which excludes inferences made from diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).js b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).js new file mode 100644 index 0000000000000..c027c13ec53d8 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).js @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +//// [inferTupleFromBindingPattern.ts] +declare function f(cb: () => T): T; +const [e1, e2, e3] = f(() => [1, "hi", true]); + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +const [[f2e1]] = f2(['1']); +f2e1.toLowerCase(); + +declare const f3: (t: T) => [[T, string[]]]; +const [[[f3e1]]] = f3(['1']); +f3e1.toLowerCase(); + +//// [inferTupleFromBindingPattern.js] +"use strict"; +var _a = f(function () { return [1, "hi", true]; }), e1 = _a[0], e2 = _a[1], e3 = _a[2]; +var f2e1 = f2(['1'])[0][0]; +f2e1.toLowerCase(); +var f3e1 = f3(['1'])[0][0][0]; +f3e1.toLowerCase(); diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).symbols b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).symbols new file mode 100644 index 0000000000000..a2a1beb0da801 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).symbols @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +=== inferTupleFromBindingPattern.ts === +declare function f(cb: () => T): T; +>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) +>cb : Symbol(cb, Decl(inferTupleFromBindingPattern.ts, 0, 22)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) + +const [e1, e2, e3] = f(() => [1, "hi", true]); +>e1 : Symbol(e1, Decl(inferTupleFromBindingPattern.ts, 1, 7)) +>e2 : Symbol(e2, Decl(inferTupleFromBindingPattern.ts, 1, 10)) +>e3 : Symbol(e3, Decl(inferTupleFromBindingPattern.ts, 1, 14)) +>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) +>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 4, 39)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) + +const [[f2e1]] = f2(['1']); +>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8)) +>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13)) + +f2e1.toLowerCase(); +>f2e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) + +declare const f3: (t: T) => [[T, string[]]]; +>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) +>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 8, 39)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) + +const [[[f3e1]]] = f3(['1']); +>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9)) +>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13)) + +f3e1.toLowerCase(); +>f3e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).types b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).types new file mode 100644 index 0000000000000..834dd0db960f2 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=false).types @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +=== inferTupleFromBindingPattern.ts === +declare function f(cb: () => T): T; +>f : (cb: () => T) => T +>cb : () => T + +const [e1, e2, e3] = f(() => [1, "hi", true]); +>e1 : number +>e2 : string +>e3 : boolean +>f(() => [1, "hi", true]) : [number, string, boolean] +>f : (cb: () => T) => T +>() => [1, "hi", true] : () => [number, string, boolean] +>[1, "hi", true] : [number, string, true] +>1 : 1 +>"hi" : "hi" +>true : true + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +>f2 : (t: T) => [T, string[]] +>t : T + +const [[f2e1]] = f2(['1']); +>f2e1 : string +>f2(['1']) : [[string], string[]] +>f2 : (t: T) => [T, string[]] +>['1'] : [string] +>'1' : "1" + +f2e1.toLowerCase(); +>f2e1.toLowerCase() : string +>f2e1.toLowerCase : () => string +>f2e1 : string +>toLowerCase : () => string + +declare const f3: (t: T) => [[T, string[]]]; +>f3 : (t: T) => [[T, string[]]] +>t : T + +const [[[f3e1]]] = f3(['1']); +>f3e1 : string +>f3(['1']) : [[[string], string[]]] +>f3 : (t: T) => [[T, string[]]] +>['1'] : [string] +>'1' : "1" + +f3e1.toLowerCase(); +>f3e1.toLowerCase() : string +>f3e1.toLowerCase : () => string +>f3e1 : string +>toLowerCase : () => string + diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).js b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).js new file mode 100644 index 0000000000000..c027c13ec53d8 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).js @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +//// [inferTupleFromBindingPattern.ts] +declare function f(cb: () => T): T; +const [e1, e2, e3] = f(() => [1, "hi", true]); + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +const [[f2e1]] = f2(['1']); +f2e1.toLowerCase(); + +declare const f3: (t: T) => [[T, string[]]]; +const [[[f3e1]]] = f3(['1']); +f3e1.toLowerCase(); + +//// [inferTupleFromBindingPattern.js] +"use strict"; +var _a = f(function () { return [1, "hi", true]; }), e1 = _a[0], e2 = _a[1], e3 = _a[2]; +var f2e1 = f2(['1'])[0][0]; +f2e1.toLowerCase(); +var f3e1 = f3(['1'])[0][0][0]; +f3e1.toLowerCase(); diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).symbols b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).symbols new file mode 100644 index 0000000000000..a2a1beb0da801 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).symbols @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +=== inferTupleFromBindingPattern.ts === +declare function f(cb: () => T): T; +>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) +>cb : Symbol(cb, Decl(inferTupleFromBindingPattern.ts, 0, 22)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) + +const [e1, e2, e3] = f(() => [1, "hi", true]); +>e1 : Symbol(e1, Decl(inferTupleFromBindingPattern.ts, 1, 7)) +>e2 : Symbol(e2, Decl(inferTupleFromBindingPattern.ts, 1, 10)) +>e3 : Symbol(e3, Decl(inferTupleFromBindingPattern.ts, 1, 14)) +>f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) +>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 4, 39)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 4, 19)) + +const [[f2e1]] = f2(['1']); +>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8)) +>f2 : Symbol(f2, Decl(inferTupleFromBindingPattern.ts, 4, 13)) + +f2e1.toLowerCase(); +>f2e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>f2e1 : Symbol(f2e1, Decl(inferTupleFromBindingPattern.ts, 5, 8)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) + +declare const f3: (t: T) => [[T, string[]]]; +>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) +>t : Symbol(t, Decl(inferTupleFromBindingPattern.ts, 8, 39)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) +>T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 8, 19)) + +const [[[f3e1]]] = f3(['1']); +>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9)) +>f3 : Symbol(f3, Decl(inferTupleFromBindingPattern.ts, 8, 13)) + +f3e1.toLowerCase(); +>f3e1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>f3e1 : Symbol(f3e1, Decl(inferTupleFromBindingPattern.ts, 9, 9)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).types b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).types new file mode 100644 index 0000000000000..834dd0db960f2 --- /dev/null +++ b/tests/baselines/reference/inferTupleFromBindingPattern(nouncheckedindexedaccess=true).types @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// + +=== inferTupleFromBindingPattern.ts === +declare function f(cb: () => T): T; +>f : (cb: () => T) => T +>cb : () => T + +const [e1, e2, e3] = f(() => [1, "hi", true]); +>e1 : number +>e2 : string +>e3 : boolean +>f(() => [1, "hi", true]) : [number, string, boolean] +>f : (cb: () => T) => T +>() => [1, "hi", true] : () => [number, string, boolean] +>[1, "hi", true] : [number, string, true] +>1 : 1 +>"hi" : "hi" +>true : true + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +>f2 : (t: T) => [T, string[]] +>t : T + +const [[f2e1]] = f2(['1']); +>f2e1 : string +>f2(['1']) : [[string], string[]] +>f2 : (t: T) => [T, string[]] +>['1'] : [string] +>'1' : "1" + +f2e1.toLowerCase(); +>f2e1.toLowerCase() : string +>f2e1.toLowerCase : () => string +>f2e1 : string +>toLowerCase : () => string + +declare const f3: (t: T) => [[T, string[]]]; +>f3 : (t: T) => [[T, string[]]] +>t : T + +const [[[f3e1]]] = f3(['1']); +>f3e1 : string +>f3(['1']) : [[[string], string[]]] +>f3 : (t: T) => [[T, string[]]] +>['1'] : [string] +>'1' : "1" + +f3e1.toLowerCase(); +>f3e1.toLowerCase() : string +>f3e1.toLowerCase : () => string +>f3e1 : string +>toLowerCase : () => string + diff --git a/tests/baselines/reference/inferTupleFromBindingPattern.js b/tests/baselines/reference/inferTupleFromBindingPattern.js deleted file mode 100644 index bab1caa7c1696..0000000000000 --- a/tests/baselines/reference/inferTupleFromBindingPattern.js +++ /dev/null @@ -1,9 +0,0 @@ -//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// - -//// [inferTupleFromBindingPattern.ts] -declare function f(cb: () => T): T; -const [e1, e2, e3] = f(() => [1, "hi", true]); - - -//// [inferTupleFromBindingPattern.js] -var _a = f(function () { return [1, "hi", true]; }), e1 = _a[0], e2 = _a[1], e3 = _a[2]; diff --git a/tests/baselines/reference/inferTupleFromBindingPattern.symbols b/tests/baselines/reference/inferTupleFromBindingPattern.symbols deleted file mode 100644 index bba0820623cda..0000000000000 --- a/tests/baselines/reference/inferTupleFromBindingPattern.symbols +++ /dev/null @@ -1,16 +0,0 @@ -//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// - -=== inferTupleFromBindingPattern.ts === -declare function f(cb: () => T): T; ->f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) ->T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) ->cb : Symbol(cb, Decl(inferTupleFromBindingPattern.ts, 0, 22)) ->T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) ->T : Symbol(T, Decl(inferTupleFromBindingPattern.ts, 0, 19)) - -const [e1, e2, e3] = f(() => [1, "hi", true]); ->e1 : Symbol(e1, Decl(inferTupleFromBindingPattern.ts, 1, 7)) ->e2 : Symbol(e2, Decl(inferTupleFromBindingPattern.ts, 1, 10)) ->e3 : Symbol(e3, Decl(inferTupleFromBindingPattern.ts, 1, 14)) ->f : Symbol(f, Decl(inferTupleFromBindingPattern.ts, 0, 0)) - diff --git a/tests/baselines/reference/inferTupleFromBindingPattern.types b/tests/baselines/reference/inferTupleFromBindingPattern.types deleted file mode 100644 index f05074b07147d..0000000000000 --- a/tests/baselines/reference/inferTupleFromBindingPattern.types +++ /dev/null @@ -1,19 +0,0 @@ -//// [tests/cases/compiler/inferTupleFromBindingPattern.ts] //// - -=== inferTupleFromBindingPattern.ts === -declare function f(cb: () => T): T; ->f : (cb: () => T) => T ->cb : () => T - -const [e1, e2, e3] = f(() => [1, "hi", true]); ->e1 : number ->e2 : string ->e3 : boolean ->f(() => [1, "hi", true]) : [number, string, boolean] ->f : (cb: () => T) => T ->() => [1, "hi", true] : () => [number, string, boolean] ->[1, "hi", true] : [number, string, true] ->1 : 1 ->"hi" : "hi" ->true : true - diff --git a/tests/cases/compiler/inferTupleFromBindingPattern.ts b/tests/cases/compiler/inferTupleFromBindingPattern.ts index 7c0c1dea98193..437ce5647bdd2 100644 --- a/tests/cases/compiler/inferTupleFromBindingPattern.ts +++ b/tests/cases/compiler/inferTupleFromBindingPattern.ts @@ -1,2 +1,14 @@ +// @strict: true +// @noUncheckedIndexedAccess: true, false + declare function f(cb: () => T): T; const [e1, e2, e3] = f(() => [1, "hi", true]); + +// repro from #42969 +declare const f2: (t: T) => [T, string[]]; +const [[f2e1]] = f2(['1']); +f2e1.toLowerCase(); + +declare const f3: (t: T) => [[T, string[]]]; +const [[[f3e1]]] = f3(['1']); +f3e1.toLowerCase(); \ No newline at end of file