Skip to content

Commit

Permalink
Optimize substitution types (#50397)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ahejlsberg committed Aug 26, 2022
1 parent 226dd0b commit 6e8337e
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 32 deletions.
60 changes: 33 additions & 27 deletions src/compiler/checker.ts
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tracing.ts
Expand Up @@ -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,
};
}

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Expand Up @@ -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,
Expand Down

0 comments on commit 6e8337e

Please sign in to comment.