diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 762a7c3b8eeb9..b3d4a6c534422 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -32029,6 +32029,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } + function areTupleLikeProperties(properties: Symbol[]): boolean { + return properties.length > 0 && every(properties, p => p.escapedName === "length" as __String || /^\d+$/.test(p.escapedName.toString())); + } + function discriminateContextualTypeByArrayElements(node: ArrayLiteralExpression, contextualType: UnionType) { const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; const cachedType = getCachedType(key); @@ -32036,9 +32040,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const elementsLength = node.elements.length; const filteredType = filterType(contextualType, type => { - if (!isTupleLikeType(type)) return true; - if (isTupleType(type)) return elementsLength >= type.target.minLength && (!!(type.target.combinedFlags & ElementFlags.Variable) || elementsLength <= type.target.fixedLength); + if (isTupleType(type)) { + return elementsLength >= type.target.minLength && (!!(type.target.combinedFlags & ElementFlags.Variable) || elementsLength <= type.target.fixedLength); + } const properties = getPropertiesOfType(type); + if (!areTupleLikeProperties(properties)) return true; if (elementsLength > properties.length || elementsLength < properties.reduce((c, p) => p.flags & SymbolFlags.Optional ? c : ++c, 0)) return false; for (let i = 0; i < elementsLength; i++) { if (!some(properties, p => p.escapedName === ("" + i) as __String)) return false; @@ -32047,18 +32053,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); if (filteredType.flags & TypeFlags.Never) return setCachedType(key, contextualType); - if (!(filteredType.flags & TypeFlags.Union)) return setCachedType(key, isTupleLikeType(filteredType) ? filteredType : contextualType); + if (!(filteredType.flags & TypeFlags.Union)) { + return setCachedType(key, isTupleType(filteredType) || areTupleLikeProperties(getPropertiesOfType(filteredType)) ? filteredType : contextualType); + } return setCachedType( key, discriminateTypeByDiscriminableItems( filteredType as UnionType, - node.elements.map((element, index) => { - const name = ("" + index) as __String; - return isPossiblyDiscriminantValue(element) && isDiscriminantProperty(filteredType, name) ? - [() => getContextFreeTypeOfExpression(element), name] as const : - undefined; - }).filter(discriminator => !!discriminator), + filter( + map(node.elements, (element, index) => { + const name = ("" + index) as __String; + return isPossiblyDiscriminantValue(element) && isDiscriminantProperty(filteredType, name) ? + [() => getContextFreeTypeOfExpression(element), name] as const : + undefined; + }), + discriminator => !!discriminator, + ), isTypeAssignableTo, ), ); diff --git a/tests/baselines/reference/tupleTypeInference3.errors.txt b/tests/baselines/reference/tupleTypeInference3.errors.txt index d3b87bc1c477b..039b6f4419e17 100644 --- a/tests/baselines/reference/tupleTypeInference3.errors.txt +++ b/tests/baselines/reference/tupleTypeInference3.errors.txt @@ -8,9 +8,10 @@ tupleTypeInference3.ts(16,7): error TS2322: Type '[1, (a: boolean) => void]' is tupleTypeInference3.ts(30,22): error TS7006: Parameter 'a' implicitly has an 'any' type. tupleTypeInference3.ts(35,22): error TS7006: Parameter 'a' implicitly has an 'any' type. tupleTypeInference3.ts(45,24): error TS7006: Parameter 'a' implicitly has an 'any' type. +tupleTypeInference3.ts(58,6): error TS7006: Parameter 'a' implicitly has an 'any' type. -==== tupleTypeInference3.ts (5 errors) ==== +==== tupleTypeInference3.ts (6 errors) ==== // Repro from #55632 type InferArg = @@ -77,4 +78,14 @@ tupleTypeInference3.ts(45,24): error TS7006: Parameter 'a' implicitly has an 'an const v112: T11 = [1, (a) => { a === "" }, 0]; const v113: T11 = [1, (a) => { a === 0 }, 0, ""]; const v114: T11 = [1, (a) => { a === "" }, 0, true]; + + type T12 = { 1: (arg: number) => void } | { 0?: number, 1: (arg: boolean) => void } | { 0: (arg: boolean) => void } | { 0: boolean, 1: (arg: number) => void } | [null, (arg: string) => void]; + function f(a: T12) { }; + f([null, (a) => { a === "" }]); + f([true, (a) => { a === 0 }]); + f([(a) => { a === true }]); + f([,(a) => { }]); // Error + ~ +!!! error TS7006: Parameter 'a' implicitly has an 'any' type. + \ No newline at end of file diff --git a/tests/baselines/reference/tupleTypeInference3.js b/tests/baselines/reference/tupleTypeInference3.js index d2646a5b148e3..f58a148bab056 100644 --- a/tests/baselines/reference/tupleTypeInference3.js +++ b/tests/baselines/reference/tupleTypeInference3.js @@ -52,6 +52,14 @@ const v111: T11 = [1, (a) => { a === "" }]; const v112: T11 = [1, (a) => { a === "" }, 0]; const v113: T11 = [1, (a) => { a === 0 }, 0, ""]; const v114: T11 = [1, (a) => { a === "" }, 0, true]; + +type T12 = { 1: (arg: number) => void } | { 0?: number, 1: (arg: boolean) => void } | { 0: (arg: boolean) => void } | { 0: boolean, 1: (arg: number) => void } | [null, (arg: string) => void]; +function f(a: T12) { }; +f([null, (a) => { a === "" }]); +f([true, (a) => { a === 0 }]); +f([(a) => { a === true }]); +f([,(a) => { }]); // Error + //// [tupleTypeInference3.js] @@ -82,3 +90,9 @@ var v111 = [1, function (a) { a === ""; }]; var v112 = [1, function (a) { a === ""; }, 0]; var v113 = [1, function (a) { a === 0; }, 0, ""]; var v114 = [1, function (a) { a === ""; }, 0, true]; +function f(a) { } +; +f([null, function (a) { a === ""; }]); +f([true, function (a) { a === 0; }]); +f([function (a) { a === true; }]); +f([, function (a) { }]); // Error diff --git a/tests/baselines/reference/tupleTypeInference3.symbols b/tests/baselines/reference/tupleTypeInference3.symbols index da9aaf0ee7457..37eba69f9987d 100644 --- a/tests/baselines/reference/tupleTypeInference3.symbols +++ b/tests/baselines/reference/tupleTypeInference3.symbols @@ -217,3 +217,42 @@ const v114: T11 = [1, (a) => { a === "" }, 0, true]; >a : Symbol(a, Decl(tupleTypeInference3.ts, 50, 23)) >a : Symbol(a, Decl(tupleTypeInference3.ts, 50, 23)) +type T12 = { 1: (arg: number) => void } | { 0?: number, 1: (arg: boolean) => void } | { 0: (arg: boolean) => void } | { 0: boolean, 1: (arg: number) => void } | [null, (arg: string) => void]; +>T12 : Symbol(T12, Decl(tupleTypeInference3.ts, 50, 52)) +>1 : Symbol(1, Decl(tupleTypeInference3.ts, 52, 12)) +>arg : Symbol(arg, Decl(tupleTypeInference3.ts, 52, 17)) +>0 : Symbol(0, Decl(tupleTypeInference3.ts, 52, 43)) +>1 : Symbol(1, Decl(tupleTypeInference3.ts, 52, 55)) +>arg : Symbol(arg, Decl(tupleTypeInference3.ts, 52, 60)) +>0 : Symbol(0, Decl(tupleTypeInference3.ts, 52, 87)) +>arg : Symbol(arg, Decl(tupleTypeInference3.ts, 52, 92)) +>0 : Symbol(0, Decl(tupleTypeInference3.ts, 52, 119)) +>1 : Symbol(1, Decl(tupleTypeInference3.ts, 52, 131)) +>arg : Symbol(arg, Decl(tupleTypeInference3.ts, 52, 136)) +>arg : Symbol(arg, Decl(tupleTypeInference3.ts, 52, 169)) + +function f(a: T12) { }; +>f : Symbol(f, Decl(tupleTypeInference3.ts, 52, 191)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 53, 11)) +>T12 : Symbol(T12, Decl(tupleTypeInference3.ts, 50, 52)) + +f([null, (a) => { a === "" }]); +>f : Symbol(f, Decl(tupleTypeInference3.ts, 52, 191)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 54, 10)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 54, 10)) + +f([true, (a) => { a === 0 }]); +>f : Symbol(f, Decl(tupleTypeInference3.ts, 52, 191)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 55, 10)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 55, 10)) + +f([(a) => { a === true }]); +>f : Symbol(f, Decl(tupleTypeInference3.ts, 52, 191)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 56, 4)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 56, 4)) + +f([,(a) => { }]); // Error +>f : Symbol(f, Decl(tupleTypeInference3.ts, 52, 191)) +>a : Symbol(a, Decl(tupleTypeInference3.ts, 57, 5)) + + diff --git a/tests/baselines/reference/tupleTypeInference3.types b/tests/baselines/reference/tupleTypeInference3.types index fb05bdab56734..55672c9069baf 100644 --- a/tests/baselines/reference/tupleTypeInference3.types +++ b/tests/baselines/reference/tupleTypeInference3.types @@ -559,3 +559,106 @@ const v114: T11 = [1, (a) => { a === "" }, 0, true]; >true : true > : ^^^^ +type T12 = { 1: (arg: number) => void } | { 0?: number, 1: (arg: boolean) => void } | { 0: (arg: boolean) => void } | { 0: boolean, 1: (arg: number) => void } | [null, (arg: string) => void]; +>T12 : T12 +> : ^^^ +>1 : (arg: number) => void +> : ^ ^^ ^^^^^ +>arg : number +> : ^^^^^^ +>0 : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>1 : (arg: boolean) => void +> : ^ ^^ ^^^^^ +>arg : boolean +> : ^^^^^^^ +>0 : (arg: boolean) => void +> : ^ ^^ ^^^^^ +>arg : boolean +> : ^^^^^^^ +>0 : boolean +> : ^^^^^^^ +>1 : (arg: number) => void +> : ^ ^^ ^^^^^ +>arg : number +> : ^^^^^^ +>arg : string +> : ^^^^^^ + +function f(a: T12) { }; +>f : (a: T12) => void +> : ^ ^^ ^^^^^^^^^ +>a : T12 +> : ^^^ + +f([null, (a) => { a === "" }]); +>f([null, (a) => { a === "" }]) : void +> : ^^^^ +>f : (a: T12) => void +> : ^ ^^ ^^^^^^^^^ +>[null, (a) => { a === "" }] : [null, (a: string) => void] +> : ^^^^^^^^ ^^^^^^^^^^^^^^^^^^ +>(a) => { a === "" } : (a: string) => void +> : ^ ^^^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>a === "" : boolean +> : ^^^^^^^ +>a : string +> : ^^^^^^ +>"" : "" +> : ^^ + +f([true, (a) => { a === 0 }]); +>f([true, (a) => { a === 0 }]) : void +> : ^^^^ +>f : (a: T12) => void +> : ^ ^^ ^^^^^^^^^ +>[true, (a) => { a === 0 }] : [true, (a: number) => void] +> : ^^^^^^^^ ^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>(a) => { a === 0 } : (a: number) => void +> : ^ ^^^^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>a === 0 : boolean +> : ^^^^^^^ +>a : number +> : ^^^^^^ +>0 : 0 +> : ^ + +f([(a) => { a === true }]); +>f([(a) => { a === true }]) : void +> : ^^^^ +>f : (a: T12) => void +> : ^ ^^ ^^^^^^^^^ +>[(a) => { a === true }] : [(a: boolean) => void] +> : ^^ ^^^^^^^^^^^^^^^^^^^ +>(a) => { a === true } : (a: boolean) => void +> : ^ ^^^^^^^^^^^^^^^^^^ +>a : boolean +> : ^^^^^^^ +>a === true : boolean +> : ^^^^^^^ +>a : boolean +> : ^^^^^^^ +>true : true +> : ^^^^ + +f([,(a) => { }]); // Error +>f([,(a) => { }]) : void +> : ^^^^ +>f : (a: T12) => void +> : ^ ^^ ^^^^^^^^^ +>[,(a) => { }] : [undefined, (a: any) => void] +> : ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +> : undefined +> : ^^^^^^^^^ +>(a) => { } : (a: any) => void +> : ^ ^^^^^^^^^^^^^^ +>a : any +> : ^^^ + + diff --git a/tests/cases/compiler/tupleTypeInference3.ts b/tests/cases/compiler/tupleTypeInference3.ts index d66601d2e0d33..9fa2fc8dc1a34 100644 --- a/tests/cases/compiler/tupleTypeInference3.ts +++ b/tests/cases/compiler/tupleTypeInference3.ts @@ -51,3 +51,11 @@ const v111: T11 = [1, (a) => { a === "" }]; const v112: T11 = [1, (a) => { a === "" }, 0]; const v113: T11 = [1, (a) => { a === 0 }, 0, ""]; const v114: T11 = [1, (a) => { a === "" }, 0, true]; + +type T12 = { 1: (arg: number) => void } | { 0?: number, 1: (arg: boolean) => void } | { 0: (arg: boolean) => void } | { 0: boolean, 1: (arg: number) => void } | [null, (arg: string) => void]; +function f(a: T12) { }; +f([null, (a) => { a === "" }]); +f([true, (a) => { a === 0 }]); +f([(a) => { a === true }]); +f([,(a) => { }]); // Error +