From 658764e4993d729faac86edd3fd606135144e263 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Oct 2021 09:34:15 -0700 Subject: [PATCH] Bypass caching in removeStringLiteralsMatchedByTemplateLiterals (#46525) * Bypass caching in removeStringLiteralsMatchedByTemplateLiterals * Add regression test --- src/compiler/checker.ts | 12 +- .../templateLiteralTypes1.errors.txt | 22 +- .../reference/templateLiteralTypes1.js | 259 ++---------------- .../reference/templateLiteralTypes1.symbols | 36 +++ .../reference/templateLiteralTypes1.types | 25 ++ .../types/literal/templateLiteralTypes1.ts | 17 ++ 6 files changed, 129 insertions(+), 242 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80122a5846..a97e96bff2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14203,13 +14203,13 @@ namespace ts { } function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { - const templates = filter(types, isPatternLiteralType); + const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; - if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeSubtypeOf(t, template))) { + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { orderedRemoveItemAt(types, i); } } @@ -19090,8 +19090,7 @@ namespace ts { // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); } - const result = inferTypesFromTemplateLiteralType(source, target as TemplateLiteralType); - if (result && every(result, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, (target as TemplateLiteralType).types[i]))) { + if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { return Ternary.True; } } @@ -21561,6 +21560,11 @@ namespace ts { undefined; } + function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { + const inferences = inferTypesFromTemplateLiteralType(source, target); + return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); + } + function getStringLikeTypeForType(type: Type) { return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); } diff --git a/tests/baselines/reference/templateLiteralTypes1.errors.txt b/tests/baselines/reference/templateLiteralTypes1.errors.txt index dae7b7009f..e586a1678e 100644 --- a/tests/baselines/reference/templateLiteralTypes1.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes1.errors.txt @@ -6,9 +6,10 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(165,15): error TS tests/cases/conformance/types/literal/templateLiteralTypes1.ts(197,16): error TS2590: Expression produces a union type that is too complex to represent. tests/cases/conformance/types/literal/templateLiteralTypes1.ts(201,16): error TS2590: Expression produces a union type that is too complex to represent. tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS2590: Expression produces a union type that is too complex to represent. +tests/cases/conformance/types/literal/templateLiteralTypes1.ts(251,7): error TS2590: Expression produces a union type that is too complex to represent. -==== tests/cases/conformance/types/literal/templateLiteralTypes1.ts (6 errors) ==== +==== tests/cases/conformance/types/literal/templateLiteralTypes1.ts (7 errors) ==== // Template types example from #12754 const createScopedActionType = (scope: S) => (type: T) => `${scope}/${type}` as `${S}/${T}`; @@ -259,4 +260,23 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS } as const; let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' + + // Repro from #46480 + + export type Spacing = + | `0` + | `${number}px` + | `${number}rem` + | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; + + const spacing: Spacing = "s12" + + export type SpacingShorthand = + | `${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + + const test1: SpacingShorthand = "0 0 0"; \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralTypes1.js b/tests/baselines/reference/templateLiteralTypes1.js index b8f7d0f406..d212909fd7 100644 --- a/tests/baselines/reference/templateLiteralTypes1.js +++ b/tests/baselines/reference/templateLiteralTypes1.js @@ -235,11 +235,29 @@ const obj2 = { } as const; let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' + +// Repro from #46480 + +export type Spacing = + | `0` + | `${number}px` + | `${number}rem` + | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; + +const spacing: Spacing = "s12" + +export type SpacingShorthand = + | `${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; + +const test1: SpacingShorthand = "0 0 0"; //// [templateLiteralTypes1.js] "use strict"; // Template types example from #12754 +exports.__esModule = true; var createScopedActionType = function (scope) { return function (type) { return "".concat(scope, "/").concat(type); }; }; var createActionInMyScope = createScopedActionType("MyScope"); // (type: T) => `MyScope/${T}` var MY_ACTION = createActionInMyScope("MY_ACTION"); // 'MyScope/MY_ACTION' @@ -274,243 +292,10 @@ var obj2 = { ] }; var make = getProp2(obj2, 'cars.1.make'); // 'Trabant' +var spacing = "s12"; +var test1 = "0 0 0"; //// [templateLiteralTypes1.d.ts] -declare const createScopedActionType: (scope: S) => (type: T) => `${S}/${T}`; -declare const createActionInMyScope: (type: T) => `MyScope/${T}`; -declare const MY_ACTION: "MyScope/MY_ACTION"; -declare type EventName = `${S}Changed`; -declare type EN1 = EventName<'Foo' | 'Bar' | 'Baz'>; -declare type Loc = `${'top' | 'middle' | 'bottom'}-${'left' | 'center' | 'right'}`; -declare type ToString = `${T}`; -declare type TS1 = ToString<'abc' | 42 | true | -1234n>; -declare type TL1 = `a${T}b${T}c`; -declare type TL2 = TL1<`x${U}y`>; -declare type TL3 = TL2<'o'>; -declare type Cases = `${Uppercase} ${Lowercase} ${Capitalize} ${Uncapitalize}`; -declare type TCA1 = Cases<'bar'>; -declare type TCA2 = Cases<'BAR'>; -declare function test(name: `get${Capitalize}`): void; -declare function fa1(x: T, y: { - [P in keyof T]: T[P]; -}, z: { - [P in keyof T & string as `p_${P}`]: T[P]; -}): void; -declare function fa2(x: { - [P in B as `p_${P}`]: T; -}, y: { - [Q in A as `p_${Q}`]: U; -}): void; -declare type Join = T extends [] ? '' : T extends [string | number | boolean | bigint] ? `${T[0]}` : T extends [string | number | boolean | bigint, ...infer U] ? `${T[0]}${D}${Join}` : string; -declare type TJ1 = Join<[1, 2, 3, 4], '.'>; -declare type TJ2 = Join<['foo', 'bar', 'baz'], '-'>; -declare type TJ3 = Join<[], '.'>; -declare type MatchPair = S extends `[${infer A},${infer B}]` ? [A, B] : unknown; -declare type T20 = MatchPair<'[1,2]'>; -declare type T21 = MatchPair<'[foo,bar]'>; -declare type T22 = MatchPair<' [1,2]'>; -declare type T23 = MatchPair<'[123]'>; -declare type T24 = MatchPair<'[1,2,3,4]'>; -declare type SnakeToCamelCase = S extends `${infer T}_${infer U}` ? `${Lowercase}${SnakeToPascalCase}` : S extends `${infer T}` ? `${Lowercase}` : SnakeToPascalCase; -declare type SnakeToPascalCase = string extends S ? string : S extends `${infer T}_${infer U}` ? `${Capitalize>}${SnakeToPascalCase}` : S extends `${infer T}` ? `${Capitalize>}` : never; -declare type RR0 = SnakeToPascalCase<'hello_world_foo'>; -declare type RR1 = SnakeToPascalCase<'FOO_BAR_BAZ'>; -declare type RR2 = SnakeToCamelCase<'hello_world_foo'>; -declare type RR3 = SnakeToCamelCase<'FOO_BAR_BAZ'>; -declare type FirstTwoAndRest = S extends `${infer A}${infer B}${infer R}` ? [`${A}${B}`, R] : unknown; -declare type T25 = FirstTwoAndRest<'abcde'>; -declare type T26 = FirstTwoAndRest<'ab'>; -declare type T27 = FirstTwoAndRest<'a'>; -declare type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'; -declare type HexColor = S extends `#${infer R1}${infer R2}${infer G1}${infer G2}${infer B1}${infer B2}` ? [ - R1, - R2, - G1, - G2, - B1, - B2 -] extends [HexDigit, HexDigit, HexDigit, HexDigit, HexDigit, HexDigit] ? S : never : never; -declare type TH1 = HexColor<'#8080FF'>; -declare type TH2 = HexColor<'#80c0ff'>; -declare type TH3 = HexColor<'#8080F'>; -declare type TH4 = HexColor<'#8080FFF'>; -declare type Trim = S extends ` ${infer T}` ? Trim : S extends `${infer T} ` ? Trim : S; -declare type TR1 = Trim<'xx '>; -declare type TR2 = Trim<' xx'>; -declare type TR3 = Trim<' xx '>; -declare type Split = string extends S ? string[] : S extends '' ? [] : S extends `${infer T}${D}${infer U}` ? [T, ...Split] : [ - S -]; -declare type T40 = Split<'foo', '.'>; -declare type T41 = Split<'foo.bar.baz', '.'>; -declare type T42 = Split<'foo.bar', ''>; -declare type T43 = Split; -declare function getProp(obj: T, path: `${P0}.${P1}.${P2}`): T[P0][P1][P2]; -declare function getProp(obj: T, path: `${P0}.${P1}`): T[P0][P1]; -declare function getProp(obj: T, path: P0): T[P0]; -declare function getProp(obj: object, path: string): unknown; -declare let p1: { - readonly b: { - readonly c: 42; - readonly d: "hello"; - }; -}; -declare let p2: { - readonly c: 42; - readonly d: "hello"; -}; -declare let p3: "hello"; -declare type PropType = string extends Path ? unknown : Path extends keyof T ? T[Path] : Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropType : unknown : unknown; -declare function getPropValue(obj: T, path: P): PropType; -declare const s: string; -declare const obj: { - a: { - b: { - c: number; - d: string; - }; - }; -}; -declare type S1 = T extends `foo${infer U}bar` ? S2 : never; -declare type S2 = S; -declare type TV1 = `${infer X}`; -declare type Chars = string extends S ? string[] : S extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer C4}${infer C5}${infer C6}${infer C7}${infer C8}${infer C9}${infer R}` ? [C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, ...Chars] : S extends `${infer C}${infer R}` ? [C, ...Chars] : S extends '' ? [] : never; -declare type L1 = Chars<'FooBarBazThisIsALongerString'>; -declare type Foo = T extends `*${infer S}*` ? S : never; -declare type TF1 = Foo; -declare type TF2 = Foo; -declare type TF3 = Foo<'abc'>; -declare type TF4 = Foo<'*abc*'>; -declare type A = any; -declare type U1 = { - a1: A; -} | { - b1: A; -} | { - c1: A; -} | { - d1: A; -} | { - e1: A; -} | { - f1: A; -} | { - g1: A; -} | { - h1: A; -} | { - i1: A; -} | { - j1: A; -}; -declare type U2 = { - a2: A; -} | { - b2: A; -} | { - c2: A; -} | { - d2: A; -} | { - e2: A; -} | { - f2: A; -} | { - g2: A; -} | { - h2: A; -} | { - i2: A; -} | { - j2: A; -}; -declare type U3 = { - a3: A; -} | { - b3: A; -} | { - c3: A; -} | { - d3: A; -} | { - e3: A; -} | { - f3: A; -} | { - g3: A; -} | { - h3: A; -} | { - i3: A; -} | { - j3: A; -}; -declare type U4 = { - a4: A; -} | { - b4: A; -} | { - c4: A; -} | { - d4: A; -} | { - e4: A; -} | { - f4: A; -} | { - g4: A; -} | { - h4: A; -} | { - i4: A; -} | { - j4: A; -}; -declare type U5 = { - a5: A; -} | { - b5: A; -} | { - c5: A; -} | { - d5: A; -} | { - e5: A; -} | { - f5: A; -} | { - g5: A; -} | { - h5: A; -} | { - i5: A; -} | { - j5: A; -}; -declare type U100000 = U1 & U2 & U3 & U4 & U5; -declare type Digits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; -declare type D100000 = `${Digits}${Digits}${Digits}${Digits}${Digits}`; -declare type TDigits = [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | [9]; -declare type T100000 = [...TDigits, ...TDigits, ...TDigits, ...TDigits, ...TDigits]; -declare type IsNegative = `${T}` extends `-${string}` ? true : false; -declare type AA = [ - true, - true -] extends [IsNegative, IsNegative] ? 'Every thing is ok!' : ['strange', IsNegative, IsNegative]; -declare type BB = AA<-2, -2>; -declare type PathKeys = T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; -declare type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; -declare function getProp2>(obj: T, path: P): PropType; -declare const obj2: { - readonly name: "John"; - readonly age: 42; - readonly cars: readonly [{ - readonly make: "Ford"; - readonly age: 10; - }, { - readonly make: "Trabant"; - readonly age: 35; - }]; -}; -declare let make: "Trabant"; +export declare type Spacing = `0` | `${number}px` | `${number}rem` | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; +export declare type SpacingShorthand = `${Spacing} ${Spacing}` | `${Spacing} ${Spacing} ${Spacing}` | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; diff --git a/tests/baselines/reference/templateLiteralTypes1.symbols b/tests/baselines/reference/templateLiteralTypes1.symbols index bc2f7990a2..8f539fe347 100644 --- a/tests/baselines/reference/templateLiteralTypes1.symbols +++ b/tests/baselines/reference/templateLiteralTypes1.symbols @@ -952,3 +952,39 @@ let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' >getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89)) >obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5)) +// Repro from #46480 + +export type Spacing = +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) + + | `0` + | `${number}px` + | `${number}rem` + | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; + +const spacing: Spacing = "s12" +>spacing : Symbol(spacing, Decl(templateLiteralTypes1.ts, 245, 5)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) + +export type SpacingShorthand = +>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30)) + + | `${Spacing} ${Spacing}` +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) + + | `${Spacing} ${Spacing} ${Spacing}` +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) + + | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) + +const test1: SpacingShorthand = "0 0 0"; +>test1 : Symbol(test1, Decl(templateLiteralTypes1.ts, 252, 5)) +>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30)) + diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types index 102e9a9193..d143dc764c 100644 --- a/tests/baselines/reference/templateLiteralTypes1.types +++ b/tests/baselines/reference/templateLiteralTypes1.types @@ -594,3 +594,28 @@ let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' >obj2 : { readonly name: "John"; readonly age: 42; readonly cars: readonly [{ readonly make: "Ford"; readonly age: 10; }, { readonly make: "Trabant"; readonly age: 35; }]; } >'cars.1.make' : "cars.1.make" +// Repro from #46480 + +export type Spacing = +>Spacing : Spacing + + | `0` + | `${number}px` + | `${number}rem` + | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; + +const spacing: Spacing = "s12" +>spacing : Spacing +>"s12" : "s12" + +export type SpacingShorthand = +>SpacingShorthand : any + + | `${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; + +const test1: SpacingShorthand = "0 0 0"; +>test1 : any +>"0 0 0" : "0 0 0" + diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts index 185e0bf402..daa3fbaeb7 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts @@ -237,3 +237,20 @@ const obj2 = { } as const; let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' + +// Repro from #46480 + +export type Spacing = + | `0` + | `${number}px` + | `${number}rem` + | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; + +const spacing: Spacing = "s12" + +export type SpacingShorthand = + | `${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing}` + | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; + +const test1: SpacingShorthand = "0 0 0";