Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Template literal types for template literal expressions #41891

Merged
merged 11 commits into from
Dec 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13137,7 +13137,7 @@ namespace ts {
i--;
const t = types[i];
const remove =
t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String ||
t.flags & TypeFlags.StringLikeLiteral && includes & TypeFlags.String ||
t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt ||
t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
Expand Down Expand Up @@ -13184,7 +13184,7 @@ namespace ts {
}
switch (unionReduction) {
case UnionReduction.Literal:
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) {
if (includes & (TypeFlags.FreshableLiteral | TypeFlags.UniqueESSymbol)) {
removeRedundantLiteralTypes(typeSet, includes);
}
if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
Expand Down Expand Up @@ -13715,6 +13715,7 @@ namespace ts {
let type = templateLiteralTypes.get(id);
if (!type) {
templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes));
type.regularType = type;
}
return type;

Expand Down Expand Up @@ -14753,26 +14754,28 @@ namespace ts {
}

function getFreshTypeOfLiteralType(type: Type): Type {
if (type.flags & TypeFlags.Literal) {
if (!(<LiteralType>type).freshType) {
const freshType = createLiteralType(type.flags, (<LiteralType>type).value, (<LiteralType>type).symbol);
freshType.regularType = <LiteralType>type;
if (type.flags & TypeFlags.FreshableLiteral) {
if (!(<FreshableLiteralType>type).freshType) {
const freshType = type.flags & TypeFlags.TemplateLiteral ?
createTemplateLiteralType((<TemplateLiteralType>type).texts, (<TemplateLiteralType>type).types) :
createLiteralType(type.flags, (<LiteralType>type).value, (<LiteralType>type).symbol);
freshType.regularType = <FreshableLiteralType>type;
freshType.freshType = freshType;
(<LiteralType>type).freshType = freshType;
(<FreshableLiteralType>type).freshType = freshType;
}
return (<LiteralType>type).freshType;
return (<FreshableLiteralType>type).freshType;
}
return type;
}

function getRegularTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.Literal ? (<LiteralType>type).regularType :
return type.flags & TypeFlags.FreshableLiteral ? (<FreshableLiteralType>type).regularType :
type.flags & TypeFlags.Union ? ((<UnionType>type).regularType || ((<UnionType>type).regularType = getUnionType(sameMap((<UnionType>type).types, getRegularTypeOfLiteralType)) as UnionType)) :
type;
}

function isFreshLiteralType(type: Type) {
return !!(type.flags & TypeFlags.Literal) && (<LiteralType>type).freshType === type;
return !!(type.flags & TypeFlags.FreshableLiteral) && (<FreshableLiteralType>type).freshType === type;
}

function getLiteralType(value: string): StringLiteralType;
Expand Down Expand Up @@ -17613,17 +17616,20 @@ namespace ts {
}
}
else if (source.flags & TypeFlags.TemplateLiteral) {
if (target.flags & TypeFlags.TemplateLiteral &&
(source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length &&
(source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length &&
every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) &&
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))) {
return Ternary.True;
if (target.flags & TypeFlags.TemplateLiteral) {
if ((source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length &&
(source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length &&
every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) &&
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))) {
return Ternary.True;
}
}
const constraint = getBaseConstraintOfType(source);
if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, reportErrors))) {
resetErrorInfo(saveErrorInfo);
return result;
else {
const constraint = getBaseConstraintOfType(source);
if (result = isRelatedTo(constraint && constraint !== source ? constraint : stringType, target, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
}
else if (source.flags & TypeFlags.StringMapping) {
Expand Down Expand Up @@ -19100,7 +19106,7 @@ namespace ts {

function getBaseTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
type.flags & TypeFlags.StringLiteral ? stringType :
type.flags & TypeFlags.StringLikeLiteral ? stringType :
type.flags & TypeFlags.NumberLiteral ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
Expand All @@ -19110,7 +19116,7 @@ namespace ts {

function getWidenedLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
type.flags & TypeFlags.StringLikeLiteral && isFreshLiteralType(type) ? stringType :
type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType :
type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType :
type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType :
Expand Down Expand Up @@ -20611,7 +20617,7 @@ namespace ts {
}

function isTypeOrBaseIdenticalTo(s: Type, t: Type) {
return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral);
return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLikeLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral);
}

function isTypeCloselyMatchedBy(s: Type, t: Type) {
Expand Down Expand Up @@ -30753,7 +30759,7 @@ namespace ts {
texts.push(span.literal.text);
types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType);
}
return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType;
return getFreshTypeOfLiteralType(getTemplateLiteralType(texts, types));
}

function getContextNode(node: Expression): Node {
Expand All @@ -30774,7 +30780,7 @@ namespace ts {
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
// here would be to not mark contextually typed literals as fresh in the first place.
const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ?
const result = maybeTypeOfKind(type, TypeFlags.FreshableLiteral) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ?
getRegularTypeOfLiteralType(type) : type;
return result;
}
Expand Down Expand Up @@ -30864,15 +30870,15 @@ namespace ts {
// this a literal context for literals of that primitive type. For example, given a
// type parameter 'T extends string', infer string literal types for T.
const constraint = getBaseConstraintOfType(contextualType) || unknownType;
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLikeLiteral) ||
maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
isLiteralOfContextualType(candidateType, constraint);
}
// If the contextual type is a literal of a particular primitive type, we consider this a
// literal context for all literals of that primitive type.
return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
return !!(contextualType.flags & (TypeFlags.StringLikeLiteral | TypeFlags.Index | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLikeLiteral) ||
contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) ||
contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
Expand Down Expand Up @@ -38398,7 +38404,8 @@ namespace ts {

function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node)));
const type = getTypeOfSymbol(getSymbolOfNode(node));
return !!(type.flags & TypeFlags.Literal) && isFreshLiteralType(type);
}
return false;
}
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4895,6 +4895,10 @@ namespace ts {
Unit = Literal | UniqueESSymbol | Nullable,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/* @internal */
StringLikeLiteral = StringLiteral | TemplateLiteral,
ahejlsberg marked this conversation as resolved.
Show resolved Hide resolved
/* @internal */
FreshableLiteral = Literal | TemplateLiteral,
ahejlsberg marked this conversation as resolved.
Show resolved Hide resolved
/* @internal */
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
/* @internal */
DefinitelyFalsy = StringLiteral | NumberLiteral | BigIntLiteral | BooleanLiteral | Void | Undefined | Null,
Expand Down Expand Up @@ -4988,7 +4992,9 @@ namespace ts {
}

/* @internal */
export type FreshableType = LiteralType | FreshableIntrinsicType;
export type FreshableLiteralType = LiteralType | TemplateLiteralType;
/* @internal */
export type FreshableType = FreshableLiteralType | FreshableIntrinsicType;

// String literal types (TypeFlags.StringLiteral)
// Numeric literal types (TypeFlags.NumberLiteral)
Expand Down Expand Up @@ -5386,6 +5392,8 @@ namespace ts {
export interface TemplateLiteralType extends InstantiableType {
texts: readonly string[]; // Always one element longer than types
types: readonly Type[]; // Always at least one element
freshType: TemplateLiteralType; // Fresh version of type
regularType: TemplateLiteralType; // Regular version of type
}

export interface StringMappingType extends InstantiableType {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/TemplateExpression1.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/conformance/es6/templates/TemplateExpression1.ts ===
var v = `foo ${ a
>v : string
>`foo ${ a : string
>`foo ${ a : `foo ${any}`
>a : any

2 changes: 1 addition & 1 deletion tests/baselines/reference/accessorsOverrideProperty2.types
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Derived extends Base {
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>`x was set to ${value}` : string
>`x was set to ${value}` : `x was set to ${number}`
>value : number
}

Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2653,6 +2653,8 @@ declare namespace ts {
export interface TemplateLiteralType extends InstantiableType {
texts: readonly string[];
types: readonly Type[];
freshType: TemplateLiteralType;
regularType: TemplateLiteralType;
}
export interface StringMappingType extends InstantiableType {
symbol: Symbol;
Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2653,6 +2653,8 @@ declare namespace ts {
export interface TemplateLiteralType extends InstantiableType {
texts: readonly string[];
types: readonly Type[];
freshType: TemplateLiteralType;
regularType: TemplateLiteralType;
}
export interface StringMappingType extends InstantiableType {
symbol: Symbol;
Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/asOperator3.types
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ declare function tag(...x: any[]): any;

var a = `${123 + 456 as number}`;
>a : string
>`${123 + 456 as number}` : string
>`${123 + 456 as number}` : `${number}`
>123 + 456 as number : number
>123 + 456 : number
>123 : 123
>456 : 456

var b = `leading ${123 + 456 as number}`;
>b : string
>`leading ${123 + 456 as number}` : string
>`leading ${123 + 456 as number}` : `leading ${number}`
>123 + 456 as number : number
>123 + 456 : number
>123 : 123
>456 : 456

var c = `${123 + 456 as number} trailing`;
>c : string
>`${123 + 456 as number} trailing` : string
>`${123 + 456 as number} trailing` : `${number} trailing`
>123 + 456 as number : number
>123 + 456 : number
>123 : 123
Expand All @@ -30,7 +30,7 @@ var c = `${123 + 456 as number} trailing`;
var d = `Hello ${123} World` as string;
>d : string
>`Hello ${123} World` as string : string
>`Hello ${123} World` : string
>`Hello ${123} World` : "Hello 123 World"
>123 : 123

var e = `Hello` as string;
Expand All @@ -43,15 +43,15 @@ var f = 1 + `${1} end of string` as string;
>1 + `${1} end of string` as string : string
>1 + `${1} end of string` : string
>1 : 1
>`${1} end of string` : string
>`${1} end of string` : "1 end of string"
>1 : 1

var g = tag `Hello ${123} World` as string;
>g : string
>tag `Hello ${123} World` as string : string
>tag `Hello ${123} World` : any
>tag : (...x: any[]) => any
>`Hello ${123} World` : string
>`Hello ${123} World` : "Hello 123 World"
>123 : 123

var h = tag `Hello` as string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let n = Math.random();

let s = `${n}`;
>s : string
>`${n}` : string
>`${n}` : `${number}`
>n : number

const numericIndex = { [n]: 1 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ var v = {

[`hello ${a} bye`]() { }
>[`hello ${a} bye`] : () => void
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ var v = {

[`hello ${a} bye`]() { }
>[`hello ${a} bye`] : () => void
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var v = {

get [`hello ${a} bye`]() { return 0; }
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var v = {

get [`hello ${a} bye`]() { return 0; }
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class C {

static [`hello ${a} bye`] = 0
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class C {

static [`hello ${a} bye`] = 0
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ class C {

static [`hello ${a} bye`]() { }
>[`hello ${a} bye`] : () => void
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ class C {

static [`hello ${a} bye`]() { }
>[`hello ${a} bye`] : () => void
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class C {

get [`hello ${a} bye`]() { return 0; }
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class C {

get [`hello ${a} bye`]() { return 0; }
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
2 changes: 1 addition & 1 deletion tests/baselines/reference/computedPropertyNames4_ES5.types
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var v = {

[`hello ${a} bye`]: 0
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
2 changes: 1 addition & 1 deletion tests/baselines/reference/computedPropertyNames4_ES6.types
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var v = {

[`hello ${a} bye`]: 0
>[`hello ${a} bye`] : number
>`hello ${a} bye` : string
>`hello ${a} bye` : `hello ${any} bye`
>a : any
>0 : 0
}
Loading