Skip to content

Commit ee1f262

Browse files
authored
Template literal types for template literal expressions (#41891)
* Infer template literal types for template literal expressions * Update test * Update another test * Accept new baselines * Minor fixes * Add tests * Accept new baselines * Make new TypeFlags internal * Accept new API baselines * Ensure template literals assignable to String, Object, {}, etc. * Add tests
1 parent 78ded65 commit ee1f262

File tree

212 files changed

+2088
-1016
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+2088
-1016
lines changed

Diff for: src/compiler/checker.ts

+35-28
Original file line numberDiff line numberDiff line change
@@ -13187,7 +13187,7 @@ namespace ts {
1318713187
i--;
1318813188
const t = types[i];
1318913189
const remove =
13190-
t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String ||
13190+
t.flags & TypeFlags.StringLikeLiteral && includes & TypeFlags.String ||
1319113191
t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
1319213192
t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt ||
1319313193
t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
@@ -13234,7 +13234,7 @@ namespace ts {
1323413234
}
1323513235
switch (unionReduction) {
1323613236
case UnionReduction.Literal:
13237-
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) {
13237+
if (includes & (TypeFlags.FreshableLiteral | TypeFlags.UniqueESSymbol)) {
1323813238
removeRedundantLiteralTypes(typeSet, includes);
1323913239
}
1324013240
if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
@@ -13765,6 +13765,7 @@ namespace ts {
1376513765
let type = templateLiteralTypes.get(id);
1376613766
if (!type) {
1376713767
templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes));
13768+
type.regularType = type;
1376813769
}
1376913770
return type;
1377013771

@@ -14803,26 +14804,28 @@ namespace ts {
1480314804
}
1480414805

1480514806
function getFreshTypeOfLiteralType(type: Type): Type {
14806-
if (type.flags & TypeFlags.Literal) {
14807-
if (!(<LiteralType>type).freshType) {
14808-
const freshType = createLiteralType(type.flags, (<LiteralType>type).value, (<LiteralType>type).symbol);
14809-
freshType.regularType = <LiteralType>type;
14807+
if (type.flags & TypeFlags.FreshableLiteral) {
14808+
if (!(<FreshableLiteralType>type).freshType) {
14809+
const freshType = type.flags & TypeFlags.TemplateLiteral ?
14810+
createTemplateLiteralType((<TemplateLiteralType>type).texts, (<TemplateLiteralType>type).types) :
14811+
createLiteralType(type.flags, (<LiteralType>type).value, (<LiteralType>type).symbol);
14812+
freshType.regularType = <FreshableLiteralType>type;
1481014813
freshType.freshType = freshType;
14811-
(<LiteralType>type).freshType = freshType;
14814+
(<FreshableLiteralType>type).freshType = freshType;
1481214815
}
14813-
return (<LiteralType>type).freshType;
14816+
return (<FreshableLiteralType>type).freshType;
1481414817
}
1481514818
return type;
1481614819
}
1481714820

1481814821
function getRegularTypeOfLiteralType(type: Type): Type {
14819-
return type.flags & TypeFlags.Literal ? (<LiteralType>type).regularType :
14822+
return type.flags & TypeFlags.FreshableLiteral ? (<FreshableLiteralType>type).regularType :
1482014823
type.flags & TypeFlags.Union ? ((<UnionType>type).regularType || ((<UnionType>type).regularType = getUnionType(sameMap((<UnionType>type).types, getRegularTypeOfLiteralType)) as UnionType)) :
1482114824
type;
1482214825
}
1482314826

1482414827
function isFreshLiteralType(type: Type) {
14825-
return !!(type.flags & TypeFlags.Literal) && (<LiteralType>type).freshType === type;
14828+
return !!(type.flags & TypeFlags.FreshableLiteral) && (<FreshableLiteralType>type).freshType === type;
1482614829
}
1482714830

1482814831
function getLiteralType(value: string): StringLiteralType;
@@ -17694,17 +17697,20 @@ namespace ts {
1769417697
}
1769517698
}
1769617699
else if (source.flags & TypeFlags.TemplateLiteral) {
17697-
if (target.flags & TypeFlags.TemplateLiteral &&
17698-
(source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length &&
17699-
(source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length &&
17700-
every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) &&
17701-
every((instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)) as TemplateLiteralType).types, (t, i) => !!((target as TemplateLiteralType).types[i].flags & (TypeFlags.Any | TypeFlags.String)) || !!isRelatedTo(t, (target as TemplateLiteralType).types[i], /*reportErrors*/ false))) {
17702-
return Ternary.True;
17700+
if (target.flags & TypeFlags.TemplateLiteral) {
17701+
if ((source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length &&
17702+
(source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length &&
17703+
every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) &&
17704+
every((instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)) as TemplateLiteralType).types, (t, i) => !!((target as TemplateLiteralType).types[i].flags & (TypeFlags.Any | TypeFlags.String)) || !!isRelatedTo(t, (target as TemplateLiteralType).types[i], /*reportErrors*/ false))) {
17705+
return Ternary.True;
17706+
}
1770317707
}
17704-
const constraint = getBaseConstraintOfType(source);
17705-
if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, reportErrors))) {
17706-
resetErrorInfo(saveErrorInfo);
17707-
return result;
17708+
else {
17709+
const constraint = getBaseConstraintOfType(source);
17710+
if (result = isRelatedTo(constraint && constraint !== source ? constraint : stringType, target, reportErrors)) {
17711+
resetErrorInfo(saveErrorInfo);
17712+
return result;
17713+
}
1770817714
}
1770917715
}
1771017716
else if (source.flags & TypeFlags.StringMapping) {
@@ -19181,7 +19187,7 @@ namespace ts {
1918119187

1918219188
function getBaseTypeOfLiteralType(type: Type): Type {
1918319189
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
19184-
type.flags & TypeFlags.StringLiteral ? stringType :
19190+
type.flags & TypeFlags.StringLikeLiteral ? stringType :
1918519191
type.flags & TypeFlags.NumberLiteral ? numberType :
1918619192
type.flags & TypeFlags.BigIntLiteral ? bigintType :
1918719193
type.flags & TypeFlags.BooleanLiteral ? booleanType :
@@ -19191,7 +19197,7 @@ namespace ts {
1919119197

1919219198
function getWidenedLiteralType(type: Type): Type {
1919319199
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
19194-
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
19200+
type.flags & TypeFlags.StringLikeLiteral && isFreshLiteralType(type) ? stringType :
1919519201
type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType :
1919619202
type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType :
1919719203
type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType :
@@ -20692,7 +20698,7 @@ namespace ts {
2069220698
}
2069320699

2069420700
function isTypeOrBaseIdenticalTo(s: Type, t: Type) {
20695-
return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral);
20701+
return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLikeLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral);
2069620702
}
2069720703

2069820704
function isTypeCloselyMatchedBy(s: Type, t: Type) {
@@ -30837,7 +30843,7 @@ namespace ts {
3083730843
texts.push(span.literal.text);
3083830844
types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType);
3083930845
}
30840-
return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType;
30846+
return getFreshTypeOfLiteralType(getTemplateLiteralType(texts, types));
3084130847
}
3084230848

3084330849
function getContextNode(node: Expression): Node {
@@ -30858,7 +30864,7 @@ namespace ts {
3085830864
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
3085930865
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
3086030866
// here would be to not mark contextually typed literals as fresh in the first place.
30861-
const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ?
30867+
const result = maybeTypeOfKind(type, TypeFlags.FreshableLiteral) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ?
3086230868
getRegularTypeOfLiteralType(type) : type;
3086330869
return result;
3086430870
}
@@ -30948,15 +30954,15 @@ namespace ts {
3094830954
// this a literal context for literals of that primitive type. For example, given a
3094930955
// type parameter 'T extends string', infer string literal types for T.
3095030956
const constraint = getBaseConstraintOfType(contextualType) || unknownType;
30951-
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
30957+
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLikeLiteral) ||
3095230958
maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
3095330959
maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
3095430960
maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
3095530961
isLiteralOfContextualType(candidateType, constraint);
3095630962
}
3095730963
// If the contextual type is a literal of a particular primitive type, we consider this a
3095830964
// literal context for all literals of that primitive type.
30959-
return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
30965+
return !!(contextualType.flags & (TypeFlags.StringLikeLiteral | TypeFlags.Index | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLikeLiteral) ||
3096030966
contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
3096130967
contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
3096230968
contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
@@ -38482,7 +38488,8 @@ namespace ts {
3848238488

3848338489
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
3848438490
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
38485-
return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node)));
38491+
const type = getTypeOfSymbol(getSymbolOfNode(node));
38492+
return !!(type.flags & TypeFlags.Literal) && isFreshLiteralType(type);
3848638493
}
3848738494
return false;
3848838495
}

