Improve reuse of nodes in signatures with type mapping (#58546)

This commit is contained in:
Titian Cernicova-Dragomir
2024-05-23 12:11:29 -07:00
committed by GitHub
parent b3f3bb3d38
commit 2b4e7e34e7
3164 changed files with 34008 additions and 33909 deletions
+90 -35
View File
@@ -740,6 +740,7 @@ import {
isThisInitializedObjectBindingExpression,
isThisInTypeQuery,
isThisProperty,
isThisTypeNode,
isThisTypeParameter,
isThisTypePredicate,
isTransientSymbol,
@@ -5970,7 +5971,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isClassInstanceSide(type: Type) {
return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone)));
}
/**
* Same as getTypeFromTypeNode, but for use in createNodeBuilder
* Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate
* This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode
*/
function getTypeFromTypeNodeWithoutContext(node: TypeNode) {
return getTypeFromTypeNode(node);
}
function createNodeBuilder() {
return {
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)),
@@ -5989,6 +5997,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)),
};
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type;
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined;
function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined {
const type = getTypeFromTypeNodeWithoutContext(node);
if (!context.mapper) return type;
const mappedType = instantiateType(type, context.mapper);
return noMappedTypes && mappedType !== type ? undefined : mappedType;
}
/**
* Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the
* same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files,
@@ -6058,7 +6076,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host);
if (clone) {
if (addUndefined && !someType(getTypeFromTypeNode(typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) {
return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
}
return clone;
@@ -6077,9 +6095,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
existing: TypeNode,
type: Type,
host = context.enclosingDeclaration,
annotationType?: Type,
annotationType = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true),
) {
if (typeNodeIsEquivalentToType(existing, host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) {
if (annotationType && typeNodeIsEquivalentToType(host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) {
const result = tryReuseExistingTypeNodeHelper(context, existing);
if (result) {
return result;
@@ -6132,6 +6150,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
typeParameterNames: undefined,
typeParameterNamesByText: undefined,
typeParameterNamesByTextNextNameCount: undefined,
mapper: undefined,
};
context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost);
const resultingNode = cb(context);
@@ -6421,8 +6440,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
context.inferTypeParameters = type.root.inferTypeParameters;
const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context);
context.inferTypeParameters = saveInferTypeParameters;
const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.trueType), newMapper));
const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.falseType), newMapper));
const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.trueType), newMapper));
const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.falseType), newMapper));
// outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive
// second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType
@@ -6519,7 +6538,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// homomorphic mapped type with a non-homomorphic naive inlining
// wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting
// type stays homomorphic
const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode((type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper);
const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode(context, (type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper);
return factory.createConditionalTypeNode(
typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context),
factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))),
@@ -7205,7 +7224,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let typeArguments: TypeNode[] | undefined;
const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0];
const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters);
const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper);
context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum
if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) {
@@ -7285,6 +7304,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
expandedParams: readonly Symbol[] | undefined,
typeParameters: readonly TypeParameter[] | undefined,
originalParameters?: readonly Symbol[] | undefined,
mapper?: TypeMapper,
) {
const cleanupContext = cloneNodeBuilderContext(context);
// For regular function/method declarations, the enclosing declaration will already be signature.declaration,
@@ -7302,6 +7322,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let cleanupParams: (() => void) | undefined;
let cleanupTypeParams: (() => void) | undefined;
const oldEnclosingDecl = context.enclosingDeclaration;
const oldMapper = context.mapper;
if (mapper) {
context.mapper = mapper;
}
if (context.enclosingDeclaration && declaration) {
// As a performance optimization, reuse the same fake scope within this chain.
// This is especially needed when we are working on an excessively deep type;
@@ -7434,6 +7458,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
cleanupTypeParams?.();
cleanupContext();
context.enclosingDeclaration = oldEnclosingDecl;
context.mapper = oldMapper;
};
}
@@ -7449,7 +7474,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
/*dotDotDotToken*/ undefined,
"this",
/*questionToken*/ undefined,
typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context),
typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context),
);
}
}
@@ -8144,7 +8169,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
};
}
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) {
function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) {
return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration)));
}
@@ -8182,11 +8207,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) {
const declWithExistingAnnotation = declaration && getNonlocalEffectiveTypeAnnotationNode(declaration)
? declaration
: getDeclarationWithTypeAnnotation(symbol, getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration));
: getDeclarationWithTypeAnnotation(symbol);
if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) {
// try to reuse the existing annotation
const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!;
const result = tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined);
const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined);
if (result) {
return result;
}
@@ -8212,7 +8237,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}
function typeNodeIsEquivalentToType(typeNode: TypeNode, annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode = getTypeFromTypeNode(typeNode)) {
function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) {
if (typeFromTypeNode === type) {
return true;
}
@@ -8245,13 +8270,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function serializeReturnTypeForSignatureWorker(context: NodeBuilderContext, signature: Signature) {
const typePredicate = getTypePredicateOfSignature(signature);
const type = getReturnTypeOfSignature(signature);
if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) {
if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames)) && signature.declaration && !nodeIsSynthesized(signature.declaration)) {
const annotation = signature.declaration && getNonlocalEffectiveReturnTypeAnnotationNode(signature.declaration);
const enclosingDeclarationIgnoringFakeScope = getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration);
if (!!findAncestor(annotation, n => n === enclosingDeclarationIgnoringFakeScope) && annotation) {
const annotated = getTypeFromTypeNode(annotation);
const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated;
const result = tryReuseExistingNonParameterTypeNode(context, annotation, type, signature.declaration, thisInstantiated);
// Default constructor signatures inherited from base classes return the derived class but have the base class declaration
// To ensure we don't serialize the wrong type we check that that return type of the signature corresponds to the declaration return type signature
if (annotation && getTypeFromTypeNode(context, annotation) === type) {
const result = tryReuseExistingTypeNodeHelper(context, annotation);
if (result) {
return result;
}
@@ -8382,13 +8406,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}
}
if (isThisTypeNode(existing)) {
if (context.mapper === undefined) return true;
const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true);
return !!type;
}
if (isTypeReferenceNode(existing)) {
if (isConstTypeReference(existing)) return false;
const type = getTypeFromTypeReference(existing);
const symbol = getNodeLinks(existing).resolvedSymbol;
if (!symbol) return false;
if (symbol.flags & SymbolFlags.TypeParameter) {
return true;
const type = getDeclaredTypeOfSymbol(symbol);
if (context.mapper && getMappedType(type, context.mapper) !== type) {
return false;
}
}
if (isInJSDoc(existing)) {
return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)
@@ -8408,7 +8440,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) {
const type = getTypeFromTypeNode(typeNode);
const type = getTypeFromTypeNode(context, typeNode);
return typeToTypeNodeHelper(type, context);
}
@@ -8472,8 +8504,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isJSDocTypeLiteral(node)) {
return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => {
const name = isIdentifier(t.name) ? t.name : t.name.right;
const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText);
const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined;
const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, node), name.escapedText);
const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(context, t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined;
return factory.createPropertySignature(
/*modifiers*/ undefined,
@@ -8509,7 +8541,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration(
/*modifiers*/ undefined,
getEffectiveDotDotDotForParameter(p),
getNameForJSDocFunctionParameter(p, i),
setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
p.questionToken,
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
/*initializer*/ undefined,
@@ -8524,7 +8556,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
factory.createParameterDeclaration(
/*modifiers*/ undefined,
getEffectiveDotDotDotForParameter(p),
getNameForJSDocFunctionParameter(p, i),
setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p),
p.questionToken,
visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode),
/*initializer*/ undefined,
@@ -8533,6 +8565,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}
}
if (isThisTypeNode(node)) {
if (canReuseTypeNode(context, node)) {
return node;
}
return serializeExistingTypeNode(context, node);
}
if (isTypeParameterDeclaration(node)) {
return factory.updateTypeParameterDeclaration(
node,
node.modifiers,
setTextRange(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node),
visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode),
visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode),
);
}
if (isTypeReferenceNode(node)) {
if (canReuseTypeNode(context, node)) {
const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context);
@@ -8567,7 +8614,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
!(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
)
) {
return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(context, node), context), node);
}
return factory.updateImportTypeNode(
node,
@@ -8633,13 +8680,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return factory.updateComputedPropertyName(node, literal);
}
}
if (isTypePredicateNode(node) && isIdentifier(node.parameterName)) {
const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context);
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
// /** @type {(v: unknown) => v is undefined} */
// const isUndef = v => v === undefined;
hadError = hadError || introducesError;
return factory.updateTypePredicateNode(node, node.assertsModifier, result, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
if (isTypePredicateNode(node)) {
let parameterName;
if (isIdentifier(node.parameterName)) {
const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context);
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
// /** @type {(v: unknown) => v is undefined} */
// const isUndef = v => v === undefined;
hadError = hadError || introducesError;
parameterName = result;
}
else {
parameterName = node.parameterName;
}
return factory.updateTypePredicateNode(node, node.assertsModifier, parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
}
if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
@@ -9478,8 +9532,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return cleanup(factory.createExpressionWithTypeArguments(
expr,
map(e.typeArguments, a =>
tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(a))
|| typeToTypeNodeHelper(getTypeFromTypeNode(a), context)),
tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(context, a))
|| typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)),
));
function cleanup<T>(result: T): T {
@@ -51616,6 +51670,7 @@ interface NodeBuilderContext {
remappedSymbolReferences: Map<SymbolId, Symbol> | undefined;
reverseMappedStack: ReverseMappedSymbol[] | undefined;
bundled: boolean;
mapper: TypeMapper | undefined;
}
class SymbolTrackerImpl implements SymbolTracker {
+1 -1
View File
@@ -557,7 +557,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(typeChecker: Type
typeChecker,
resolvedSymbol,
getSourceFileOfNode(resolvedNode),
resolvedNode,
enclosingDeclaration,
declarationName,
type,
semanticMeaning,