Disable constraint reduction in intersections created by constraint hoisting (#58403)

This commit is contained in:
Anders Hejlsberg
2024-05-02 16:55:11 -07:00
committed by GitHub
parent 7a38980a7e
commit 3a74ec4e99
7 changed files with 266 additions and 17 deletions

View File

@@ -446,6 +446,7 @@ import {
InterfaceType,
InterfaceTypeWithDeclaredMembers,
InternalSymbolName,
IntersectionFlags,
IntersectionType,
IntersectionTypeNode,
intrinsicTagNameToString,
@@ -14196,7 +14197,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
// The source types were normalized; ensure the result is normalized too.
return getNormalizedType(getIntersectionType(constraints), /*writing*/ false);
return getNormalizedType(getIntersectionType(constraints, IntersectionFlags.NoConstraintReduction), /*writing*/ false);
}
return undefined;
}
@@ -17459,7 +17460,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// a type alias of the form "type List<T> = T & { next: List<T> }" cannot be reduced during its declaration.
// Also, unlike union types, the order of the constituent types is preserved in order that overload resolution
// for intersections of types with signatures can be deterministic.
function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], noSupertypeReduction?: boolean): Type {
function getIntersectionType(types: readonly Type[], flags = IntersectionFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
const typeMembershipMap = new Map<string, Type>();
const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types);
const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
@@ -17504,7 +17505,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
includes & TypeFlags.Void && includes & TypeFlags.Undefined ||
includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.DefinitelyNonNullable
) {
if (!noSupertypeReduction) removeRedundantSupertypes(typeSet, includes);
if (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes);
}
if (includes & TypeFlags.IncludesMissingType) {
typeSet[typeSet.indexOf(undefinedType)] = missingType;
@@ -17515,7 +17516,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (typeSet.length === 1) {
return typeSet[0];
}
if (typeSet.length === 2) {
if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) {
const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1;
const typeVariable = typeSet[typeVarIndex];
const primitiveType = typeSet[1 - typeVarIndex];
@@ -17548,7 +17549,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, aliasTypeArguments);
const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : getAliasId(aliasSymbol, aliasTypeArguments));
let result = intersectionTypes.get(id);
if (!result) {
if (includes & TypeFlags.Union) {
@@ -17556,16 +17557,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// When the intersection creates a reduced set (which might mean that *all* union types have
// disappeared), we restart the operation to get a new set of combined flags. Once we have
// reduced we'll never reduce again, so this occurs at most once.
result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
result = getIntersectionType(typeSet, flags, aliasSymbol, aliasTypeArguments);
}
else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) {
const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType;
removeFromEach(typeSet, TypeFlags.Undefined);
result = getUnionType([getIntersectionType(typeSet), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) {
removeFromEach(typeSet, TypeFlags.Null);
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
else if (typeSet.length >= 4) {
// When we have four or more constituents, some of which are unions, we employ a "divide and conquer" strategy
@@ -17573,7 +17574,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can
// dramatically reduce the overall work.
const middle = Math.floor(typeSet.length / 2);
result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle)), getIntersectionType(typeSet.slice(middle))], aliasSymbol, aliasTypeArguments);
result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, aliasSymbol, aliasTypeArguments);
}
else {
// We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
@@ -17582,7 +17583,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!checkCrossProductUnion(typeSet)) {
return errorType;
}
const constituents = getCrossProductIntersections(typeSet);
const constituents = getCrossProductIntersections(typeSet, flags);
// We attach a denormalized origin type when at least one constituent of the cross-product union is an
// intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and
// the denormalized origin has fewer constituents than the union itself.
@@ -17612,7 +17613,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}
function getCrossProductIntersections(types: readonly Type[]) {
function getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) {
const count = getCrossProductUnionSize(types);
const intersections: Type[] = [];
for (let i = 0; i < count; i++) {
@@ -17626,7 +17627,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
n = Math.floor(n / length);
}
}
const t = getIntersectionType(constituents);
const t = getIntersectionType(constituents, flags);
if (!(t.flags & TypeFlags.Never)) intersections.push(t);
}
return intersections;
@@ -17653,7 +17654,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1;
const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType;
const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t));
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
}
return links.resolvedType;
}
@@ -18533,7 +18534,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return undefined;
}
return accessFlags & AccessFlags.Writing
? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments)
? getIntersectionType(propTypes, IntersectionFlags.None, aliasSymbol, aliasTypeArguments)
: getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated);
@@ -19884,7 +19885,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const newAliasSymbol = aliasSymbol || type.aliasSymbol;
const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ?
getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) :
getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) :
getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments);
}
if (flags & TypeFlags.Index) {

View File

@@ -5379,6 +5379,13 @@ export const enum UnionReduction {
Subtype,
}
/** @internal */
export const enum IntersectionFlags {
None = 0,
NoSupertypeReduction = 1 << 0,
NoConstraintReduction = 1 << 1,
}
// dprint-ignore
/** @internal */
export const enum ContextFlags {

View File

@@ -0,0 +1,136 @@
//// [tests/cases/compiler/intersectionConstraintReduction.ts] ////
=== intersectionConstraintReduction.ts ===
type Test1<K1 extends keyof any, K2 extends keyof any> =
>Test1 : Symbol(Test1, Decl(intersectionConstraintReduction.ts, 0, 0))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 0, 11))
>K2 : Symbol(K2, Decl(intersectionConstraintReduction.ts, 0, 32))
MustBeKey<Extract<K1, keyof any> & K1 & K2>;
>MustBeKey : Symbol(MustBeKey, Decl(intersectionConstraintReduction.ts, 4, 48))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 0, 11))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 0, 11))
>K2 : Symbol(K2, Decl(intersectionConstraintReduction.ts, 0, 32))
type Test2<K1 extends keyof any, K2 extends keyof any> =
>Test2 : Symbol(Test2, Decl(intersectionConstraintReduction.ts, 1, 48))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 3, 11))
>K2 : Symbol(K2, Decl(intersectionConstraintReduction.ts, 3, 32))
MustBeKey<K1 & K2 & Extract<K1, keyof any>>;
>MustBeKey : Symbol(MustBeKey, Decl(intersectionConstraintReduction.ts, 4, 48))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 3, 11))
>K2 : Symbol(K2, Decl(intersectionConstraintReduction.ts, 3, 32))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>K1 : Symbol(K1, Decl(intersectionConstraintReduction.ts, 3, 11))
type MustBeKey<K extends keyof any> = K;
>MustBeKey : Symbol(MustBeKey, Decl(intersectionConstraintReduction.ts, 4, 48))
>K : Symbol(K, Decl(intersectionConstraintReduction.ts, 6, 15))
>K : Symbol(K, Decl(intersectionConstraintReduction.ts, 6, 15))
// https://github.com/microsoft/TypeScript/issues/58370
type AnyKey = number | string | symbol;
>AnyKey : Symbol(AnyKey, Decl(intersectionConstraintReduction.ts, 6, 40))
type ReturnTypeKeyof<Obj extends object> = Obj extends object
>ReturnTypeKeyof : Symbol(ReturnTypeKeyof, Decl(intersectionConstraintReduction.ts, 10, 39))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 12, 21))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 12, 21))
? [keyof Obj] extends [never]
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 12, 21))
? never
: { [Key in keyof Obj as string]-?: () => Key }[string]
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 15, 13))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 12, 21))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 15, 13))
: never;
type KeyIfSignatureOfObject<
>KeyIfSignatureOfObject : Symbol(KeyIfSignatureOfObject, Decl(intersectionConstraintReduction.ts, 16, 12))
Obj extends object,
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 18, 28))
Key extends AnyKey,
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 19, 23))
>AnyKey : Symbol(AnyKey, Decl(intersectionConstraintReduction.ts, 6, 40))
ReturnTypeKeys = ReturnTypeKeyof<Obj>,
>ReturnTypeKeys : Symbol(ReturnTypeKeys, Decl(intersectionConstraintReduction.ts, 20, 23))
>ReturnTypeKeyof : Symbol(ReturnTypeKeyof, Decl(intersectionConstraintReduction.ts, 10, 39))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 18, 28))
> = ReturnTypeKeys extends () => Key ? ((() => Key) extends ReturnTypeKeys ? Key : never) : never;
>ReturnTypeKeys : Symbol(ReturnTypeKeys, Decl(intersectionConstraintReduction.ts, 20, 23))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 19, 23))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 19, 23))
>ReturnTypeKeys : Symbol(ReturnTypeKeys, Decl(intersectionConstraintReduction.ts, 20, 23))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 19, 23))
export type Reduced1<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
>Reduced1 : Symbol(Reduced1, Decl(intersectionConstraintReduction.ts, 22, 98))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 24, 21))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 24, 40))
>AnyKey : Symbol(AnyKey, Decl(intersectionConstraintReduction.ts, 6, 40))
>Value : Symbol(Value, Decl(intersectionConstraintReduction.ts, 24, 60))
>ObjKeys : Symbol(ObjKeys, Decl(intersectionConstraintReduction.ts, 24, 67))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 24, 21))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 24, 21))
Key extends KeyIfSignatureOfObject<Obj, Key>
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 24, 40))
>KeyIfSignatureOfObject : Symbol(KeyIfSignatureOfObject, Decl(intersectionConstraintReduction.ts, 16, 12))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 24, 21))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 24, 40))
? Key extends ObjKeys
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 24, 40))
>ObjKeys : Symbol(ObjKeys, Decl(intersectionConstraintReduction.ts, 24, 67))
? { [K in Key]: Value }
>K : Symbol(K, Decl(intersectionConstraintReduction.ts, 27, 17))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 24, 40))
>Value : Symbol(Value, Decl(intersectionConstraintReduction.ts, 24, 60))
: never
: never;
export type Reduced2<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
>Reduced2 : Symbol(Reduced2, Decl(intersectionConstraintReduction.ts, 29, 16))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 31, 21))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
>AnyKey : Symbol(AnyKey, Decl(intersectionConstraintReduction.ts, 6, 40))
>Value : Symbol(Value, Decl(intersectionConstraintReduction.ts, 31, 60))
>ObjKeys : Symbol(ObjKeys, Decl(intersectionConstraintReduction.ts, 31, 67))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 31, 21))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 31, 21))
Key extends AnyKey
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
>AnyKey : Symbol(AnyKey, Decl(intersectionConstraintReduction.ts, 6, 40))
? Key extends KeyIfSignatureOfObject<Obj, Key>
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
>KeyIfSignatureOfObject : Symbol(KeyIfSignatureOfObject, Decl(intersectionConstraintReduction.ts, 16, 12))
>Obj : Symbol(Obj, Decl(intersectionConstraintReduction.ts, 31, 21))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
? Key extends ObjKeys
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
>ObjKeys : Symbol(ObjKeys, Decl(intersectionConstraintReduction.ts, 31, 67))
? { [K in Key]: Value }
>K : Symbol(K, Decl(intersectionConstraintReduction.ts, 35, 20))
>Key : Symbol(Key, Decl(intersectionConstraintReduction.ts, 31, 40))
>Value : Symbol(Value, Decl(intersectionConstraintReduction.ts, 31, 60))
: never
: never
: never;

