mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-10 21:07:52 -05:00
Fix issues + Support template literal types as discriminants (#46137)
* Fix issues + Support template literal types in discriminants * Add tests * Address CR feedback
This commit is contained in:
@@ -12040,7 +12040,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) {
|
||||
@@ -19014,6 +19014,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));
|
||||
@@ -19053,11 +19056,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;
|
||||
}
|
||||
@@ -21428,6 +21430,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;
|
||||
@@ -22528,7 +22542,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;
|
||||
}
|
||||
@@ -23087,15 +23101,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);
|
||||
}
|
||||
@@ -23938,7 +23954,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ 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(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 (6 errors) ====
|
||||
==== tests/cases/conformance/types/literal/templateLiteralTypes3.ts (8 errors) ====
|
||||
// Inference from template literal type to template literal type
|
||||
|
||||
type Foo1<T> = T extends `*${infer U}*` ? U : never;
|
||||
@@ -146,4 +148,54 @@ tests/cases/conformance/types/literal/templateLiteralTypes3.ts(74,5): error TS23
|
||||
declare function chain<F extends keyof Schema>(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<T extends string>(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 ff4(x: string, y: `foo-${string}`) {
|
||||
if (x === 'foo-test') {
|
||||
x; // 'foo-test'
|
||||
}
|
||||
if (y === 'foo-test') {
|
||||
y; // 'foo-test'
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #46045
|
||||
|
||||
type Action =
|
||||
| { type: `${string}_REQUEST` }
|
||||
| { type: `${string}_SUCCESS`, response: string };
|
||||
|
||||
function reducer(action: Action) {
|
||||
if (action.type === 'FOO_SUCCESS') {
|
||||
action.type;
|
||||
action.response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,52 @@ type Schema = { a: { b: { c: number } } };
|
||||
declare function chain<F extends keyof Schema>(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<T extends string>(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 ff4(x: string, y: `foo-${string}`) {
|
||||
if (x === 'foo-test') {
|
||||
x; // 'foo-test'
|
||||
}
|
||||
if (y === 'foo-test') {
|
||||
y; // 'foo-test'
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #46045
|
||||
|
||||
type Action =
|
||||
| { type: `${string}_REQUEST` }
|
||||
| { type: `${string}_SUCCESS`, response: string };
|
||||
|
||||
function reducer(action: Action) {
|
||||
if (action.type === 'FOO_SUCCESS') {
|
||||
action.type;
|
||||
action.response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [templateLiteralTypes3.js]
|
||||
@@ -177,6 +223,40 @@ 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, z) {
|
||||
if (x === y) {
|
||||
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'
|
||||
}
|
||||
if (y === 'foo-test') {
|
||||
y; // 'foo-test'
|
||||
}
|
||||
}
|
||||
function reducer(action) {
|
||||
if (action.type === 'FOO_SUCCESS') {
|
||||
action.type;
|
||||
action.response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [templateLiteralTypes3.d.ts]
|
||||
@@ -233,3 +313,14 @@ declare type Schema = {
|
||||
};
|
||||
};
|
||||
declare function chain<F extends keyof Schema>(field: F | `${F}.${F}`): void;
|
||||
declare function ff1(x: `foo-${string}`, y: `${string}-bar`, z: `baz-${string}`): void;
|
||||
declare function ff2<T extends string>(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;
|
||||
};
|
||||
declare function reducer(action: Action): void;
|
||||
|
||||
@@ -405,3 +405,114 @@ declare function chain<F extends keyof Schema>(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))
|
||||
>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, 13))
|
||||
>y : Symbol(y, Decl(templateLiteralTypes3.ts, 128, 32))
|
||||
|
||||
x; // `foo-${string}`
|
||||
>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13))
|
||||
}
|
||||
if (x === z) { // Error
|
||||
>x : Symbol(x, Decl(templateLiteralTypes3.ts, 128, 13))
|
||||
>z : Symbol(z, Decl(templateLiteralTypes3.ts, 128, 52))
|
||||
}
|
||||
}
|
||||
|
||||
function ff2<T extends string>(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) {
|
||||
>ff2 : Symbol(ff2, Decl(templateLiteralTypes3.ts, 134, 1))
|
||||
>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, 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, 144, 13))
|
||||
}
|
||||
}
|
||||
|
||||
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, 150, 13))
|
||||
|
||||
x; // 'foo-test'
|
||||
>x : Symbol(x, Decl(templateLiteralTypes3.ts, 150, 13))
|
||||
}
|
||||
if (y === 'foo-test') {
|
||||
>y : Symbol(y, Decl(templateLiteralTypes3.ts, 150, 23))
|
||||
|
||||
y; // 'foo-test'
|
||||
>y : Symbol(y, Decl(templateLiteralTypes3.ts, 150, 23))
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #46045
|
||||
|
||||
type Action =
|
||||
>Action : Symbol(Action, Decl(templateLiteralTypes3.ts, 157, 1))
|
||||
|
||||
| { type: `${string}_REQUEST` }
|
||||
>type : Symbol(type, Decl(templateLiteralTypes3.ts, 162, 7))
|
||||
|
||||
| { type: `${string}_SUCCESS`, response: string };
|
||||
>type : Symbol(type, Decl(templateLiteralTypes3.ts, 163, 7))
|
||||
>response : Symbol(response, Decl(templateLiteralTypes3.ts, 163, 34))
|
||||
|
||||
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, 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, 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, 163, 34))
|
||||
>action : Symbol(action, Decl(templateLiteralTypes3.ts, 165, 17))
|
||||
>response : Symbol(response, Decl(templateLiteralTypes3.ts, 163, 34))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -396,3 +396,120 @@ chain("a");
|
||||
>chain : <F extends "a">(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<T extends string>(x: `foo-${T}`, y: `${T}-bar`, z: `baz-${T}`) {
|
||||
>ff2 : <T extends string>(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}`
|
||||
|
||||
if (x === y) {
|
||||
>x === y : boolean
|
||||
>x : string
|
||||
>y : "bar" | `foo-${string}`
|
||||
|
||||
x; // `foo-${string}` | 'bar'
|
||||
>x : "bar" | `foo-${string}`
|
||||
}
|
||||
}
|
||||
|
||||
function ff4(x: string, y: `foo-${string}`) {
|
||||
>ff4 : (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
|
||||
|
||||
type Action =
|
||||
>Action : Action
|
||||
|
||||
| { type: `${string}_REQUEST` }
|
||||
>type : `${string}_REQUEST`
|
||||
|
||||
| { type: `${string}_SUCCESS`, response: string };
|
||||
>type : `${string}_SUCCESS`
|
||||
>response : string
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,3 +126,49 @@ type Schema = { a: { b: { c: number } } };
|
||||
declare function chain<F extends keyof Schema>(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<T extends string>(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 ff4(x: string, y: `foo-${string}`) {
|
||||
if (x === 'foo-test') {
|
||||
x; // 'foo-test'
|
||||
}
|
||||
if (y === 'foo-test') {
|
||||
y; // 'foo-test'
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #46045
|
||||
|
||||
type Action =
|
||||
| { type: `${string}_REQUEST` }
|
||||
| { type: `${string}_SUCCESS`, response: string };
|
||||
|
||||
function reducer(action: Action) {
|
||||
if (action.type === 'FOO_SUCCESS') {
|
||||
action.type;
|
||||
action.response;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user