Skip to content

Commit 26aef89

Browse files
authored
Make never inferences with template literal types only in special cases (#46075)
* Make 'never' inferences with template literal types only in special cases * Accept new baselines * Add regression test * Fix comment
1 parent 4ce902c commit 26aef89

File tree

7 files changed

+85
-7
lines changed

7 files changed

+85
-7
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21939,8 +21939,16 @@ namespace ts {
2193921939
function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) {
2194021940
const matches = inferTypesFromTemplateLiteralType(source, target);
2194121941
const types = target.types;
21942-
for (let i = 0; i < types.length; i++) {
21943-
inferFromTypes(matches ? matches[i] : neverType, types[i]);
21942+
// When the target template literal contains only placeholders (meaning that inference is intended to extract
21943+
// single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for
21944+
// each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an
21945+
// assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which,
21946+
// upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might
21947+
// succeed. That would be a pointless and confusing outcome.
21948+
if (matches || every(target.texts, s => s.length === 0)) {
21949+
for (let i = 0; i < types.length; i++) {
21950+
inferFromTypes(matches ? matches[i] : neverType, types[i]);
21951+
}
2194421952
}
2194521953
}
2194621954

tests/baselines/reference/templateLiteralTypes1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ type Foo<T> = T extends `*${infer S}*` ? S : never;
419419
>Foo : Foo<T>
420420

421421
type TF1 = Foo<any>; // never
422-
>TF1 : never
422+
>TF1 : string
423423

424424
type TF2 = Foo<string>; // never
425425
>TF2 : never

tests/baselines/reference/templateLiteralTypes3.errors.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
tests/cases/conformance/types/literal/templateLiteralTypes3.ts(20,19): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
1+
tests/cases/conformance/types/literal/templateLiteralTypes3.ts(20,19): error TS2345: Argument of type '"hello"' is not assignable to parameter of type '`*${string}*`'.
22
tests/cases/conformance/types/literal/templateLiteralTypes3.ts(57,5): error TS2322: Type '"hello"' is not assignable to type '`*${string}*`'.
33
tests/cases/conformance/types/literal/templateLiteralTypes3.ts(69,5): error TS2322: Type '"123"' is not assignable to type '`*${number}*`'.
44
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
2929
function f1<T extends string>(s: string, n: number, b: boolean, t: T) {
3030
let x1 = foo1('hello'); // Error
3131
~~~~~~~
32-
!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
32+
!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type '`*${string}*`'.
3333
let x2 = foo1('*hello*');
3434
let x3 = foo1('**hello**');
3535
let x4 = foo1(`*${s}*` as const);
@@ -138,4 +138,12 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS23
138138
interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
139139
blah: string;
140140
}
141+
142+
// Repro from #45906
143+
144+
type Schema = { a: { b: { c: number } } };
145+
146+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
147+
148+
chain("a");
141149

tests/baselines/reference/templateLiteralTypes3.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ type PrefixData<P extends Prefixes> = `${P}:baz`;
116116
interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
117117
blah: string;
118118
}
119+
120+
// Repro from #45906
121+
122+
type Schema = { a: { b: { c: number } } };
123+
124+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
125+
126+
chain("a");
119127

120128

121129
//// [templateLiteralTypes3.js]
@@ -168,6 +176,7 @@ var templated1 = value1 + " abc";
168176
// Type '`${string} abc`' is not assignable to type '`${string} ${string}`'.
169177
var value2 = "abc";
170178
var templated2 = value2 + " abc";
179+
chain("a");
171180

172181

173182
//// [templateLiteralTypes3.d.ts]
@@ -216,3 +225,11 @@ declare type PrefixData<P extends Prefixes> = `${P}:baz`;
216225
interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
217226
blah: string;
218227
}
228+
declare type Schema = {
229+
a: {
230+
b: {
231+
c: number;
232+
};
233+
};
234+
};
235+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;

tests/baselines/reference/templateLiteralTypes3.symbols

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,23 @@ interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
385385
>blah : Symbol(ITest.blah, Decl(templateLiteralTypes3.ts, 114, 78))
386386
}
387387

388+
// Repro from #45906
389+
390+
type Schema = { a: { b: { c: number } } };
391+
>Schema : Symbol(Schema, Decl(templateLiteralTypes3.ts, 116, 1))
392+
>a : Symbol(a, Decl(templateLiteralTypes3.ts, 120, 15))
393+
>b : Symbol(b, Decl(templateLiteralTypes3.ts, 120, 20))
394+
>c : Symbol(c, Decl(templateLiteralTypes3.ts, 120, 25))
395+
396+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
397+
>chain : Symbol(chain, Decl(templateLiteralTypes3.ts, 120, 42))
398+
>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23))
399+
>Schema : Symbol(Schema, Decl(templateLiteralTypes3.ts, 116, 1))
400+
>field : Symbol(field, Decl(templateLiteralTypes3.ts, 122, 47))
401+
>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23))
402+
>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23))
403+
>F : Symbol(F, Decl(templateLiteralTypes3.ts, 122, 23))
404+
405+
chain("a");
406+
>chain : Symbol(chain, Decl(templateLiteralTypes3.ts, 120, 42))
407+

tests/baselines/reference/templateLiteralTypes3.types

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ function f1<T extends string>(s: string, n: number, b: boolean, t: T) {
4949
>t : T
5050

5151
let x1 = foo1('hello'); // Error
52-
>x1 : never
53-
>foo1('hello') : never
52+
>x1 : string
53+
>foo1('hello') : string
5454
>foo1 : <V extends string>(arg: `*${V}*`) => V
5555
>'hello' : "hello"
5656

@@ -379,3 +379,20 @@ interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
379379
>blah : string
380380
}
381381

382+
// Repro from #45906
383+
384+
type Schema = { a: { b: { c: number } } };
385+
>Schema : Schema
386+
>a : { b: { c: number;}; }
387+
>b : { c: number; }
388+
>c : number
389+
390+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
391+
>chain : <F extends "a">(field: F | `${F}.${F}`) => void
392+
>field : F | `${F}.${F}`
393+
394+
chain("a");
395+
>chain("a") : void
396+
>chain : <F extends "a">(field: F | `${F}.${F}`) => void
397+
>"a" : "a"
398+

tests/cases/conformance/types/literal/templateLiteralTypes3.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,11 @@ type PrefixData<P extends Prefixes> = `${P}:baz`;
118118
interface ITest<P extends Prefixes, E extends AllPrefixData = PrefixData<P>> {
119119
blah: string;
120120
}
121+
122+
// Repro from #45906
123+
124+
type Schema = { a: { b: { c: number } } };
125+
126+
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
127+
128+
chain("a");

0 commit comments

Comments
 (0)