Diff for: src/compiler/types.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -4970,6 +4970,10 @@ namespace ts {
49704970
Unit = Literal | UniqueESSymbol | Nullable,
49714971
StringOrNumberLiteral = StringLiteral | NumberLiteral,
49724972
/* @internal */
4973+
StringLikeLiteral = StringLiteral | TemplateLiteral,
4974+
/* @internal */
4975+
FreshableLiteral = Literal | TemplateLiteral,
4976+
/* @internal */
49734977
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
49744978
/* @internal */
49754979
DefinitelyFalsy = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral | Void | Undefined | Null,
@@ -5063,7 +5067,9 @@ namespace ts {
50635067
}
50645068

50655069
/* @internal */
5066-
export type FreshableType = LiteralType | FreshableIntrinsicType;
5070+
export type FreshableLiteralType = LiteralType | TemplateLiteralType;
5071+
/* @internal */
5072+
export type FreshableType = FreshableLiteralType | FreshableIntrinsicType;
50675073

50685074
// String literal types (TypeFlags.StringLiteral)
50695075
// Numeric literal types (TypeFlags.NumberLiteral)
@@ -5461,6 +5467,8 @@ namespace ts {
54615467
export interface TemplateLiteralType extends InstantiableType {
54625468
texts: readonly string[]; // Always one element longer than types
54635469
types: readonly Type[]; // Always at least one element
5470+
freshType: TemplateLiteralType; // Fresh version of type
5471+
regularType: TemplateLiteralType; // Regular version of type
54645472
}
54655473

54665474
export interface StringMappingType extends InstantiableType {

Diff for: tests/baselines/reference/TemplateExpression1.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
=== tests/cases/conformance/es6/templates/TemplateExpression1.ts ===
22
var v = `foo ${ a
33
>v : string
4-
>`foo ${ a : string
4+
>`foo ${ a : `foo ${any}`
55
>a : any
66

Diff for: tests/baselines/reference/accessorsOverrideProperty2.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Derived extends Base {
2222
>console.log : (...data: any[]) => void
2323
>console : Console
2424
>log : (...data: any[]) => void
25-
>`x was set to ${value}` : string
25+
>`x was set to ${value}` : `x was set to ${number}`
2626
>value : number
2727
}
2828

Diff for: tests/baselines/reference/api/tsserverlibrary.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2653,6 +2653,8 @@ declare namespace ts {
26532653
export interface TemplateLiteralType extends InstantiableType {
26542654
texts: readonly string[];
26552655
types: readonly Type[];
2656+
freshType: TemplateLiteralType;
2657+
regularType: TemplateLiteralType;
26562658
}
26572659
export interface StringMappingType extends InstantiableType {
26582660
symbol: Symbol;

Diff for: tests/baselines/reference/api/typescript.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2653,6 +2653,8 @@ declare namespace ts {
26532653
export interface TemplateLiteralType extends InstantiableType {
26542654
texts: readonly string[];
26552655
types: readonly Type[];
2656+
freshType: TemplateLiteralType;
2657+
regularType: TemplateLiteralType;
26562658
}
26572659
export interface StringMappingType extends InstantiableType {
26582660
symbol: Symbol;

Diff for: tests/baselines/reference/asOperator3.types

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ declare function tag(...x: any[]): any;
55

66
var a = `${123 + 456 as number}`;
77
>a : string
8-
>`${123 + 456 as number}` : string
8+
>`${123 + 456 as number}` : `${number}`
99
>123 + 456 as number : number
1010
>123 + 456 : number
1111
>123 : 123
1212
>456 : 456
1313

1414
var b = `leading ${123 + 456 as number}`;
1515
>b : string
16-
>`leading ${123 + 456 as number}` : string
16+
>`leading ${123 + 456 as number}` : `leading ${number}`
1717
>123 + 456 as number : number
1818
>123 + 456 : number
1919
>123 : 123
2020
>456 : 456
2121

2222
var c = `${123 + 456 as number} trailing`;
2323
>c : string
24-
>`${123 + 456 as number} trailing` : string
24+
>`${123 + 456 as number} trailing` : `${number} trailing`
2525
>123 + 456 as number : number
2626
>123 + 456 : number
2727
>123 : 123
@@ -30,7 +30,7 @@ var c = `${123 + 456 as number} trailing`;
3030
var d = `Hello ${123} World` as string;
3131
>d : string
3232
>`Hello ${123} World` as string : string
33-
>`Hello ${123} World` : string
33+
>`Hello ${123} World` : "Hello 123 World"
3434
>123 : 123
3535

3636
var e = `Hello` as string;
@@ -43,15 +43,15 @@ var f = 1 + `${1} end of string` as string;
4343
>1 + `${1} end of string` as string : string
4444
>1 + `${1} end of string` : string
4545
>1 : 1
46-
>`${1} end of string` : string
46+
>`${1} end of string` : "1 end of string"
4747
>1 : 1
4848

4949
var g = tag `Hello ${123} World` as string;
5050
>g : string
5151
>tag `Hello ${123} World` as string : string
5252
>tag `Hello ${123} World` : any
5353
>tag : (...x: any[]) => any
54-
>`Hello ${123} World` : string
54+
>`Hello ${123} World` : "Hello 123 World"
5555
>123 : 123
5656

5757
var h = tag `Hello` as string;

Diff for: tests/baselines/reference/checkJsObjectLiteralIndexSignatures.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ let n = Math.random();
1010

1111
let s = `${n}`;
1212
>s : string
13-
>`${n}` : string
13+
>`${n}` : `${number}`
1414
>n : number
1515

1616
const numericIndex = { [n]: 1 };

Diff for: tests/baselines/reference/computedPropertyNames10_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ var v = {
6060

6161
[`hello ${a} bye`]() { }
6262
>[`hello ${a} bye`] : () => void
63-
>`hello ${a} bye` : string
63+
>`hello ${a} bye` : `hello ${any} bye`
6464
>a : any
6565
}

Diff for: tests/baselines/reference/computedPropertyNames10_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ var v = {
6060

6161
[`hello ${a} bye`]() { }
6262
>[`hello ${a} bye`] : () => void
63-
>`hello ${a} bye` : string
63+
>`hello ${a} bye` : `hello ${any} bye`
6464
>a : any
6565
}

Diff for: tests/baselines/reference/computedPropertyNames11_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var v = {
7070

7171
get [`hello ${a} bye`]() { return 0; }
7272
>[`hello ${a} bye`] : number
73-
>`hello ${a} bye` : string
73+
>`hello ${a} bye` : `hello ${any} bye`
7474
>a : any
7575
>0 : 0
7676
}

Diff for: tests/baselines/reference/computedPropertyNames11_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var v = {
7070

7171
get [`hello ${a} bye`]() { return 0; }
7272
>[`hello ${a} bye`] : number
73-
>`hello ${a} bye` : string
73+
>`hello ${a} bye` : `hello ${any} bye`
7474
>a : any
7575
>0 : 0
7676
}

Diff for: tests/baselines/reference/computedPropertyNames12_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class C {
6363

6464
static [`hello ${a} bye`] = 0
6565
>[`hello ${a} bye`] : number
66-
>`hello ${a} bye` : string
66+
>`hello ${a} bye` : `hello ${any} bye`
6767
>a : any
6868
>0 : 0
6969
}

Diff for: tests/baselines/reference/computedPropertyNames12_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class C {
6363

6464
static [`hello ${a} bye`] = 0
6565
>[`hello ${a} bye`] : number
66-
>`hello ${a} bye` : string
66+
>`hello ${a} bye` : `hello ${any} bye`
6767
>a : any
6868
>0 : 0
6969
}

Diff for: tests/baselines/reference/computedPropertyNames13_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,6 @@ class C {
5959

6060
static [`hello ${a} bye`]() { }
6161
>[`hello ${a} bye`] : () => void
62-
>`hello ${a} bye` : string
62+
>`hello ${a} bye` : `hello ${any} bye`
6363
>a : any
6464
}

Diff for: tests/baselines/reference/computedPropertyNames13_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,6 @@ class C {
5959

6060
static [`hello ${a} bye`]() { }
6161
>[`hello ${a} bye`] : () => void
62-
>`hello ${a} bye` : string
62+
>`hello ${a} bye` : `hello ${any} bye`
6363
>a : any
6464
}

Diff for: tests/baselines/reference/computedPropertyNames16_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class C {
6969

7070
get [`hello ${a} bye`]() { return 0; }
7171
>[`hello ${a} bye`] : number
72-
>`hello ${a} bye` : string
72+
>`hello ${a} bye` : `hello ${any} bye`
7373
>a : any
7474
>0 : 0
7575
}

Diff for: tests/baselines/reference/computedPropertyNames16_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class C {
6969

7070
get [`hello ${a} bye`]() { return 0; }
7171
>[`hello ${a} bye`] : number
72-
>`hello ${a} bye` : string
72+
>`hello ${a} bye` : `hello ${any} bye`
7373
>a : any
7474
>0 : 0
7575
}

Diff for: tests/baselines/reference/computedPropertyNames4_ES5.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var v = {
7070

7171
[`hello ${a} bye`]: 0
7272
>[`hello ${a} bye`] : number
73-
>`hello ${a} bye` : string
73+
>`hello ${a} bye` : `hello ${any} bye`
7474
>a : any
7575
>0 : 0
7676
}

Diff for: tests/baselines/reference/computedPropertyNames4_ES6.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var v = {
7070

7171
[`hello ${a} bye`]: 0
7272
>[`hello ${a} bye`] : number
73-
>`hello ${a} bye` : string
73+
>`hello ${a} bye` : `hello ${any} bye`
7474
>a : any
7575
>0 : 0
7676
}

0 commit comments

Comments
 (0)