Resolve keyof and index operations instead of their targets. (#58758)

This commit is contained in:
Titian Cernicova-Dragomir
2024-06-04 02:21:48 +03:00
committed by GitHub
parent 145b106c20
commit 370228311c
6 changed files with 285 additions and 23 deletions

View File

@@ -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;
}
}

View File

@@ -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<HasInferredType>(node);

View File

@@ -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;
};

View File

@@ -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))

View File

@@ -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
> : ^^^^^^^^

View File

@@ -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}