Skip to content

Commit

Permalink
Partially reuse type nodes (#58516)
Browse files Browse the repository at this point in the history
  • Loading branch information
dragomirtitian committed May 14, 2024
1 parent 2d47953 commit 4ece0a3
Show file tree
Hide file tree
Showing 56 changed files with 807 additions and 300 deletions.
135 changes: 125 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5996,6 +5996,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!nodeIsSynthesized(range) && !(range.flags & NodeFlags.Synthesized) && (!context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(range))) {
range = factory.cloneNode(range);
}
if (range === location) return range;
if (!location) {
return range;
}
Expand Down Expand Up @@ -8340,6 +8341,62 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) {
const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type;
const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true);
if (!symbol) return undefined;
const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol;
if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined;
return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments);
}

function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) {
if (isInJSFile(existing)) {
if (isLiteralImportTypeNode(existing)) {
// Ensure resolvedSymbol is present
void getTypeFromImportTypeNode(existing);
const nodeSymbol = getNodeLinks(existing).resolvedSymbol;
return (
!nodeSymbol ||
!(
// The import type resolved using jsdoc fallback logic
(!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) ||
// The import type had type arguments autofilled by js fallback logic
!(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
)
);
}
}
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;
}
if (isInJSDoc(existing)) {
return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)
&& !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc
&& (symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes
}
}
if (
isTypeOperatorNode(existing) &&
existing.operator === SyntaxKind.UniqueKeyword &&
existing.type.kind === SyntaxKind.SymbolKeyword
) {
const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration);
return !!findAncestor(existing, n => n === effectiveEnclosingContext);
}
return true;
}

function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) {
const type = getTypeFromTypeNode(typeNode);
return typeToTypeNodeHelper(type, context);
}

/**
* Do you mean to call this directly? You probably should use `tryReuseExistingTypeNode` instead,
* which performs sanity checking on the type before doing this.
Expand All @@ -8353,6 +8410,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (hadError) {
return undefined;
}
context.approximateLength += existing.end - existing.pos;
return transformed;

function visitExistingNodeTreeSymbols(node: Node): Node | undefined {
Expand Down Expand Up @@ -8456,8 +8514,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}
}
if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) {
return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
if (isTypeReferenceNode(node)) {
if (canReuseTypeNode(context, node)) {
const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context);
const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode);

if (!introducesError) {
const updated = factory.updateTypeReferenceNode(
node,
newName,
typeArguments,
);
return setTextRange(context, updated, node);
}
else {
const serializedName = serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments);
if (serializedName) {
return setTextRange(context, serializedName, node.typeName);
}
}
}
return serializeExistingTypeNode(context, node);
}
if (isLiteralImportTypeNode(node)) {
const nodeSymbol = getNodeLinks(node).resolvedSymbol;
Expand All @@ -8471,7 +8548,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
!(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol)))
)
) {
return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
return setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node);
}
return factory.updateImportTypeNode(
node,
Expand Down Expand Up @@ -8503,15 +8580,47 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return visited;
}

if (isEntityName(node) || isEntityNameExpression(node)) {
if (isDeclarationName(node)) {
return node;
if (isTypeQueryNode(node)) {
const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context);
if (introducesError) {
const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
if (serializedName) {
return setTextRange(context, serializedName, node.exprName);
}
return serializeExistingTypeNode(context, node);
}
const { introducesError, node: result } = trackExistingEntityName(node, context);
return factory.updateTypeQueryNode(
node,
exprName,
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
);
}
if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) {
const { node: result, introducesError } = trackExistingEntityName(node.expression, context);
if (!introducesError) {
return factory.updateComputedPropertyName(node, result);
}
else {
const type = getWidenedType(getRegularTypeOfExpression(node.expression));
const computedPropertyNameType = typeToTypeNodeHelper(type, context);
Debug.assertNode(computedPropertyNameType, isLiteralTypeNode);
const literal = computedPropertyNameType.literal;
if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(compilerOptions))) {
return factory.createIdentifier(literal.text);
}
if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) {
return literal;
}
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;
// We should not go to child nodes of the entity name, they will not be accessible
return result;
return factory.updateTypePredicateNode(node, node.assertsModifier, result, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode));
}

if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) {
Expand Down Expand Up @@ -8543,6 +8652,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}

if (isTypeOperatorNode(node) && node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) {
if (!canReuseTypeNode(context, node)) {
return serializeExistingTypeNode(context, node);
}
}

return visitEachChild(node, visitExistingNodeTreeSymbols, /*context*/ undefined);

