Skip to content

Commit

Permalink
Properly union inferred template literal string types (#46782)
Browse files Browse the repository at this point in the history
* Template literals and string mappings have 'string' as base type

* Add regression test

* Add tests for generic template literals

* One more test
  • Loading branch information
ahejlsberg committed Dec 6, 2021
1 parent 7a1687d commit 2a7eb58
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Expand Up @@ -20763,7 +20763,7 @@ namespace ts {

function getBaseTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
type.flags & TypeFlags.StringLiteral ? stringType :
type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
type.flags & TypeFlags.NumberLiteral ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
Expand Down
19 changes: 19 additions & 0 deletions tests/baselines/reference/templateLiteralTypes3.errors.txt
Expand Up @@ -198,4 +198,23 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(141,9): error TS2
action.response;
}
}

// Repro from #46768

type DotString = `${string}.${string}.${string}`;

declare function noSpread<P extends DotString>(args: P[]): P;
declare function spread<P extends DotString>(...args: P[]): P;

noSpread([`1.${'2'}.3`, `1.${'2'}.4`]);
noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]);

spread(`1.${'2'}.3`, `1.${'2'}.4`);
spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`);

function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) {
spread(`1.${t}.3`, `1.${t}.4`);
spread(`1.${u}.3`, `1.${u}.4`);
spread(u1, u2);
}

32 changes: 32 additions & 0 deletions tests/baselines/reference/templateLiteralTypes3.js
Expand Up @@ -170,6 +170,25 @@ function reducer(action: Action) {
action.response;
}
}

// Repro from #46768

type DotString = `${string}.${string}.${string}`;

declare function noSpread<P extends DotString>(args: P[]): P;
declare function spread<P extends DotString>(...args: P[]): P;

noSpread([`1.${'2'}.3`, `1.${'2'}.4`]);
noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]);

spread(`1.${'2'}.3`, `1.${'2'}.4`);
spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`);

function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) {
spread(`1.${t}.3`, `1.${t}.4`);
spread(`1.${u}.3`, `1.${u}.4`);
spread(u1, u2);
}


//// [templateLiteralTypes3.js]
Expand Down Expand Up @@ -257,6 +276,15 @@ function reducer(action) {
action.response;
}
}
noSpread(["1.".concat('2', ".3"), "1.".concat('2', ".4")]);
noSpread(["1.".concat('2', ".3"), "1.".concat('2', ".4")]);
spread("1.".concat('2', ".3"), "1.".concat('2', ".4"));
spread("1.".concat('2', ".3"), "1.".concat('2', ".4"));
function ft1(t, u, u1, u2) {
spread("1.".concat(t, ".3"), "1.".concat(t, ".4"));
spread("1.".concat(u, ".3"), "1.".concat(u, ".4"));
spread(u1, u2);
}


//// [templateLiteralTypes3.d.ts]
Expand Down Expand Up @@ -324,3 +352,7 @@ declare type Action = {
response: string;
};
declare function reducer(action: Action): void;
declare type DotString = `${string}.${string}.${string}`;
declare function noSpread<P extends DotString>(args: P[]): P;
declare function spread<P extends DotString>(...args: P[]): P;
declare function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>): void;
64 changes: 64 additions & 0 deletions tests/baselines/reference/templateLiteralTypes3.symbols
Expand Up @@ -516,3 +516,67 @@ function reducer(action: Action) {
}
}

// Repro from #46768

type DotString = `${string}.${string}.${string}`;
>DotString : Symbol(DotString, Decl(templateLiteralTypes3.ts, 170, 1))

declare function noSpread<P extends DotString>(args: P[]): P;
>noSpread : Symbol(noSpread, Decl(templateLiteralTypes3.ts, 174, 49))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 176, 26))
>DotString : Symbol(DotString, Decl(templateLiteralTypes3.ts, 170, 1))
>args : Symbol(args, Decl(templateLiteralTypes3.ts, 176, 47))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 176, 26))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 176, 26))

declare function spread<P extends DotString>(...args: P[]): P;
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 177, 24))
>DotString : Symbol(DotString, Decl(templateLiteralTypes3.ts, 170, 1))
>args : Symbol(args, Decl(templateLiteralTypes3.ts, 177, 45))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 177, 24))
>P : Symbol(P, Decl(templateLiteralTypes3.ts, 177, 24))

noSpread([`1.${'2'}.3`, `1.${'2'}.4`]);
>noSpread : Symbol(noSpread, Decl(templateLiteralTypes3.ts, 174, 49))

noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]);
>noSpread : Symbol(noSpread, Decl(templateLiteralTypes3.ts, 174, 49))

spread(`1.${'2'}.3`, `1.${'2'}.4`);
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))

spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`);
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))

function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) {
>ft1 : Symbol(ft1, Decl(templateLiteralTypes3.ts, 183, 55))
>T : Symbol(T, Decl(templateLiteralTypes3.ts, 185, 13))
>t : Symbol(t, Decl(templateLiteralTypes3.ts, 185, 31))
>T : Symbol(T, Decl(templateLiteralTypes3.ts, 185, 13))
>u : Symbol(u, Decl(templateLiteralTypes3.ts, 185, 36))
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(templateLiteralTypes3.ts, 185, 13))
>u1 : Symbol(u1, Decl(templateLiteralTypes3.ts, 185, 53))
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(templateLiteralTypes3.ts, 185, 13))
>u2 : Symbol(u2, Decl(templateLiteralTypes3.ts, 185, 80))
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(templateLiteralTypes3.ts, 185, 13))

spread(`1.${t}.3`, `1.${t}.4`);
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))
>t : Symbol(t, Decl(templateLiteralTypes3.ts, 185, 31))
>t : Symbol(t, Decl(templateLiteralTypes3.ts, 185, 31))

spread(`1.${u}.3`, `1.${u}.4`);
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))
>u : Symbol(u, Decl(templateLiteralTypes3.ts, 185, 36))
>u : Symbol(u, Decl(templateLiteralTypes3.ts, 185, 36))

spread(u1, u2);
>spread : Symbol(spread, Decl(templateLiteralTypes3.ts, 176, 61))
>u1 : Symbol(u1, Decl(templateLiteralTypes3.ts, 185, 53))
>u2 : Symbol(u2, Decl(templateLiteralTypes3.ts, 185, 80))
}

81 changes: 81 additions & 0 deletions tests/baselines/reference/templateLiteralTypes3.types
Expand Up @@ -513,3 +513,84 @@ function reducer(action: Action) {
}
}

// Repro from #46768

type DotString = `${string}.${string}.${string}`;
>DotString : `${string}.${string}.${string}`

declare function noSpread<P extends DotString>(args: P[]): P;
>noSpread : <P extends `${string}.${string}.${string}`>(args: P[]) => P
>args : P[]

declare function spread<P extends DotString>(...args: P[]): P;
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>args : P[]

noSpread([`1.${'2'}.3`, `1.${'2'}.4`]);
>noSpread([`1.${'2'}.3`, `1.${'2'}.4`]) : "1.2.3" | "1.2.4"
>noSpread : <P extends `${string}.${string}.${string}`>(args: P[]) => P
>[`1.${'2'}.3`, `1.${'2'}.4`] : ("1.2.3" | "1.2.4")[]
>`1.${'2'}.3` : "1.2.3"
>'2' : "2"
>`1.${'2'}.4` : "1.2.4"
>'2' : "2"

noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]);
>noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]) : `1.${string}.3` | `1.${string}.4`
>noSpread : <P extends `${string}.${string}.${string}`>(args: P[]) => P
>[`1.${'2' as string}.3`, `1.${'2' as string}.4`] : (`1.${string}.3` | `1.${string}.4`)[]
>`1.${'2' as string}.3` : `1.${string}.3`
>'2' as string : string
>'2' : "2"
>`1.${'2' as string}.4` : `1.${string}.4`
>'2' as string : string
>'2' : "2"

spread(`1.${'2'}.3`, `1.${'2'}.4`);
>spread(`1.${'2'}.3`, `1.${'2'}.4`) : "1.2.3" | "1.2.4"
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>`1.${'2'}.3` : "1.2.3"
>'2' : "2"
>`1.${'2'}.4` : "1.2.4"
>'2' : "2"

spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`);
>spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`) : `1.${string}.3` | `1.${string}.4`
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>`1.${'2' as string}.3` : `1.${string}.3`
>'2' as string : string
>'2' : "2"
>`1.${'2' as string}.4` : `1.${string}.4`
>'2' as string : string
>'2' : "2"

function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) {
>ft1 : <T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) => void
>t : T
>u : Uppercase<T>
>u1 : Uppercase<`1.${T}.3`>
>u2 : Uppercase<`1.${T}.4`>

spread(`1.${t}.3`, `1.${t}.4`);
>spread(`1.${t}.3`, `1.${t}.4`) : `1.${T}.3` | `1.${T}.4`
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>`1.${t}.3` : `1.${T}.3`
>t : T
>`1.${t}.4` : `1.${T}.4`
>t : T

spread(`1.${u}.3`, `1.${u}.4`);
>spread(`1.${u}.3`, `1.${u}.4`) : `1.${Uppercase<T>}.3` | `1.${Uppercase<T>}.4`
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>`1.${u}.3` : `1.${Uppercase<T>}.3`
>u : Uppercase<T>
>`1.${u}.4` : `1.${Uppercase<T>}.4`
>u : Uppercase<T>

spread(u1, u2);
>spread(u1, u2) : Uppercase<`1.${T}.3`> | Uppercase<`1.${T}.4`>
>spread : <P extends `${string}.${string}.${string}`>(...args: P[]) => P
>u1 : Uppercase<`1.${T}.3`>
>u2 : Uppercase<`1.${T}.4`>
}

19 changes: 19 additions & 0 deletions tests/cases/conformance/types/literal/templateLiteralTypes3.ts
Expand Up @@ -172,3 +172,22 @@ function reducer(action: Action) {
action.response;
}
}

// Repro from #46768

type DotString = `${string}.${string}.${string}`;

declare function noSpread<P extends DotString>(args: P[]): P;
declare function spread<P extends DotString>(...args: P[]): P;

noSpread([`1.${'2'}.3`, `1.${'2'}.4`]);
noSpread([`1.${'2' as string}.3`, `1.${'2' as string}.4`]);

spread(`1.${'2'}.3`, `1.${'2'}.4`);
spread(`1.${'2' as string}.3`, `1.${'2' as string}.4`);

function ft1<T extends string>(t: T, u: Uppercase<T>, u1: Uppercase<`1.${T}.3`>, u2: Uppercase<`1.${T}.4`>) {
spread(`1.${t}.3`, `1.${t}.4`);
spread(`1.${u}.3`, `1.${u}.4`);
spread(u1, u2);
}

0 comments on commit 2a7eb58

Please sign in to comment.