From 370228311cb426e80d811e29d9122f67244a735b Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Tue, 4 Jun 2024 02:21:48 +0300 Subject: [PATCH] Resolve keyof and index operations instead of their targets. (#58758) --- src/compiler/checker.ts | 88 ++++++++++++++----- src/compiler/utilities.ts | 8 ++ ...eclarationEmitResolveTypesIfNotReusable.js | 56 ++++++++++++ ...ationEmitResolveTypesIfNotReusable.symbols | 49 +++++++++++ ...arationEmitResolveTypesIfNotReusable.types | 87 ++++++++++++++++++ ...eclarationEmitResolveTypesIfNotReusable.ts | 20 +++++ 6 files changed, 285 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.js create mode 100644 tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.symbols create mode 100644 tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.types create mode 100644 tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 36bf6491f93..f029ccd321d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1088,6 +1088,7 @@ import { UnionType, UnionTypeNode, UniqueESSymbolType, + unwrapParenthesizedType, usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, @@ -8601,7 +8602,58 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return enterNewScope(context, node, getParametersInScope(node), getTypeParametersInScope(node)); } - function tryVisitTypeReference(node: TypeReferenceNode) { + function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined { + const innerNode = unwrapParenthesizedType(node); + switch (innerNode.kind) { + case SyntaxKind.TypeReference: + return tryVisitTypeReference(innerNode as TypeReferenceNode); + case SyntaxKind.TypeQuery: + return tryVisitTypeQuery(innerNode as TypeQueryNode); + case SyntaxKind.IndexedAccessType: + return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode); + case SyntaxKind.TypeOperator: + const typeOperatorNode = innerNode as TypeOperatorNode; + if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) { + return tryVisitKeyOf(typeOperatorNode); + } + } + return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode); + } + + function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined { + const resultObjectType = tryVisitSimpleTypeNode(node.objectType); + if (resultObjectType === undefined) { + return undefined; + } + return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!); + } + + function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined { + Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword); + const type = tryVisitSimpleTypeNode(node.type); + if (type === undefined) { + return undefined; + } + return factory.updateTypeOperatorNode(node, type); + } + + function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined { + const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context); + if (!introducesError) { + return factory.updateTypeQueryNode( + node, + exprName, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true); + if (serializedName) { + return setTextRange(context, serializedName, node.exprName); + } + } + + function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined { if (canReuseTypeNode(context, node)) { const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context); const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode); @@ -8728,13 +8780,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } - if (isIndexedAccessTypeNode(node) && isTypeReferenceNode(node.objectType)) { - const objectType = tryVisitTypeReference(node.objectType); - if (!objectType) { + if (isIndexedAccessTypeNode(node)) { + const result = tryVisitIndexedAccess(node); + if (!result) { hadError = true; return node; } - return factory.updateIndexedAccessTypeNode(node, objectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!); + return result; } if (isTypeReferenceNode(node)) { @@ -8790,20 +8842,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return visited; } 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); - } + const result = tryVisitTypeQuery(node); + if (!result) { hadError = true; return node; } - return factory.updateTypeQueryNode( - node, - exprName, - visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), - ); + return result; } if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) { const { node: result, introducesError } = trackExistingEntityName(node.expression, context); @@ -8877,14 +8921,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (node.operator === SyntaxKind.KeyOfKeyword) { - if (isTypeReferenceNode(node.type)) { - const type = tryVisitTypeReference(node.type); - if (!type) { - hadError = true; - return node; - } - return factory.updateTypeOperatorNode(node, type); + const result = tryVisitKeyOf(node); + if (!result) { + hadError = true; + return node; } + return result; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c7170af2355..38af602b781 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -11644,6 +11644,14 @@ export function unwrapParenthesizedExpression(o: Expression) { return o; } +/** @internal */ +export function unwrapParenthesizedType(o: TypeNode) { + while (o.kind === SyntaxKind.ParenthesizedType) { + o = (o as ParenthesizedTypeNode).type; + } + return o; +} + /** @internal */ export function hasInferredType(node: Node): node is HasInferredType { Debug.type(node); diff --git a/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.js b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.js new file mode 100644 index 00000000000..93dc0d48255 --- /dev/null +++ b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.js @@ -0,0 +1,56 @@ +//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] //// + +//// [decl.ts] +const u = "X"; +type A = { a: { b : "value of b", notNecessary: typeof u }} +const a = { a: "value of a", notNecessary: u } as const + + +export const o1 = (o: A['a']['b']) => {} + +export const o2 = (o: (typeof a)['a']) => {} +export const o3 = (o: typeof a['a']) => {} + +export const o4 = (o: keyof (A['a'])) => {} + +//// [main.ts] +import * as d from './decl' + +export const f = {...d} + +//// [decl.js] +const u = "X"; +const a = { a: "value of a", notNecessary: u }; +export const o1 = (o) => { }; +export const o2 = (o) => { }; +export const o3 = (o) => { }; +export const o4 = (o) => { }; +//// [main.js] +import * as d from './decl'; +export const f = { ...d }; + + +//// [decl.d.ts] +declare const u = "X"; +type A = { + a: { + b: "value of b"; + notNecessary: typeof u; + }; +}; +declare const a: { + readonly a: "value of a"; + readonly notNecessary: "X"; +}; +export declare const o1: (o: A["a"]["b"]) => void; +export declare const o2: (o: (typeof a)["a"]) => void; +export declare const o3: (o: (typeof a)["a"]) => void; +export declare const o4: (o: keyof A["a"]) => void; +export {}; +//// [main.d.ts] +export declare const f: { + o1: (o: "value of b") => void; + o2: (o: "value of a") => void; + o3: (o: "value of a") => void; + o4: (o: "b" | "notNecessary") => void; +}; diff --git a/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.symbols b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.symbols new file mode 100644 index 00000000000..6701591d9a9 --- /dev/null +++ b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.symbols @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] //// + +=== decl.ts === +const u = "X"; +>u : Symbol(u, Decl(decl.ts, 0, 5)) + +type A = { a: { b : "value of b", notNecessary: typeof u }} +>A : Symbol(A, Decl(decl.ts, 0, 14)) +>a : Symbol(a, Decl(decl.ts, 1, 10)) +>b : Symbol(b, Decl(decl.ts, 1, 15)) +>notNecessary : Symbol(notNecessary, Decl(decl.ts, 1, 33)) +>u : Symbol(u, Decl(decl.ts, 0, 5)) + +const a = { a: "value of a", notNecessary: u } as const +>a : Symbol(a, Decl(decl.ts, 2, 5)) +>a : Symbol(a, Decl(decl.ts, 2, 11)) +>notNecessary : Symbol(notNecessary, Decl(decl.ts, 2, 28)) +>u : Symbol(u, Decl(decl.ts, 0, 5)) +>const : Symbol(const) + + +export const o1 = (o: A['a']['b']) => {} +>o1 : Symbol(o1, Decl(decl.ts, 5, 12)) +>o : Symbol(o, Decl(decl.ts, 5, 19)) +>A : Symbol(A, Decl(decl.ts, 0, 14)) + +export const o2 = (o: (typeof a)['a']) => {} +>o2 : Symbol(o2, Decl(decl.ts, 7, 12)) +>o : Symbol(o, Decl(decl.ts, 7, 19)) +>a : Symbol(a, Decl(decl.ts, 2, 5)) + +export const o3 = (o: typeof a['a']) => {} +>o3 : Symbol(o3, Decl(decl.ts, 8, 12)) +>o : Symbol(o, Decl(decl.ts, 8, 19)) +>a : Symbol(a, Decl(decl.ts, 2, 5)) + +export const o4 = (o: keyof (A['a'])) => {} +>o4 : Symbol(o4, Decl(decl.ts, 10, 12)) +>o : Symbol(o, Decl(decl.ts, 10, 19)) +>A : Symbol(A, Decl(decl.ts, 0, 14)) + +=== main.ts === +import * as d from './decl' +>d : Symbol(d, Decl(main.ts, 0, 6)) + +export const f = {...d} +>f : Symbol(f, Decl(main.ts, 2, 12)) +>d : Symbol(d, Decl(main.ts, 0, 6)) + diff --git a/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.types b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.types new file mode 100644 index 00000000000..9df830887a4 --- /dev/null +++ b/tests/baselines/reference/declarationEmitResolveTypesIfNotReusable.types @@ -0,0 +1,87 @@ +//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] //// + +=== decl.ts === +const u = "X"; +>u : "X" +> : ^^^ +>"X" : "X" +> : ^^^ + +type A = { a: { b : "value of b", notNecessary: typeof u }} +>A : A +> : ^ +>a : { b: "value of b"; notNecessary: typeof u; } +> : ^^^^^ ^^^^^^^^^^^^^^^^ ^^^ +>b : "value of b" +> : ^^^^^^^^^^^^ +>notNecessary : "X" +> : ^^^ +>u : "X" +> : ^^^ + +const a = { a: "value of a", notNecessary: u } as const +>a : { readonly a: "value of a"; readonly notNecessary: "X"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ a: "value of a", notNecessary: u } as const : { readonly a: "value of a"; readonly notNecessary: "X"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ a: "value of a", notNecessary: u } : { readonly a: "value of a"; readonly notNecessary: "X"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : "value of a" +> : ^^^^^^^^^^^^ +>"value of a" : "value of a" +> : ^^^^^^^^^^^^ +>notNecessary : "X" +> : ^^^ +>u : "X" +> : ^^^ + + +export const o1 = (o: A['a']['b']) => {} +>o1 : (o: A["a"]["b"]) => void +> : ^ ^^ ^^^^^^^^^ +>(o: A['a']['b']) => {} : (o: A["a"]["b"]) => void +> : ^ ^^ ^^^^^^^^^ +>o : "value of b" +> : ^^^^^^^^^^^^ + +export const o2 = (o: (typeof a)['a']) => {} +>o2 : (o: (typeof a)["a"]) => void +> : ^ ^^^ ^ ^^^^^^^^^ +>(o: (typeof a)['a']) => {} : (o: (typeof a)["a"]) => void +> : ^ ^^^ ^ ^^^^^^^^^ +>o : "value of a" +> : ^^^^^^^^^^^^ +>a : { readonly a: "value of a"; readonly notNecessary: "X"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +export const o3 = (o: typeof a['a']) => {} +>o3 : (o: (typeof a)["a"]) => void +> : ^ ^^^ ^ ^^^^^^^^^ +>(o: typeof a['a']) => {} : (o: (typeof a)["a"]) => void +> : ^ ^^^ ^ ^^^^^^^^^ +>o : "value of a" +> : ^^^^^^^^^^^^ +>a : { readonly a: "value of a"; readonly notNecessary: "X"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +export const o4 = (o: keyof (A['a'])) => {} +>o4 : (o: keyof A["a"]) => void +> : ^ ^^ ^^^^^^^^^ +>(o: keyof (A['a'])) => {} : (o: keyof A["a"]) => void +> : ^ ^^ ^^^^^^^^^ +>o : "b" | "notNecessary" +> : ^^^^^^^^^^^^^^^^^^^^ + +=== main.ts === +import * as d from './decl' +>d : typeof d +> : ^^^^^^^^ + +export const f = {...d} +>f : { o1: (o: "value of b") => void; o2: (o: "value of a") => void; o3: (o: "value of a") => void; o4: (o: "b" | "notNecessary") => void; } +> : ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{...d} : { o1: (o: "value of b") => void; o2: (o: "value of a") => void; o3: (o: "value of a") => void; o4: (o: "b" | "notNecessary") => void; } +> : ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>d : typeof d +> : ^^^^^^^^ + diff --git a/tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts b/tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts new file mode 100644 index 00000000000..d1ec5d3c9ef --- /dev/null +++ b/tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts @@ -0,0 +1,20 @@ +// @declaration: true +// @target: esnext + +// @filename: decl.ts +const u = "X"; +type A = { a: { b : "value of b", notNecessary: typeof u }} +const a = { a: "value of a", notNecessary: u } as const + + +export const o1 = (o: A['a']['b']) => {} + +export const o2 = (o: (typeof a)['a']) => {} +export const o3 = (o: typeof a['a']) => {} + +export const o4 = (o: keyof (A['a'])) => {} + +// @filename: main.ts +import * as d from './decl' + +export const f = {...d} \ No newline at end of file