function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callbackTagNamespace.types
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var x = 1;
/** @type {NS.Nested.Inner} */
function f(space, peace) {
>f : (space: any, peace: any) => string | number
> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
>space : any
>peace : any

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callsOnComplexSignatures.types
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function test1() {

function test(t: Temp1 | Temp2) {
>test : (t: Temp1 | Temp2) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^
>t : Temp1 | Temp2
> : ^^^^^^^^^^^^^

Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/correlatedUnions.types
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ function ff1() {
}
function apply<K extends Keys>(funKey: K, ...args: ArgMap[K]) {
>apply : <K extends keyof { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }>(funKey: K, ...args: { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }[K]) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
>funKey : K
> : ^
>args : { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }[K]
Expand Down Expand Up @@ -854,7 +854,7 @@ function ff1() {
>apply('sum', 1, 2) : void
> : ^^^^
>apply : <K extends keyof { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }>(funKey: K, ...args: { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }[K]) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
>'sum' : "sum"
> : ^^^^^
>1 : 1
Expand All @@ -868,7 +868,7 @@ function ff1() {
>apply('concat', 'str1', 'str2', 'str3' ) : void
> : ^^^^
>apply : <K extends keyof { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }>(funKey: K, ...args: { sum: [a: number, b: number]; concat: [a: string, b: string, c: string]; }[K]) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
>'concat' : "concat"
> : ^^^^^^^^
>'str1' : "str1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export default (suit: Suit, rank: Rank) => ({suit, rank});
=== index.ts ===
export let lazyCard = () => import('./Card').then(a => a.default);
>lazyCard : () => Promise<(suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }>
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^^^^^^^^^^^^^^^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>() => import('./Card').then(a => a.default) : () => Promise<(suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }>
> : ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^^^^^^^^^^^^^^^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>import('./Card').then(a => a.default) : Promise<(suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }>
> : ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^^^^^^^^^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>import('./Card').then : <TResult1 = typeof import("Card"), TResult2 = never>(onfulfilled?: (value: typeof import("Card")) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>import('./Card') : Promise<typeof import("Card")>
Expand All @@ -55,15 +55,15 @@ export let lazyCard = () => import('./Card').then(a => a.default);
>then : <TResult1 = typeof import("Card"), TResult2 = never>(onfulfilled?: (value: typeof import("Card")) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>a => a.default : (a: typeof import("Card")) => (suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>a : typeof import("Card")
> : ^^^^^^^^^^^^^^^^^^^^^
>a.default : (suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>a : typeof import("Card")
> : ^^^^^^^^^^^^^^^^^^^^^
>default : (suit: import("Types").Suit, rank: import("Types").Rank) => { suit: import("Types").Suit; rank: import("Types").Rank; }
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^ ^^ ^^^^^^^ ^^^^^^ ^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

export { Suit, Rank } from './Types';
>Suit : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function getComp(): Component {
=== src/inferred-comp-export.ts ===
import { getComp } from "./get-comp";
>getComp : () => import("node_modules/@types/react/index").Component
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^

// this shouldn't need any triple-slash references - it should have a direct import to `react` and that's it
// This issue (#35343) _only_ reproduces in the test harness when the file in question is in a subfolder
Expand All @@ -71,7 +71,7 @@ export const obj = {
>getComp() : import("node_modules/@types/react/index").Component
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>getComp : () => import("node_modules/@types/react/index").Component
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> : ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
}
=== src/some-other-file.ts ===

Expand Down

0 comments on commit 4ece0a3

Please sign in to comment.