From 7f347928fbae689f6fdedae471e3ec29d76bb963 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 Sep 2021 13:42:38 -0700 Subject: [PATCH 1/3] Fix issues + Support template literal types in discriminants --- src/compiler/checker.ts | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 364f0de7f642f..40fd8c8d56df6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12029,7 +12029,7 @@ namespace ts { else if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } - if (isLiteralType(type)) { + if (isLiteralType(type) || isPatternLiteralType(type)) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never) { @@ -18935,6 +18935,9 @@ namespace ts { } else if (target.flags & TypeFlags.TemplateLiteral) { if (source.flags & TypeFlags.TemplateLiteral) { + if (relation === comparableRelation) { + return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True; + } // Report unreliable variance for type variables referenced in template literal type placeholders. // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); @@ -18974,11 +18977,10 @@ namespace ts { return result; } } - else if (source.flags & TypeFlags.TemplateLiteral) { + else if (source.flags & TypeFlags.TemplateLiteral && !(target.flags & TypeFlags.Object)) { if (!(target.flags & TypeFlags.TemplateLiteral)) { - const baseConstraint = getBaseConstraintOfType(source); - const constraint = baseConstraint && baseConstraint !== source ? baseConstraint : stringType; - if (result = isRelatedTo(constraint, target, reportErrors)) { + const constraint = getBaseConstraintOfType(source); + if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, reportErrors))) { resetErrorInfo(saveErrorInfo); return result; } @@ -21349,6 +21351,18 @@ namespace ts { return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); } + function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { + // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. + const sourceStart = source.texts[0]; + const targetStart = target.texts[0]; + const sourceEnd = source.texts[source.texts.length - 1]; + const targetEnd = target.texts[target.texts.length - 1]; + const startLen = Math.min(sourceStart.length, targetStart.length); + const endLen = Math.min(sourceEnd.length, targetEnd.length); + return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || + sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); + } + function isValidBigIntString(s: string): boolean { const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); let success = true; @@ -22449,7 +22463,7 @@ namespace ts { if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { (prop as TransientSymbol).isDiscriminantProperty = ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && - !maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable & ~TypeFlags.TemplateLiteral); + !isGenericType(getTypeOfSymbol(prop)); } return !!(prop as TransientSymbol).isDiscriminantProperty; } @@ -23008,15 +23022,17 @@ namespace ts { return filterType(type, t => (t.flags & kind) !== 0); } - // Return a new type in which occurrences of the string and number primitive types in - // typeWithPrimitives have been replaced with occurrences of string literals and numeric - // literals in typeWithLiterals, respectively. + // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template + // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types + // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a + // true intersection because it is more costly and, when applied to union types, generates a large number of + // types we don't actually care about. function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { - if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) || - isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) || - isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) { + if (maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && + maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)) { return mapType(typeWithPrimitives, t => - t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral) : + t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : + isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); } @@ -23859,7 +23875,7 @@ namespace ts { const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); - return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType); + return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType); }); } From e1907a2656458aa34fb21bf7b97b6b409ac7119e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 Sep 2021 13:56:06 -0700 Subject: [PATCH 2/3] Add tests --- .../templateLiteralTypes3.errors.txt | 43 +++++- .../reference/templateLiteralTypes3.js | 126 ++++++++++-------- .../reference/templateLiteralTypes3.symbols | 89 +++++++++++++ .../reference/templateLiteralTypes3.types | 96 +++++++++++++ .../types/literal/templateLiteralTypes3.ts | 38 ++++++ 5 files changed, 339 insertions(+), 53 deletions(-) diff --git a/tests/baselines/reference/templateLiteralTypes3.errors.txt b/tests/baselines/reference/templateLiteralTypes3.errors.txt index 81865b5dba5dc..787654982a049 100644 --- a/tests/baselines/reference/templateLiteralTypes3.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes3.errors.txt @@ -5,9 +5,10 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(71,5): error TS23 tests/cases/conformance/types/literal/templateLiteralTypes3.ts(72,5): error TS2322: Type '`*${string}*`' is not assignable to type '`*${number}*`'. tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS2322: Type '"*false*" | "*true*"' is not assignable to type '`*${number}*`'. Type '"*false*"' is not assignable to type '`*${number}*`'. +tests/cases/conformance/types/literal/templateLiteralTypes3.ts(133,9): error TS2367: This condition will always return 'false' since the types '`foo-${string}`' and '`baz-${string}`' have no overlap. -==== tests/cases/conformance/types/literal/templateLiteralTypes3.ts (6 errors) ==== +==== tests/cases/conformance/types/literal/templateLiteralTypes3.ts (7 errors) ==== // Inference from template literal type to template literal type type Foo1 = T extends `*${infer U}*` ? U : never; @@ -146,4 +147,44 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS23 declare function chain(field: F | `${F}.${F}`): void; chain("a"); + + // Repro from #46125 + + function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { + if (x === y) { + x; // `foo-${string}` + } + if (x === z) { // Error + ~~~~~~~ +!!! error TS2367: This condition will always return 'false' since the types '`foo-${string}`' and '`baz-${string}`' have no overlap. + } + } + + function ff2(x: string, y: `foo-${string}` | 'bar') { + if (x === y) { + x; // `foo-${string}` | 'bar' + } + } + + function ff3(x: string, y: `foo-${string}`) { + if (x === 'foo-test') { + x; // 'foo-test' + } + if (y === 'foo-test') { + y; // 'foo-test' + } + } + + // Repro from #46045 + + export type Action = + | { type: `${string}_REQUEST` } + | { type: `${string}_SUCCESS`, response: string }; + + export function reducer(action: Action) { + if (action.type === 'FOO_SUCCESS') { + action.type; + action.response; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/templateLiteralTypes3.js b/tests/baselines/reference/templateLiteralTypes3.js index 07a3e669db098..384f4f67692b1 100644 --- a/tests/baselines/reference/templateLiteralTypes3.js +++ b/tests/baselines/reference/templateLiteralTypes3.js @@ -124,11 +124,51 @@ type Schema = { a: { b: { c: number } } }; declare function chain(field: F | `${F}.${F}`): void; chain("a"); + +// Repro from #46125 + +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { + if (x === y) { + x; // `foo-${string}` + } + if (x === z) { // Error + } +} + +function ff2(x: string, y: `foo-${string}` | 'bar') { + if (x === y) { + x; // `foo-${string}` | 'bar' + } +} + +function ff3(x: string, y: `foo-${string}`) { + if (x === 'foo-test') { + x; // 'foo-test' + } + if (y === 'foo-test') { + y; // 'foo-test' + } +} + +// Repro from #46045 + +export type Action = + | { type: `${string}_REQUEST` } + | { type: `${string}_SUCCESS`, response: string }; + +export function reducer(action: Action) { + if (action.type === 'FOO_SUCCESS') { + action.type; + action.response; + } +} //// [templateLiteralTypes3.js] "use strict"; // Inference from template literal type to template literal type +exports.__esModule = true; +exports.reducer = void 0; function f1(s, n, b, t) { var x1 = foo1('hello'); // Error var x2 = foo1('*hello*'); @@ -177,59 +217,41 @@ var templated1 = value1 + " abc"; var value2 = "abc"; var templated2 = value2 + " abc"; chain("a"); +// Repro from #46125 +function ff1(x, y, z) { + if (x === y) { + x; // `foo-${string}` + } + if (x === z) { // Error + } +} +function ff2(x, y) { + if (x === y) { + x; // `foo-${string}` | 'bar' + } +} +function ff3(x, y) { + if (x === 'foo-test') { + x; // 'foo-test' + } + if (y === 'foo-test') { + y; // 'foo-test' + } +} +function reducer(action) { + if (action.type === 'FOO_SUCCESS') { + action.type; + action.response; + } +} +exports.reducer = reducer; //// [templateLiteralTypes3.d.ts] -declare type Foo1 = T extends `*${infer U}*` ? U : never; -declare type T01 = Foo1<'hello'>; -declare type T02 = Foo1<'*hello*'>; -declare type T03 = Foo1<'**hello**'>; -declare type T04 = Foo1<`*${string}*`>; -declare type T05 = Foo1<`*${number}*`>; -declare type T06 = Foo1<`*${bigint}*`>; -declare type T07 = Foo1<`*${any}*`>; -declare type T08 = Foo1<`**${string}**`>; -declare type T09 = Foo1<`**${string}**${string}**`>; -declare type T10 = Foo1<`**${'a' | 'b' | 'c'}**`>; -declare type T11 = Foo1<`**${boolean}**${boolean}**`>; -declare function foo1(arg: `*${V}*`): V; -declare function f1(s: string, n: number, b: boolean, t: T): void; -declare type Parts = T extends '' ? [] : T extends `${infer Head}${infer Tail}` ? [Head, ...Parts] : never; -declare type T20 = Parts<`abc`>; -declare type T21 = Parts<`*${string}*`>; -declare type T22 = Parts<`*${number}*`>; -declare type T23 = Parts<`*${number}*${string}*${bigint}*`>; -declare function f2(): void; -declare function f3(s: string, n: number, b: boolean, t: T): void; -declare function f4(s: string, n: number, b: boolean, t: T): void; -declare type A = T extends `${infer U}.${infer V}` ? U | V : never; -declare type B = A<`test.1024`>; -declare type C = A<`test.${number}`>; -declare type D = T extends `${infer U}.${number}` ? U : never; -declare type E = D<`test.1024`>; -declare type F = D<`test.${number}`>; -declare type G = T extends `${infer U}.${infer V}` ? U | V : never; -declare type H = G<`test.hoge`>; -declare type I = G<`test.${string}`>; -declare type J = T extends `${infer U}.${string}` ? U : never; -declare type K = J<`test.hoge`>; -declare type L = J<`test.${string}`>; -declare type Templated = `${string} ${string}`; -declare const value1: string; -declare const templated1: Templated; -declare const value2 = "abc"; -declare const templated2: Templated; -declare type Prefixes = "foo" | "bar"; -declare type AllPrefixData = "foo:baz" | "bar:baz"; -declare type PrefixData

= `${P}:baz`; -interface ITest

> { - blah: string; -} -declare type Schema = { - a: { - b: { - c: number; - }; - }; +export declare type Action = { + type: `${string}_REQUEST`; +} | { + type: `${string}_SUCCESS`; + response: string; }; -declare function chain(field: F | `${F}.${F}`): void; +export declare function reducer(action: Action): void; diff --git a/tests/baselines/reference/templateLiteralTypes3.symbols b/tests/baselines/reference/templateLiteralTypes3.symbols index 509a9a36c373e..7184ef0f5abf1 100644 --- a/tests/baselines/reference/templateLiteralTypes3.symbols +++ b/tests/baselines/reference/templateLiteralTypes3.symbols @@ -405,3 +405,92 @@ declare function chain(field: F | `${F}.${F}`): void; chain("a"); >chain : Symbol(chain, Decl(templateLiteralTypes3.ts, 120, 42)) +// Repro from #46125 + +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +>ff1 : Symbol(ff1, Decl(templateLiteralTypes3.ts, 124, 11)) +>T : Symbol(T, Decl(templateLiteralTypes3.ts, 128, 13)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 50)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 70)) + + if (x === y) { +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 50)) + + x; // `foo-${string}` +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) + } + if (x === z) { // Error +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 70)) + } +} + +function ff2(x: string, y: `foo-${string}` | 'bar') { +>ff2 : Symbol(ff2, Decl(templateLiteralTypes3.ts, 134, 1)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 23)) + + if (x === y) { +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 23)) + + x; // `foo-${string}` | 'bar' +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) + } +} + +function ff3(x: string, y: `foo-${string}`) { +>ff3 : Symbol(ff3, Decl(templateLiteralTypes3.ts, 140, 1)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) + + if (x === 'foo-test') { +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) + + x; // 'foo-test' +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) + } + if (y === 'foo-test') { +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) + + y; // 'foo-test' +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) + } +} + +// Repro from #46045 + +export type Action = +>Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 149, 1)) + + | { type: `${string}_REQUEST` } +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7)) + + | { type: `${string}_SUCCESS`, response: string }; +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) +>response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) + +export function reducer(action: Action) { +>reducer : Symbol(reducer, Decl(templateLiteralTypes3.ts, 155, 54)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) +>Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 149, 1)) + + if (action.type === 'FOO_SUCCESS') { +>action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7), Decl(templateLiteralTypes3.ts, 155, 7)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7), Decl(templateLiteralTypes3.ts, 155, 7)) + + action.type; +>action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) + + action.response; +>action.response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) +>response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) + } +} + diff --git a/tests/baselines/reference/templateLiteralTypes3.types b/tests/baselines/reference/templateLiteralTypes3.types index 934fa60857f9e..35ace30c6f112 100644 --- a/tests/baselines/reference/templateLiteralTypes3.types +++ b/tests/baselines/reference/templateLiteralTypes3.types @@ -396,3 +396,99 @@ chain("a"); >chain : (field: F | `${F}.${F}`) => void >"a" : "a" +// Repro from #46125 + +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +>ff1 : (x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) => void +>x : `foo-${string}` +>y : `${string}-bar` +>z : `baz-${string}` + + if (x === y) { +>x === y : boolean +>x : `foo-${string}` +>y : `${string}-bar` + + x; // `foo-${string}` +>x : `foo-${string}` + } + if (x === z) { // Error +>x === z : boolean +>x : `foo-${string}` +>z : `baz-${string}` + } +} + +function ff2(x: string, y: `foo-${string}` | 'bar') { +>ff2 : (x: string, y: `foo-${string}` | 'bar') => void +>x : string +>y : "bar" | `foo-${string}` + + if (x === y) { +>x === y : boolean +>x : string +>y : "bar" | `foo-${string}` + + x; // `foo-${string}` | 'bar' +>x : "bar" | `foo-${string}` + } +} + +function ff3(x: string, y: `foo-${string}`) { +>ff3 : (x: string, y: `foo-${string}`) => void +>x : string +>y : `foo-${string}` + + if (x === 'foo-test') { +>x === 'foo-test' : boolean +>x : string +>'foo-test' : "foo-test" + + x; // 'foo-test' +>x : "foo-test" + } + if (y === 'foo-test') { +>y === 'foo-test' : boolean +>y : `foo-${string}` +>'foo-test' : "foo-test" + + y; // 'foo-test' +>y : "foo-test" + } +} + +// Repro from #46045 + +export type Action = +>Action : Action + + | { type: `${string}_REQUEST` } +>type : `${string}_REQUEST` + + | { type: `${string}_SUCCESS`, response: string }; +>type : `${string}_SUCCESS` +>response : string + +export function reducer(action: Action) { +>reducer : (action: Action) => void +>action : Action + + if (action.type === 'FOO_SUCCESS') { +>action.type === 'FOO_SUCCESS' : boolean +>action.type : `${string}_REQUEST` | `${string}_SUCCESS` +>action : Action +>type : `${string}_REQUEST` | `${string}_SUCCESS` +>'FOO_SUCCESS' : "FOO_SUCCESS" + + action.type; +>action.type : "FOO_SUCCESS" +>action : { type: `${string}_SUCCESS`; response: string; } +>type : "FOO_SUCCESS" + + action.response; +>action.response : string +>action : { type: `${string}_SUCCESS`; response: string; } +>response : string + } +} + diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts index b5fc39d32ef3c..663f3fa1f2933 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts @@ -126,3 +126,41 @@ type Schema = { a: { b: { c: number } } }; declare function chain(field: F | `${F}.${F}`): void; chain("a"); + +// Repro from #46125 + +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { + if (x === y) { + x; // `foo-${string}` + } + if (x === z) { // Error + } +} + +function ff2(x: string, y: `foo-${string}` | 'bar') { + if (x === y) { + x; // `foo-${string}` | 'bar' + } +} + +function ff3(x: string, y: `foo-${string}`) { + if (x === 'foo-test') { + x; // 'foo-test' + } + if (y === 'foo-test') { + y; // 'foo-test' + } +} + +// Repro from #46045 + +export type Action = + | { type: `${string}_REQUEST` } + | { type: `${string}_SUCCESS`, response: string }; + +export function reducer(action: Action) { + if (action.type === 'FOO_SUCCESS') { + action.type; + action.response; + } +} From 955fda50cd36b82bb95f2d9ac7a70a1494ef7545 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 Sep 2021 15:04:03 -0700 Subject: [PATCH 3/3] Address CR feedback --- .../templateLiteralTypes3.errors.txt | 23 +++- .../reference/templateLiteralTypes3.js | 93 +++++++++++++-- .../reference/templateLiteralTypes3.symbols | 106 +++++++++++------- .../reference/templateLiteralTypes3.types | 37 ++++-- .../types/literal/templateLiteralTypes3.ts | 18 ++- 5 files changed, 204 insertions(+), 73 deletions(-) diff --git a/tests/baselines/reference/templateLiteralTypes3.errors.txt b/tests/baselines/reference/templateLiteralTypes3.errors.txt index 787654982a049..4c6e98d93653a 100644 --- a/tests/baselines/reference/templateLiteralTypes3.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes3.errors.txt @@ -6,9 +6,10 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(72,5): error TS23 tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS2322: Type '"*false*" | "*true*"' is not assignable to type '`*${number}*`'. Type '"*false*"' is not assignable to type '`*${number}*`'. tests/cases/conformance/types/literal/templateLiteralTypes3.ts(133,9): error TS2367: This condition will always return 'false' since the types '`foo-${string}`' and '`baz-${string}`' have no overlap. +tests/cases/conformance/types/literal/templateLiteralTypes3.ts(141,9): error TS2367: This condition will always return 'false' since the types '`foo-${T}`' and '`baz-${T}`' have no overlap. -==== tests/cases/conformance/types/literal/templateLiteralTypes3.ts (7 errors) ==== +==== tests/cases/conformance/types/literal/templateLiteralTypes3.ts (8 errors) ==== // Inference from template literal type to template literal type type Foo1 = T extends `*${infer U}*` ? U : never; @@ -150,7 +151,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(133,9): error TS2 // Repro from #46125 - function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { + function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { if (x === y) { x; // `foo-${string}` } @@ -160,13 +161,23 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(133,9): error TS2 } } - function ff2(x: string, y: `foo-${string}` | 'bar') { + function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) { + if (x === y) { + x; // `foo-${T}` + } + if (x === z) { // Error + ~~~~~~~ +!!! error TS2367: This condition will always return 'false' since the types '`foo-${T}`' and '`baz-${T}`' have no overlap. + } + } + + function ff3(x: string, y: `foo-${string}` | 'bar') { if (x === y) { x; // `foo-${string}` | 'bar' } } - function ff3(x: string, y: `foo-${string}`) { + function ff4(x: string, y: `foo-${string}`) { if (x === 'foo-test') { x; // 'foo-test' } @@ -177,11 +188,11 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(133,9): error TS2 // Repro from #46045 - export type Action = + type Action = | { type: `${string}_REQUEST` } | { type: `${string}_SUCCESS`, response: string }; - export function reducer(action: Action) { + function reducer(action: Action) { if (action.type === 'FOO_SUCCESS') { action.type; action.response; diff --git a/tests/baselines/reference/templateLiteralTypes3.js b/tests/baselines/reference/templateLiteralTypes3.js index 384f4f67692b1..7122f13da6dad 100644 --- a/tests/baselines/reference/templateLiteralTypes3.js +++ b/tests/baselines/reference/templateLiteralTypes3.js @@ -127,7 +127,7 @@ chain("a"); // Repro from #46125 -function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { if (x === y) { x; // `foo-${string}` } @@ -135,13 +135,21 @@ function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-$ } } -function ff2(x: string, y: `foo-${string}` | 'bar') { +function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) { + if (x === y) { + x; // `foo-${T}` + } + if (x === z) { // Error + } +} + +function ff3(x: string, y: `foo-${string}` | 'bar') { if (x === y) { x; // `foo-${string}` | 'bar' } } -function ff3(x: string, y: `foo-${string}`) { +function ff4(x: string, y: `foo-${string}`) { if (x === 'foo-test') { x; // 'foo-test' } @@ -152,11 +160,11 @@ function ff3(x: string, y: `foo-${string}`) { // Repro from #46045 -export type Action = +type Action = | { type: `${string}_REQUEST` } | { type: `${string}_SUCCESS`, response: string }; -export function reducer(action: Action) { +function reducer(action: Action) { if (action.type === 'FOO_SUCCESS') { action.type; action.response; @@ -167,8 +175,6 @@ export function reducer(action: Action) { //// [templateLiteralTypes3.js] "use strict"; // Inference from template literal type to template literal type -exports.__esModule = true; -exports.reducer = void 0; function f1(s, n, b, t) { var x1 = foo1('hello'); // Error var x2 = foo1('*hello*'); @@ -225,12 +231,19 @@ function ff1(x, y, z) { if (x === z) { // Error } } -function ff2(x, y) { +function ff2(x, y, z) { if (x === y) { - x; // `foo-${string}` | 'bar' + x; // `foo-${T}` + } + if (x === z) { // Error } } function ff3(x, y) { + if (x === y) { + x; // `foo-${string}` | 'bar' + } +} +function ff4(x, y) { if (x === 'foo-test') { x; // 'foo-test' } @@ -244,14 +257,70 @@ function reducer(action) { action.response; } } -exports.reducer = reducer; //// [templateLiteralTypes3.d.ts] -export declare type Action = { +declare type Foo1 = T extends `*${infer U}*` ? U : never; +declare type T01 = Foo1<'hello'>; +declare type T02 = Foo1<'*hello*'>; +declare type T03 = Foo1<'**hello**'>; +declare type T04 = Foo1<`*${string}*`>; +declare type T05 = Foo1<`*${number}*`>; +declare type T06 = Foo1<`*${bigint}*`>; +declare type T07 = Foo1<`*${any}*`>; +declare type T08 = Foo1<`**${string}**`>; +declare type T09 = Foo1<`**${string}**${string}**`>; +declare type T10 = Foo1<`**${'a' | 'b' | 'c'}**`>; +declare type T11 = Foo1<`**${boolean}**${boolean}**`>; +declare function foo1(arg: `*${V}*`): V; +declare function f1(s: string, n: number, b: boolean, t: T): void; +declare type Parts = T extends '' ? [] : T extends `${infer Head}${infer Tail}` ? [Head, ...Parts] : never; +declare type T20 = Parts<`abc`>; +declare type T21 = Parts<`*${string}*`>; +declare type T22 = Parts<`*${number}*`>; +declare type T23 = Parts<`*${number}*${string}*${bigint}*`>; +declare function f2(): void; +declare function f3(s: string, n: number, b: boolean, t: T): void; +declare function f4(s: string, n: number, b: boolean, t: T): void; +declare type A = T extends `${infer U}.${infer V}` ? U | V : never; +declare type B = A<`test.1024`>; +declare type C = A<`test.${number}`>; +declare type D = T extends `${infer U}.${number}` ? U : never; +declare type E = D<`test.1024`>; +declare type F = D<`test.${number}`>; +declare type G = T extends `${infer U}.${infer V}` ? U | V : never; +declare type H = G<`test.hoge`>; +declare type I = G<`test.${string}`>; +declare type J = T extends `${infer U}.${string}` ? U : never; +declare type K = J<`test.hoge`>; +declare type L = J<`test.${string}`>; +declare type Templated = `${string} ${string}`; +declare const value1: string; +declare const templated1: Templated; +declare const value2 = "abc"; +declare const templated2: Templated; +declare type Prefixes = "foo" | "bar"; +declare type AllPrefixData = "foo:baz" | "bar:baz"; +declare type PrefixData

= `${P}:baz`; +interface ITest

> { + blah: string; +} +declare type Schema = { + a: { + b: { + c: number; + }; + }; +}; +declare function chain(field: F | `${F}.${F}`): void; +declare function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`): void; +declare function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`): void; +declare function ff3(x: string, y: `foo-${string}` | 'bar'): void; +declare function ff4(x: string, y: `foo-${string}`): void; +declare type Action = { type: `${string}_REQUEST`; } | { type: `${string}_SUCCESS`; response: string; }; -export declare function reducer(action: Action): void; +declare function reducer(action: Action): void; diff --git a/tests/baselines/reference/templateLiteralTypes3.symbols b/tests/baselines/reference/templateLiteralTypes3.symbols index 7184ef0f5abf1..570a85a809dd8 100644 --- a/tests/baselines/reference/templateLiteralTypes3.symbols +++ b/tests/baselines/reference/templateLiteralTypes3.symbols @@ -407,90 +407,112 @@ chain("a"); // Repro from #46125 -function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { >ff1 : Symbol(ff1, Decl(templateLiteralTypes3.ts, 124, 11)) ->T : Symbol(T, Decl(templateLiteralTypes3.ts, 128, 13)) ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 50)) ->z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 70)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 32)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 52)) if (x === y) { ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 50)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 32)) x; // `foo-${string}` ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13)) } if (x === z) { // Error ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 31)) ->z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 70)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 52)) } } -function ff2(x: string, y: `foo-${string}` | 'bar') { +function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) { >ff2 : Symbol(ff2, Decl(templateLiteralTypes3.ts, 134, 1)) ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 23)) +>T : Symbol(T, Decl(templateLiteralTypes3.ts, 136, 13)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 31)) +>T : Symbol(T, Decl(templateLiteralTypes3.ts, 136, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 45)) +>T : Symbol(T, Decl(templateLiteralTypes3.ts, 136, 13)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 136, 60)) +>T : Symbol(T, Decl(templateLiteralTypes3.ts, 136, 13)) if (x === y) { ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 23)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 31)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 136, 45)) + + x; // `foo-${T}` +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 31)) + } + if (x === z) { // Error +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 31)) +>z : Symbol(z, Decl(templateLiteralTypes3.ts, 136, 60)) + } +} + +function ff3(x: string, y: `foo-${string}` | 'bar') { +>ff3 : Symbol(ff3, Decl(templateLiteralTypes3.ts, 142, 1)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 144, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 144, 23)) + + if (x === y) { +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 144, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 144, 23)) x; // `foo-${string}` | 'bar' ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 136, 13)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 144, 13)) } } -function ff3(x: string, y: `foo-${string}`) { ->ff3 : Symbol(ff3, Decl(templateLiteralTypes3.ts, 140, 1)) ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) +function ff4(x: string, y: `foo-${string}`) { +>ff4 : Symbol(ff4, Decl(templateLiteralTypes3.ts, 148, 1)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 150, 13)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 150, 23)) if (x === 'foo-test') { ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 150, 13)) x; // 'foo-test' ->x : Symbol(x, Decl(templateLiteralTypes3.ts, 142, 13)) +>x : Symbol(x, Decl(templateLiteralTypes3.ts, 150, 13)) } if (y === 'foo-test') { ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 150, 23)) y; // 'foo-test' ->y : Symbol(y, Decl(templateLiteralTypes3.ts, 142, 23)) +>y : Symbol(y, Decl(templateLiteralTypes3.ts, 150, 23)) } } // Repro from #46045 -export type Action = ->Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 149, 1)) +type Action = +>Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 157, 1)) | { type: `${string}_REQUEST` } ->type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 162, 7)) | { type: `${string}_SUCCESS`, response: string }; ->type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) ->response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 163, 7)) +>response : Symbol(response, Decl(templateLiteralTypes3.ts, 163, 34)) -export function reducer(action: Action) { ->reducer : Symbol(reducer, Decl(templateLiteralTypes3.ts, 155, 54)) ->action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) ->Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 149, 1)) +function reducer(action: Action) { +>reducer : Symbol(reducer, Decl(templateLiteralTypes3.ts, 163, 54)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 165, 17)) +>Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 157, 1)) if (action.type === 'FOO_SUCCESS') { ->action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7), Decl(templateLiteralTypes3.ts, 155, 7)) ->action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) ->type : Symbol(type, Decl(templateLiteralTypes3.ts, 154, 7), Decl(templateLiteralTypes3.ts, 155, 7)) +>action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 162, 7), Decl(templateLiteralTypes3.ts, 163, 7)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 165, 17)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 162, 7), Decl(templateLiteralTypes3.ts, 163, 7)) action.type; ->action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) ->action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) ->type : Symbol(type, Decl(templateLiteralTypes3.ts, 155, 7)) +>action.type : Symbol(type, Decl(templateLiteralTypes3.ts, 163, 7)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 165, 17)) +>type : Symbol(type, Decl(templateLiteralTypes3.ts, 163, 7)) action.response; ->action.response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) ->action : Symbol(action, Decl(templateLiteralTypes3.ts, 157, 24)) ->response : Symbol(response, Decl(templateLiteralTypes3.ts, 155, 34)) +>action.response : Symbol(response, Decl(templateLiteralTypes3.ts, 163, 34)) +>action : Symbol(action, Decl(templateLiteralTypes3.ts, 165, 17)) +>response : Symbol(response, Decl(templateLiteralTypes3.ts, 163, 34)) } } diff --git a/tests/baselines/reference/templateLiteralTypes3.types b/tests/baselines/reference/templateLiteralTypes3.types index 35ace30c6f112..5a8f15f27fbab 100644 --- a/tests/baselines/reference/templateLiteralTypes3.types +++ b/tests/baselines/reference/templateLiteralTypes3.types @@ -398,8 +398,8 @@ chain("a"); // Repro from #46125 -function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { ->ff1 : (x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) => void +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +>ff1 : (x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) => void >x : `foo-${string}` >y : `${string}-bar` >z : `baz-${string}` @@ -419,8 +419,29 @@ function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-$ } } -function ff2(x: string, y: `foo-${string}` | 'bar') { ->ff2 : (x: string, y: `foo-${string}` | 'bar') => void +function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) { +>ff2 : (x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) => void +>x : `foo-${T}` +>y : `${T}-bar` +>z : `baz-${T}` + + if (x === y) { +>x === y : boolean +>x : `foo-${T}` +>y : `${T}-bar` + + x; // `foo-${T}` +>x : `foo-${T}` + } + if (x === z) { // Error +>x === z : boolean +>x : `foo-${T}` +>z : `baz-${T}` + } +} + +function ff3(x: string, y: `foo-${string}` | 'bar') { +>ff3 : (x: string, y: `foo-${string}` | 'bar') => void >x : string >y : "bar" | `foo-${string}` @@ -434,8 +455,8 @@ function ff2(x: string, y: `foo-${string}` | 'bar') { } } -function ff3(x: string, y: `foo-${string}`) { ->ff3 : (x: string, y: `foo-${string}`) => void +function ff4(x: string, y: `foo-${string}`) { +>ff4 : (x: string, y: `foo-${string}`) => void >x : string >y : `foo-${string}` @@ -459,7 +480,7 @@ function ff3(x: string, y: `foo-${string}`) { // Repro from #46045 -export type Action = +type Action = >Action : Action | { type: `${string}_REQUEST` } @@ -469,7 +490,7 @@ export type Action = >type : `${string}_SUCCESS` >response : string -export function reducer(action: Action) { +function reducer(action: Action) { >reducer : (action: Action) => void >action : Action diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts index 663f3fa1f2933..fbeae4b3f6545 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes3.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes3.ts @@ -129,7 +129,7 @@ chain("a"); // Repro from #46125 -function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { +function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`) { if (x === y) { x; // `foo-${string}` } @@ -137,13 +137,21 @@ function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-$ } } -function ff2(x: string, y: `foo-${string}` | 'bar') { +function ff2(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) { + if (x === y) { + x; // `foo-${T}` + } + if (x === z) { // Error + } +} + +function ff3(x: string, y: `foo-${string}` | 'bar') { if (x === y) { x; // `foo-${string}` | 'bar' } } -function ff3(x: string, y: `foo-${string}`) { +function ff4(x: string, y: `foo-${string}`) { if (x === 'foo-test') { x; // 'foo-test' } @@ -154,11 +162,11 @@ function ff3(x: string, y: `foo-${string}`) { // Repro from #46045 -export type Action = +type Action = | { type: `${string}_REQUEST` } | { type: `${string}_SUCCESS`, response: string }; -export function reducer(action: Action) { +function reducer(action: Action) { if (action.type === 'FOO_SUCCESS') { action.type; action.response;