diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 074dd90fe6d..364f0de7f64 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21939,8 +21939,16 @@ namespace ts { function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { const matches = inferTypesFromTemplateLiteralType(source, target); const types = target.types; - for (let i = 0; i < types.length; i++) { - inferFromTypes(matches ? matches[i] : neverType, types[i]); + // When the target template literal contains only placeholders (meaning that inference is intended to extract + // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for + // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an + // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, + // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might + // succeed. That would be a pointless and confusing outcome. + if (matches || every(target.texts, s => s.length === 0)) { + for (let i = 0; i < types.length; i++) { + inferFromTypes(matches ? matches[i] : neverType, types[i]); + } } } diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types index bc572e20bdc..102e9a91930 100644 --- a/tests/baselines/reference/templateLiteralTypes1.types +++ b/tests/baselines/reference/templateLiteralTypes1.types @@ -419,7 +419,7 @@ type Foo = T extends `*${infer S}*` ? S : never; >Foo : Foo type TF1 = Foo; // never ->TF1 : never +>TF1 : string type TF2 = Foo; // never >TF2 : never diff --git a/tests/baselines/reference/templateLiteralTypes3.errors.txt b/tests/baselines/reference/templateLiteralTypes3.errors.txt index 4e177c8d225..81865b5dba5 100644 --- a/tests/baselines/reference/templateLiteralTypes3.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes3.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/types/literal/templateLiteralTypes3.ts(20,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. +tests/cases/conformance/types/literal/templateLiteralTypes3.ts(20,19): error TS2345: Argument of type '"hello"' is not assignable to parameter of type '`*${string}*`'. tests/cases/conformance/types/literal/templateLiteralTypes3.ts(57,5): error TS2322: Type '"hello"' is not assignable to type '`*${string}*`'. tests/cases/conformance/types/literal/templateLiteralTypes3.ts(69,5): error TS2322: Type '"123"' is not assignable to type '`*${number}*`'. tests/cases/conformance/types/literal/templateLiteralTypes3.ts(71,5): error TS2322: Type '"**123**"' is not assignable to type '`*${number}*`'. @@ -29,7 +29,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS23 function f1(s: string, n: number, b: boolean, t: T) { let x1 = foo1('hello'); // Error ~~~~~~~ -!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. +!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type '`*${string}*`'. let x2 = foo1('*hello*'); let x3 = foo1('**hello**'); let x4 = foo1(`*${s}*` as const); @@ -138,4 +138,12 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS23 interface ITest

> { blah: string; } + + // Repro from #45906 + + type Schema = { a: { b: { c: number } } }; + + declare function chain(field: F | `${F}.${F}`): void; + + chain("a"); \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralTypes3.js b/tests/baselines/reference/templateLiteralTypes3.js index 7557ad9f474..07a3e669db0 100644 --- a/tests/baselines/reference/templateLiteralTypes3.js +++ b/tests/baselines/reference/templateLiteralTypes3.js @@ -116,6 +116,14 @@ type PrefixData

= `${P}:baz`; interface ITest

> { blah: string; } + +// Repro from #45906 + +type Schema = { a: { b: { c: number } } }; + +declare function chain(field: F | `${F}.${F}`): void; + +chain("a"); //// [templateLiteralTypes3.js] @@ -168,6 +176,7 @@ var templated1 = value1 + " abc"; // Type '`${string} abc`' is not assignable to type '`${string} ${string}`'. var value2 = "abc"; var templated2 = value2 + " abc"; +chain("a"); //// [templateLiteralTypes3.d.ts] @@ -216,3 +225,11 @@ declare type PrefixData

= `${P}:baz`; interface ITest

> { blah: string; } +declare type Schema = { + a: { + b: { + c: number; + }; + }; +}; +declare function chain(field: F | `${F}.${F}`): void; diff --git a/tests/baselines/reference/templateLiteralTypes3.symbols b/tests/baselines/reference/templateLiteralTypes3.symbols index ca53672a1c7..509a9a36c37 100644 --- a/tests/baselines/reference/templateLiteralTypes3.symbols +++ b/tests/baselines/reference/templateLiteralTypes3.symbols @@ -385,3 +385,23 @@ interface ITest

> { >blah : Symbol(ITest.blah, Decl(templateLiteralTypes3.ts, 114, 78)) } +// Repro from #45906 + +type Schema = { a: { b: { c: number } } }; +>Schema : Symbol(Schema, Decl(templateLiteralTypes3.ts, 116, 1)) +>a : Symbol(a, Decl(templateLiteralTypes3.ts, 120, 15)) +>b : Symbol(b, Decl(templateLiteralTypes3.ts, 120, 20)) +>c : Symbol(c, Decl(templateLiteralTypes3.ts, 120, 25)) + +declare function chain(field: F | `${F}.${F}`): void; +>chain : Symbol(chain, Decl(templateLiteralTypes3.ts, 120, 42)) +>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23)) +>Schema : Symbol(Schema, Decl(templateLiteralTypes3.ts, 116, 1)) +>field : Symbol(field, Decl(templateLiteralTypes3.ts, 122, 47)) +>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23)) +>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23)) +>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23)) + +chain("a"); +>chain : Symbol(chain, Decl(templateLiteralTypes3.ts, 120, 42)) + diff --git a/tests/baselines/reference/templateLiteralTypes3.types b/tests/baselines/reference/templateLiteralTypes3.types index 7fd34b2337d..934fa60857f 100644 --- a/tests/baselines/reference/templateLiteralTypes3.types +++ b/tests/baselines/reference/templateLiteralTypes3.types @@ -49,8 +49,8 @@ function f1(s: string, n: number, b: boolean, t: T) { >t : T let x1 = foo1('hello'); // Error ->x1 : never ->foo1('hello') : never +>x1 : string +>foo1('hello') : string >foo1 : (arg: `*${V}*`) => V >'hello' : "hello" @@ -379,3 +379,20 @@ interface ITest

> { >blah : string } +// Repro from #45906 + +type Schema = { a: { b: { c: number } } }; +>Schema : Schema +>a : { b: { c: number;}; } +>b : { c: number; } +>c : number + +declare function chain(field: F | `${F}.${F}`): void; +>chain : (field: F | `${F}.${F}`) => void +>field : F | `${F}.${F}` + +chain("a"); +>chain("a") : void +>chain : (field: F | `${F}.${F}`) => void +>"a" : "a" + diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts index fa29ad779ab..b5fc39d32ef 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts @@ -118,3 +118,11 @@ type PrefixData

= `${P}:baz`; interface ITest

> { blah: string; } + +// Repro from #45906 + +type Schema = { a: { b: { c: number } } }; + +declare function chain(field: F | `${F}.${F}`): void; + +chain("a");