View File

@@ -0,0 +1,65 @@
//// [tests/cases/compiler/intersectionConstraintReduction.ts] ////
=== intersectionConstraintReduction.ts ===
type Test1<K1 extends keyof any, K2 extends keyof any> =
>Test1 : Extract<K1, string | number | symbol> & K1 & K2
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MustBeKey<Extract<K1, keyof any> & K1 & K2>;
type Test2<K1 extends keyof any, K2 extends keyof any> =
>Test2 : K1 & K2 & Extract<K1, string | number | symbol>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MustBeKey<K1 & K2 & Extract<K1, keyof any>>;
type MustBeKey<K extends keyof any> = K;
>MustBeKey : K
> : ^
// https://github.com/microsoft/TypeScript/issues/58370
type AnyKey = number | string | symbol;
>AnyKey : AnyKey
> : ^^^^^^
type ReturnTypeKeyof<Obj extends object> = Obj extends object
>ReturnTypeKeyof : ReturnTypeKeyof<Obj>
> : ^^^^^^^^^^^^^^^^^^^^
? [keyof Obj] extends [never]
? never
: { [Key in keyof Obj as string]-?: () => Key }[string]
: never;
type KeyIfSignatureOfObject<
>KeyIfSignatureOfObject : KeyIfSignatureOfObject<Obj, Key, ReturnTypeKeys>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Obj extends object,
Key extends AnyKey,
ReturnTypeKeys = ReturnTypeKeyof<Obj>,
> = ReturnTypeKeys extends () => Key ? ((() => Key) extends ReturnTypeKeys ? Key : never) : never;
export type Reduced1<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
>Reduced1 : Reduced1<Obj, Key, Value, ObjKeys>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Key extends KeyIfSignatureOfObject<Obj, Key>
? Key extends ObjKeys
? { [K in Key]: Value }
: never
: never;
export type Reduced2<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
>Reduced2 : Reduced2<Obj, Key, Value, ObjKeys>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Key extends AnyKey
? Key extends KeyIfSignatureOfObject<Obj, Key>
? Key extends ObjKeys
? { [K in Key]: Value }
: never
: never
: never;

