From 952dfc508256fea697aacc431edb81e3f23033b5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Mar 2024 16:13:31 -0700 Subject: [PATCH] Exclude generic string-like types from intersection reduction (#57751) --- src/compiler/checker.ts | 11 ++- ...tionReductionGenericStringLikeType.symbols | 77 +++++++++++++++++++ ...ectionReductionGenericStringLikeType.types | 44 +++++++++++ ...ersectionReductionGenericStringLikeType.ts | 31 ++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/intersectionReductionGenericStringLikeType.symbols create mode 100644 tests/baselines/reference/intersectionReductionGenericStringLikeType.types create mode 100644 tests/cases/compiler/intersectionReductionGenericStringLikeType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee1dfb41fa59b..f1a496116984f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17642,7 +17642,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1; const typeVariable = typeSet[typeVarIndex]; const primitiveType = typeSet[1 - typeVarIndex]; - if (typeVariable.flags & TypeFlags.TypeVariable && (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || includes & TypeFlags.IncludesEmptyObject)) { + if ( + typeVariable.flags & TypeFlags.TypeVariable && + (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject) + ) { // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. const constraint = getBaseConstraintOfType(typeVariable); // Check that T's constraint is similarly composed of primitive types, the object type, or {}. @@ -18389,6 +18392,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); } + function isGenericStringLikeType(type: Type) { + return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); + } + function isGenericType(type: Type): boolean { return !!getGenericObjectFlags(type); } @@ -18417,7 +18424,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.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(type) ? ObjectFlags.IsGenericIndexType : 0); } function getSimplifiedType(type: Type, writing: boolean): Type { diff --git a/tests/baselines/reference/intersectionReductionGenericStringLikeType.symbols b/tests/baselines/reference/intersectionReductionGenericStringLikeType.symbols new file mode 100644 index 0000000000000..ad256535dde14 --- /dev/null +++ b/tests/baselines/reference/intersectionReductionGenericStringLikeType.symbols @@ -0,0 +1,77 @@ +//// [tests/cases/compiler/intersectionReductionGenericStringLikeType.ts] //// + +=== intersectionReductionGenericStringLikeType.ts === +// https://github.com/microsoft/TypeScript/issues/57736 + +type obj = { +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) + + foo: 1; +>foo : Symbol(foo, Decl(intersectionReductionGenericStringLikeType.ts, 2, 12)) + + bar: 2; +>bar : Symbol(bar, Decl(intersectionReductionGenericStringLikeType.ts, 3, 11)) + +}; + +type keyContaining1< +>keyContaining1 : Symbol(keyContaining1, Decl(intersectionReductionGenericStringLikeType.ts, 5, 2)) + + str extends string, +>str : Symbol(str, Decl(intersectionReductionGenericStringLikeType.ts, 7, 20)) + + keys extends keyof obj = keyof obj, +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 8, 23)) +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) + +> = keys extends infer key extends keyof obj +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 8, 23)) +>key : Symbol(key, Decl(intersectionReductionGenericStringLikeType.ts, 10, 22)) +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) + + ? key extends `${string}${str}${string}` +>key : Symbol(key, Decl(intersectionReductionGenericStringLikeType.ts, 10, 22)) +>str : Symbol(str, Decl(intersectionReductionGenericStringLikeType.ts, 7, 20)) + + ? obj[key] +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) +>key : Symbol(key, Decl(intersectionReductionGenericStringLikeType.ts, 10, 22)) + + : never + : never; + +type _1 = keyContaining1<"foo">; // 1 +>_1 : Symbol(_1, Decl(intersectionReductionGenericStringLikeType.ts, 14, 12)) +>keyContaining1 : Symbol(keyContaining1, Decl(intersectionReductionGenericStringLikeType.ts, 5, 2)) + +type keyContaining2< +>keyContaining2 : Symbol(keyContaining2, Decl(intersectionReductionGenericStringLikeType.ts, 16, 32)) + + str extends string, +>str : Symbol(str, Decl(intersectionReductionGenericStringLikeType.ts, 18, 20)) + + keys extends keyof obj = keyof obj, +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 19, 23)) +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) + +> = keys extends keys +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 19, 23)) +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 19, 23)) + + ? keys extends `${string}${str}${string}` +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 19, 23)) +>str : Symbol(str, Decl(intersectionReductionGenericStringLikeType.ts, 18, 20)) + + ? obj[keys] +>obj : Symbol(obj, Decl(intersectionReductionGenericStringLikeType.ts, 0, 0)) +>keys : Symbol(keys, Decl(intersectionReductionGenericStringLikeType.ts, 19, 23)) + + : never + : never; + +type _2 = keyContaining2<"foo">; // 1 +>_2 : Symbol(_2, Decl(intersectionReductionGenericStringLikeType.ts, 25, 12)) +>keyContaining2 : Symbol(keyContaining2, Decl(intersectionReductionGenericStringLikeType.ts, 16, 32)) + diff --git a/tests/baselines/reference/intersectionReductionGenericStringLikeType.types b/tests/baselines/reference/intersectionReductionGenericStringLikeType.types new file mode 100644 index 0000000000000..0d3af56acf2ad --- /dev/null +++ b/tests/baselines/reference/intersectionReductionGenericStringLikeType.types @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/intersectionReductionGenericStringLikeType.ts] //// + +=== intersectionReductionGenericStringLikeType.ts === +// https://github.com/microsoft/TypeScript/issues/57736 + +type obj = { +>obj : { foo: 1; bar: 2; } + + foo: 1; +>foo : 1 + + bar: 2; +>bar : 2 + +}; + +type keyContaining1< +>keyContaining1 : keyContaining1 + + str extends string, + keys extends keyof obj = keyof obj, +> = keys extends infer key extends keyof obj + ? key extends `${string}${str}${string}` + ? obj[key] + : never + : never; + +type _1 = keyContaining1<"foo">; // 1 +>_1 : 1 + +type keyContaining2< +>keyContaining2 : keyContaining2 + + str extends string, + keys extends keyof obj = keyof obj, +> = keys extends keys + ? keys extends `${string}${str}${string}` + ? obj[keys] + : never + : never; + +type _2 = keyContaining2<"foo">; // 1 +>_2 : 1 + diff --git a/tests/cases/compiler/intersectionReductionGenericStringLikeType.ts b/tests/cases/compiler/intersectionReductionGenericStringLikeType.ts new file mode 100644 index 0000000000000..c59a4321d074d --- /dev/null +++ b/tests/cases/compiler/intersectionReductionGenericStringLikeType.ts @@ -0,0 +1,31 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/57736 + +type obj = { + foo: 1; + bar: 2; +}; + +type keyContaining1< + str extends string, + keys extends keyof obj = keyof obj, +> = keys extends infer key extends keyof obj + ? key extends `${string}${str}${string}` + ? obj[key] + : never + : never; + +type _1 = keyContaining1<"foo">; // 1 + +type keyContaining2< + str extends string, + keys extends keyof obj = keyof obj, +> = keys extends keys + ? keys extends `${string}${str}${string}` + ? obj[keys] + : never + : never; + +type _2 = keyContaining2<"foo">; // 1