From 6e8337ef70e4a36a4df76a3362a41cbcac0dcb84 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 26 Aug 2022 12:06:06 -0700 Subject: [PATCH] Optimize substitution types (#50397) * Optimize substitution type infrastructure * Accept new baselines * Preserve instantiated substitution types for type variables * Restrictive type parameters should have no constraint * Fix issues from top100 test run * Accept new baselines --- src/compiler/checker.ts | 60 ++++++++++--------- src/compiler/tracing.ts | 2 +- src/compiler/types.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 321592bcd44bc..f1dacf1f1eee2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12366,7 +12366,7 @@ namespace ts { return constraint && getBaseConstraint(constraint); } if (t.flags & TypeFlags.Substitution) { - return getBaseConstraint((t as SubstitutionType).substitute); + return getBaseConstraint(getSubstitutionIntersection(t as SubstitutionType)); } return t; } @@ -13903,22 +13903,27 @@ namespace ts { return links.resolvedJSDocType; } - function getSubstitutionType(baseType: Type, substitute: Type) { - if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === baseType) { + function getSubstitutionType(baseType: Type, constraint: Type) { + if (constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || + !isGenericType(baseType) && !isGenericType(constraint)) { return baseType; } - const id = `${getTypeId(baseType)}>${getTypeId(substitute)}`; + const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; const cached = substitutionTypes.get(id); if (cached) { return cached; } const result = createType(TypeFlags.Substitution) as SubstitutionType; result.baseType = baseType; - result.substitute = substitute; + result.constraint = constraint; substitutionTypes.set(id, result); return result; } + function getSubstitutionIntersection(substitutionType: SubstitutionType) { + return getIntersectionType([substitutionType.constraint, substitutionType.baseType]); + } + function isUnaryTupleTypeNode(node: TypeNode) { return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; } @@ -13963,7 +13968,7 @@ namespace ts { } node = parent; } - return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type; + return constraints ? getSubstitutionType(type, getIntersectionType(constraints)) : type; } function isJSDocTypeReference(node: Node): node is TypeReferenceNode { @@ -15363,7 +15368,7 @@ namespace ts { type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : - type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).substitute) : + type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint): type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : false; } @@ -15883,7 +15888,7 @@ namespace ts { if (type.flags & TypeFlags.Substitution) { if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | - getGenericObjectFlags((type as SubstitutionType).substitute) | getGenericObjectFlags((type as SubstitutionType).baseType); + getGenericObjectFlags((type as SubstitutionType).baseType) | getGenericObjectFlags((type as SubstitutionType).constraint); } return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } @@ -16095,11 +16100,7 @@ namespace ts { const objectType = getTypeFromTypeNode(node.objectType); const indexType = getTypeFromTypeNode(node.indexType); const potentialAlias = getAliasSymbolForTypeNode(node); - const resolved = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); - links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && - (resolved as IndexedAccessType).objectType === objectType && - (resolved as IndexedAccessType).indexType === indexType ? - getConditionalFlowTypeOfType(resolved, node) : resolved; + links.resolvedType = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); } return links.resolvedType; } @@ -17040,9 +17041,9 @@ namespace ts { } function getRestrictiveTypeParameter(tp: TypeParameter) { - return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( + return !tp.constraint && !getConstraintDeclaration(tp) || tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || ( tp.restrictiveInstantiation = createTypeParameter(tp.symbol), - (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, + (tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType, tp.restrictiveInstantiation ); } @@ -17429,17 +17430,18 @@ namespace ts { return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); } if (flags & TypeFlags.Substitution) { - const maybeVariable = instantiateType((type as SubstitutionType).baseType, mapper); - if (maybeVariable.flags & TypeFlags.TypeVariable) { - return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type as SubstitutionType).substitute, mapper)); + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); + // A substitution type originates in the true branch of a conditional type and can be resolved + // to just the base type in the same cases as the conditional type resolves to its true branch + // (because the base type is then known to satisfy the constraint). + if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { + return getSubstitutionType(newBaseType, newConstraint); } - else { - const sub = instantiateType((type as SubstitutionType).substitute, mapper); - if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { - return maybeVariable; - } - return sub; + if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); } return type; } @@ -18478,7 +18480,7 @@ namespace ts { const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : getSingleBaseForNonAugmentingSubtype(type) || type : type.flags & TypeFlags.UnionOrIntersection ? getNormalizedUnionOrIntersectionType(type as UnionOrIntersectionType, writing) : - type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : (type as SubstitutionType).substitute : + type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : getSubstitutionIntersection(type as SubstitutionType) : type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : type; if (t === type) return t; @@ -19561,7 +19563,11 @@ namespace ts { } } if (sourceFlags & TypeFlags.Substitution) { - return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, RecursionFlags.Both, /*reportErrors*/ false); + if (result = isRelatedTo((source as SubstitutionType).baseType, (target as SubstitutionType).baseType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as SubstitutionType).constraint, (target as SubstitutionType).constraint, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } if (!(sourceFlags & TypeFlags.Object)) { return Ternary.False; @@ -22699,7 +22705,7 @@ namespace ts { } else if (source.flags & TypeFlags.Substitution) { inferFromTypes((source as SubstitutionType).baseType, target); - inferWithPriority((source as SubstitutionType).substitute, target, InferencePriority.SubstituteSource); // Make substitute inference at a lower priority + inferWithPriority(getSubstitutionIntersection(source as SubstitutionType), target, InferencePriority.SubstituteSource); // Make substitute inference at a lower priority } else if (target.flags & TypeFlags.Conditional) { invokeOnce(source, target, inferToConditionalType); diff --git a/src/compiler/tracing.ts b/src/compiler/tracing.ts index 3fe7e335c7b35..5c7037b7e5c53 100644 --- a/src/compiler/tracing.ts +++ b/src/compiler/tracing.ts @@ -253,7 +253,7 @@ namespace ts { // eslint-disable-line local/one-namespace-per-file const substitutionType = type as SubstitutionType; substitutionProperties = { substitutionBaseType: substitutionType.baseType?.id, - substituteType: substitutionType.substitute?.id, + constraintType: substitutionType.constraint?.id, }; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 31bdb48f757ab..2fb40e37d3ff0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6145,8 +6145,8 @@ namespace ts { // types disappear upon instantiation (just like type parameters). export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; - baseType: Type; // Target type - substitute: Type; // Type to substitute for type parameter + baseType: Type; // Target type + constraint: Type; // Constraint that target type is known to satisfy } /* @internal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e377bbec1710a..5a0a1e2782c5d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2840,7 +2840,7 @@ declare namespace ts { export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; baseType: Type; - substitute: Type; + constraint: Type; } export enum SignatureKind { Call = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 857e3398c4c23..1f2b8230a6e12 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2840,7 +2840,7 @@ declare namespace ts { export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; baseType: Type; - substitute: Type; + constraint: Type; } export enum SignatureKind { Call = 0,