From 5c9a5a7f7e8179d37c34009aa75e952762c49564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 25 Sep 2023 12:41:23 +0200 Subject: [PATCH 1/5] Fixed an issue with string mappings over generic intersections not being deferred in conditional types --- src/compiler/checker.ts | 2 +- ...gMappingDeferralInConditionalTypes.symbols | 25 +++++++++++++++++++ ...ingMappingDeferralInConditionalTypes.types | 19 ++++++++++++++ ...stringMappingDeferralInConditionalTypes.ts | 10 ++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols create mode 100644 tests/baselines/reference/stringMappingDeferralInConditionalTypes.types create mode 100644 tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 40d4f07ffd325..3bae0e563ed79 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17915,7 +17915,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isPatternLiteralType(type: Type) { return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || - !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); + !!(type.flags & TypeFlags.StringMapping) && everyContainedType((type as StringMappingType).type, isPatternLiteralPlaceholderType); } function isGenericType(type: Type): boolean { diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols new file mode 100644 index 0000000000000..d081448fa39ef --- /dev/null +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts] //// + +=== stringMappingDeferralInConditionalTypes.ts === +// https://github.com/microsoft/TypeScript/issues/55847 + +type A = Lowercase extends "foo" ? 1 : 0; +>A : Symbol(A, Decl(stringMappingDeferralInConditionalTypes.ts, 0, 0)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 2, 7)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 2, 7)) + +let x1: A<"foo"> = 1; // ok +>x1 : Symbol(x1, Decl(stringMappingDeferralInConditionalTypes.ts, 3, 3)) +>A : Symbol(A, Decl(stringMappingDeferralInConditionalTypes.ts, 0, 0)) + +type B = Lowercase extends `f${string}` ? 1 : 0; +>B : Symbol(B, Decl(stringMappingDeferralInConditionalTypes.ts, 3, 21)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 5, 7)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 5, 7)) + +let x2: B<"foo"> = 1; // ok +>x2 : Symbol(x2, Decl(stringMappingDeferralInConditionalTypes.ts, 6, 3)) +>B : Symbol(B, Decl(stringMappingDeferralInConditionalTypes.ts, 3, 21)) + diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types new file mode 100644 index 0000000000000..128198a7878be --- /dev/null +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types @@ -0,0 +1,19 @@ +//// [tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts] //// + +=== stringMappingDeferralInConditionalTypes.ts === +// https://github.com/microsoft/TypeScript/issues/55847 + +type A = Lowercase extends "foo" ? 1 : 0; +>A : A + +let x1: A<"foo"> = 1; // ok +>x1 : 1 +>1 : 1 + +type B = Lowercase extends `f${string}` ? 1 : 0; +>B : B + +let x2: B<"foo"> = 1; // ok +>x2 : 1 +>1 : 1 + diff --git a/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts new file mode 100644 index 0000000000000..06947345e6f9d --- /dev/null +++ b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts @@ -0,0 +1,10 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/55847 + +type A = Lowercase extends "foo" ? 1 : 0; +let x1: A<"foo"> = 1; // ok + +type B = Lowercase extends `f${string}` ? 1 : 0; +let x2: B<"foo"> = 1; // ok From 9f56c7f72f69fb793ced78eb9c95441ec6ecbc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 25 Sep 2023 12:57:50 +0200 Subject: [PATCH 2/5] add extra test cases --- ...gMappingDeferralInConditionalTypes.symbols | 22 +++++++++++++++++++ ...ingMappingDeferralInConditionalTypes.types | 14 ++++++++++++ ...stringMappingDeferralInConditionalTypes.ts | 6 +++++ 3 files changed, 42 insertions(+) diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols index d081448fa39ef..a4f17c22c3a6b 100644 --- a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols @@ -23,3 +23,25 @@ let x2: B<"foo"> = 1; // ok >x2 : Symbol(x2, Decl(stringMappingDeferralInConditionalTypes.ts, 6, 3)) >B : Symbol(B, Decl(stringMappingDeferralInConditionalTypes.ts, 3, 21)) +type C = Capitalize> extends "Foo" ? 1 : 0; +>C : Symbol(C, Decl(stringMappingDeferralInConditionalTypes.ts, 6, 21)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 8, 7)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 8, 7)) + +let x3: C<"foo"> = 1; // ok +>x3 : Symbol(x3, Decl(stringMappingDeferralInConditionalTypes.ts, 9, 3)) +>C : Symbol(C, Decl(stringMappingDeferralInConditionalTypes.ts, 6, 21)) + +type D = Capitalize> extends "Foo" ? 1 : 0; +>D : Symbol(D, Decl(stringMappingDeferralInConditionalTypes.ts, 9, 21)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 11, 7)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 11, 7)) + +let x4: D<"foo"> = 1; // ok +>x4 : Symbol(x4, Decl(stringMappingDeferralInConditionalTypes.ts, 12, 3)) +>D : Symbol(D, Decl(stringMappingDeferralInConditionalTypes.ts, 9, 21)) + diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types index 128198a7878be..718827784371a 100644 --- a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types @@ -17,3 +17,17 @@ let x2: B<"foo"> = 1; // ok >x2 : 1 >1 : 1 +type C = Capitalize> extends "Foo" ? 1 : 0; +>C : C + +let x3: C<"foo"> = 1; // ok +>x3 : 1 +>1 : 1 + +type D = Capitalize> extends "Foo" ? 1 : 0; +>D : D + +let x4: D<"foo"> = 1; // ok +>x4 : 1 +>1 : 1 + diff --git a/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts index 06947345e6f9d..4204eb0b345ee 100644 --- a/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts +++ b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts @@ -8,3 +8,9 @@ let x1: A<"foo"> = 1; // ok type B = Lowercase extends `f${string}` ? 1 : 0; let x2: B<"foo"> = 1; // ok + +type C = Capitalize> extends "Foo" ? 1 : 0; +let x3: C<"foo"> = 1; // ok + +type D = Capitalize> extends "Foo" ? 1 : 0; +let x4: D<"foo"> = 1; // ok From f06327111ec403746b782fd2695e00960fb2bad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 27 Sep 2023 12:36:33 +0200 Subject: [PATCH 3/5] Fixed an extra test case --- src/compiler/checker.ts | 17 ++++++++------- ...gMappingDeferralInConditionalTypes.symbols | 21 +++++++++++++++++++ ...ingMappingDeferralInConditionalTypes.types | 13 ++++++++++++ ...stringMappingDeferralInConditionalTypes.ts | 5 +++++ 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3bae0e563ed79..fcf88c4bcc21f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17906,16 +17906,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { accessNode; } - function isPatternLiteralPlaceholderType(type: Type): boolean { + function isPatternLiteralPlaceholderType(type: Type, ignoreGenericIntersections = false): boolean { if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) || isPatternLiteralPlaceholderType(t)); + if (ignoreGenericIntersections && isGenericType(type)) { + return false; + } + return some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t, ignoreGenericIntersections)); } - return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type, ignoreGenericIntersections); } - function isPatternLiteralType(type: Type) { - return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || - !!(type.flags & TypeFlags.StringMapping) && everyContainedType((type as StringMappingType).type, isPatternLiteralPlaceholderType); + function isPatternLiteralType(type: Type, ignoreGenericIntersections = false) { + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, t => isPatternLiteralPlaceholderType(t, ignoreGenericIntersections)) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type, ignoreGenericIntersections); } function isGenericType(type: Type): boolean { @@ -17946,7 +17949,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | - (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type, /*ignoreGenericIntersections*/ true) ? ObjectFlags.IsGenericIndexType : 0); } function getSimplifiedType(type: Type, writing: boolean): Type { diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols index a4f17c22c3a6b..dc7a1a0bae710 100644 --- a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.symbols @@ -45,3 +45,24 @@ let x4: D<"foo"> = 1; // ok >x4 : Symbol(x4, Decl(stringMappingDeferralInConditionalTypes.ts, 12, 3)) >D : Symbol(D, Decl(stringMappingDeferralInConditionalTypes.ts, 9, 21)) +type E = Lowercase<`f${S & string}` & `${S & string}f`>; +>E : Symbol(E, Decl(stringMappingDeferralInConditionalTypes.ts, 12, 21)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 14, 7)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 14, 7)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 14, 7)) + +type F = E<""> extends "f" ? 1 : 0; // 1 +>F : Symbol(F, Decl(stringMappingDeferralInConditionalTypes.ts, 14, 59)) +>E : Symbol(E, Decl(stringMappingDeferralInConditionalTypes.ts, 12, 21)) + +type G = E extends "f" ? 1 : 0; +>G : Symbol(G, Decl(stringMappingDeferralInConditionalTypes.ts, 15, 35)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 16, 7)) +>E : Symbol(E, Decl(stringMappingDeferralInConditionalTypes.ts, 12, 21)) +>S : Symbol(S, Decl(stringMappingDeferralInConditionalTypes.ts, 16, 7)) + +let x5: G<""> = 1; // ok +>x5 : Symbol(x5, Decl(stringMappingDeferralInConditionalTypes.ts, 17, 3)) +>G : Symbol(G, Decl(stringMappingDeferralInConditionalTypes.ts, 15, 35)) + diff --git a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types index 718827784371a..2fe4b9b941521 100644 --- a/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types +++ b/tests/baselines/reference/stringMappingDeferralInConditionalTypes.types @@ -31,3 +31,16 @@ let x4: D<"foo"> = 1; // ok >x4 : 1 >1 : 1 +type E = Lowercase<`f${S & string}` & `${S & string}f`>; +>E : Lowercase<`f${S & string}` & `${S & string}f`> + +type F = E<""> extends "f" ? 1 : 0; // 1 +>F : 1 + +type G = E extends "f" ? 1 : 0; +>G : G + +let x5: G<""> = 1; // ok +>x5 : 1 +>1 : 1 + diff --git a/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts index 4204eb0b345ee..d003accdb9140 100644 --- a/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts +++ b/tests/cases/conformance/types/literal/stringMappingDeferralInConditionalTypes.ts @@ -14,3 +14,8 @@ let x3: C<"foo"> = 1; // ok type D = Capitalize> extends "Foo" ? 1 : 0; let x4: D<"foo"> = 1; // ok + +type E = Lowercase<`f${S & string}` & `${S & string}f`>; +type F = E<""> extends "f" ? 1 : 0; // 1 +type G = E extends "f" ? 1 : 0; +let x5: G<""> = 1; // ok From a6787385c31cd6d08c0f0c32800b8d69a70f8c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 29 Sep 2023 08:20:23 +0200 Subject: [PATCH 4/5] Simplify the fix --- src/compiler/checker.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fcf88c4bcc21f..6cc99c4fefb5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17906,19 +17906,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { accessNode; } - function isPatternLiteralPlaceholderType(type: Type, ignoreGenericIntersections = false): boolean { + function isPatternLiteralPlaceholderType(type: Type): boolean { if (type.flags & TypeFlags.Intersection) { - if (ignoreGenericIntersections && isGenericType(type)) { - return false; - } - return some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t, ignoreGenericIntersections)); + return !isGenericType(type) && some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t)); } - return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type, ignoreGenericIntersections); + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); } - function isPatternLiteralType(type: Type, ignoreGenericIntersections = false) { - return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, t => isPatternLiteralPlaceholderType(t, ignoreGenericIntersections)) || - !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type, ignoreGenericIntersections); + function isPatternLiteralType(type: Type) { + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, t => isPatternLiteralPlaceholderType(t)) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); } function isGenericType(type: Type): boolean { @@ -17949,7 +17946,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | - (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type, /*ignoreGenericIntersections*/ true) ? ObjectFlags.IsGenericIndexType : 0); + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); } function getSimplifiedType(type: Type, writing: boolean): Type { From 5d29c23025ede0c2cd8054f743c881e11f4f253a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 29 Sep 2023 15:00:41 +0200 Subject: [PATCH 5/5] remove redundant passthrough function --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6cc99c4fefb5a..d3bb73aa7189c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17914,7 +17914,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isPatternLiteralType(type: Type) { - return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, t => isPatternLiteralPlaceholderType(t)) || + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); }