View File

@@ -1,7 +1,6 @@
//// [tests/cases/compiler/intersectionsOfLargeUnions.ts] ////
=== Performance Stats ===
Strict subtype cache: 1,000
Assignability cache: 1,000
Type Count: 2,500

View File

@@ -1,7 +1,6 @@
//// [tests/cases/compiler/intersectionsOfLargeUnions2.ts] ////
=== Performance Stats ===
Strict subtype cache: 1,000
Assignability cache: 1,000
Type Count: 2,500

View File

@@ -0,0 +1,42 @@
// @strict: true
// @noEmit: true
type Test1<K1 extends keyof any, K2 extends keyof any> =
MustBeKey<Extract<K1, keyof any> & K1 & K2>;
type Test2<K1 extends keyof any, K2 extends keyof any> =
MustBeKey<K1 & K2 & Extract<K1, keyof any>>;
type MustBeKey<K extends keyof any> = K;
// https://github.com/microsoft/TypeScript/issues/58370
type AnyKey = number | string | symbol;
type ReturnTypeKeyof<Obj extends object> = Obj extends object
? [keyof Obj] extends [never]
? never
: { [Key in keyof Obj as string]-?: () => Key }[string]
: never;
type KeyIfSignatureOfObject<
Obj extends object,
Key extends AnyKey,
ReturnTypeKeys = ReturnTypeKeyof<Obj>,
> = ReturnTypeKeys extends () => Key ? ((() => Key) extends ReturnTypeKeys ? Key : never) : never;
export type Reduced1<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
Key extends KeyIfSignatureOfObject<Obj, Key>
? Key extends ObjKeys
? { [K in Key]: Value }
: never
: never;
export type Reduced2<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
Key extends AnyKey
? Key extends KeyIfSignatureOfObject<Obj, Key>
? Key extends ObjKeys
? { [K in Key]: Value }
: never
: never
: never;