Skip to content

Commit 3702283

Browse files
Resolve keyof and index operations instead of their targets. (#58758)
1 parent 145b106 commit 3702283

File tree

6 files changed

+285
-23
lines changed

6 files changed

+285
-23
lines changed

src/compiler/checker.ts

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,7 @@ import {
10881088
UnionType,
10891089
UnionTypeNode,
10901090
UniqueESSymbolType,
1091+
unwrapParenthesizedType,
10911092
usingSingleLineStringWriter,
10921093
VariableDeclaration,
10931094
VariableDeclarationList,
@@ -8601,7 +8602,58 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
86018602
return enterNewScope(context, node, getParametersInScope(node), getTypeParametersInScope(node));
86028603
}
86038604

8604-
function tryVisitTypeReference(node: TypeReferenceNode) {
8605+
function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined {
8606+
const innerNode = unwrapParenthesizedType(node);
8607+
switch (innerNode.kind) {
8608+
case SyntaxKind.TypeReference:
8609+
return tryVisitTypeReference(innerNode as TypeReferenceNode);
8610+
case SyntaxKind.TypeQuery:
8611+
return tryVisitTypeQuery(innerNode as TypeQueryNode);
8612+
case SyntaxKind.IndexedAccessType:
8613+
return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode);
8614+
case SyntaxKind.TypeOperator:
8615+
const typeOperatorNode = innerNode as TypeOperatorNode;
8616+
if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) {
8617+
return tryVisitKeyOf(typeOperatorNode);
8618+
}
8619+
}
8620+
return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode);
8621+
}
8622+
8623+
function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined {
8624+
const resultObjectType = tryVisitSimpleTypeNode(node.objectType);
8625+
if (resultObjectType === undefined) {
8626+
return undefined;
8627+
}
8628+
return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!);
8629+
}
8630+
8631+
function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined {
8632+
Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword);
8633+
const type = tryVisitSimpleTypeNode(node.type);
8634+
if (type === undefined) {
8635+
return undefined;
8636+
}
8637+
return factory.updateTypeOperatorNode(node, type);
8638+
}
8639+
8640+
function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined {
8641+
const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context);
8642+
if (!introducesError) {
8643+
return factory.updateTypeQueryNode(
8644+
node,
8645+
exprName,
8646+
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
8647+
);
8648+
}
8649+
8650+
const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
8651+
if (serializedName) {
8652+
return setTextRange(context, serializedName, node.exprName);
8653+
}
8654+
}
8655+
8656+
function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined {
86058657
if (canReuseTypeNode(context, node)) {
86068658
const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context);
86078659
const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode);
@@ -8728,13 +8780,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
87288780
);
87298781
}
87308782

8731-
if (isIndexedAccessTypeNode(node) && isTypeReferenceNode(node.objectType)) {
8732-
const objectType = tryVisitTypeReference(node.objectType);
8733-
if (!objectType) {
8783+
if (isIndexedAccessTypeNode(node)) {
8784+
const result = tryVisitIndexedAccess(node);
8785+
if (!result) {
87348786
hadError = true;
87358787
return node;
87368788
}
8737-
return factory.updateIndexedAccessTypeNode(node, objectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!);
8789+
return result;
87388790
}
87398791

87408792
if (isTypeReferenceNode(node)) {
@@ -8790,20 +8842,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
87908842
return visited;
87918843
}
87928844
if (isTypeQueryNode(node)) {
8793-
const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context);
8794-
if (introducesError) {
8795-
const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true);
8796-
if (serializedName) {
8797-
return setTextRange(context, serializedName, node.exprName);
8798-
}
8845+
const result = tryVisitTypeQuery(node);
8846+
if (!result) {
87998847
hadError = true;
88008848
return node;
88018849
}
8802-
return factory.updateTypeQueryNode(
8803-
node,
8804-
exprName,
8805-
visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode),
8806-
);
8850+
return result;
88078851
}
88088852
if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) {
88098853
const { node: result, introducesError } = trackExistingEntityName(node.expression, context);
@@ -8877,14 +8921,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
88778921
}
88788922
}
88798923
else if (node.operator === SyntaxKind.KeyOfKeyword) {
8880-
if (isTypeReferenceNode(node.type)) {
8881-
const type = tryVisitTypeReference(node.type);
8882-
if (!type) {
8883-
hadError = true;
8884-
return node;
8885-
}
8886-
return factory.updateTypeOperatorNode(node, type);
8924+
const result = tryVisitKeyOf(node);
8925+
if (!result) {
8926+
hadError = true;
8927+
return node;
88878928
}
8929+
return result;
88888930
}
88898931
}
88908932

src/compiler/utilities.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11644,6 +11644,14 @@ export function unwrapParenthesizedExpression(o: Expression) {
1164411644
return o;
1164511645
}
1164611646

11647+
/** @internal */
11648+
export function unwrapParenthesizedType(o: TypeNode) {
11649+
while (o.kind === SyntaxKind.ParenthesizedType) {
11650+
o = (o as ParenthesizedTypeNode).type;
11651+
}
11652+
return o;
11653+
}
11654+
1164711655
/** @internal */
1164811656
export function hasInferredType(node: Node): node is HasInferredType {
1164911657
Debug.type<HasInferredType>(node);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] ////
2+
3+
//// [decl.ts]
4+
const u = "X";
5+
type A = { a: { b : "value of b", notNecessary: typeof u }}
6+
const a = { a: "value of a", notNecessary: u } as const
7+
8+
9+
export const o1 = (o: A['a']['b']) => {}
10+
11+
export const o2 = (o: (typeof a)['a']) => {}
12+
export const o3 = (o: typeof a['a']) => {}
13+
14+
export const o4 = (o: keyof (A['a'])) => {}
15+
16+
//// [main.ts]
17+
import * as d from './decl'
18+
19+
export const f = {...d}
20+
21+
//// [decl.js]
22+
const u = "X";
23+
const a = { a: "value of a", notNecessary: u };
24+
export const o1 = (o) => { };
25+
export const o2 = (o) => { };
26+
export const o3 = (o) => { };
27+
export const o4 = (o) => { };
28+
//// [main.js]
29+
import * as d from './decl';
30+
export const f = { ...d };
31+
32+
33+
//// [decl.d.ts]
34+
declare const u = "X";
35+
type A = {
36+
a: {
37+
b: "value of b";
38+
notNecessary: typeof u;
39+
};
40+
};
41+
declare const a: {
42+
readonly a: "value of a";
43+
readonly notNecessary: "X";
44+
};
45+
export declare const o1: (o: A["a"]["b"]) => void;
46+
export declare const o2: (o: (typeof a)["a"]) => void;
47+
export declare const o3: (o: (typeof a)["a"]) => void;
48+
export declare const o4: (o: keyof A["a"]) => void;
49+
export {};
50+
//// [main.d.ts]
51+
export declare const f: {
52+
o1: (o: "value of b") => void;
53+
o2: (o: "value of a") => void;
54+
o3: (o: "value of a") => void;
55+
o4: (o: "b" | "notNecessary") => void;
56+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] ////
2+
3+
=== decl.ts ===
4+
const u = "X";
5+
>u : Symbol(u, Decl(decl.ts, 0, 5))
6+
7+
type A = { a: { b : "value of b", notNecessary: typeof u }}
8+
>A : Symbol(A, Decl(decl.ts, 0, 14))
9+
>a : Symbol(a, Decl(decl.ts, 1, 10))
10+
>b : Symbol(b, Decl(decl.ts, 1, 15))
11+
>notNecessary : Symbol(notNecessary, Decl(decl.ts, 1, 33))
12+
>u : Symbol(u, Decl(decl.ts, 0, 5))
13+
14+
const a = { a: "value of a", notNecessary: u } as const
15+
>a : Symbol(a, Decl(decl.ts, 2, 5))
16+
>a : Symbol(a, Decl(decl.ts, 2, 11))
17+
>notNecessary : Symbol(notNecessary, Decl(decl.ts, 2, 28))
18+
>u : Symbol(u, Decl(decl.ts, 0, 5))
19+
>const : Symbol(const)
20+
21+
22+
export const o1 = (o: A['a']['b']) => {}
23+
>o1 : Symbol(o1, Decl(decl.ts, 5, 12))
24+
>o : Symbol(o, Decl(decl.ts, 5, 19))
25+
>A : Symbol(A, Decl(decl.ts, 0, 14))
26+
27+
export const o2 = (o: (typeof a)['a']) => {}
28+
>o2 : Symbol(o2, Decl(decl.ts, 7, 12))
29+
>o : Symbol(o, Decl(decl.ts, 7, 19))
30+
>a : Symbol(a, Decl(decl.ts, 2, 5))
31+
32+
export const o3 = (o: typeof a['a']) => {}
33+
>o3 : Symbol(o3, Decl(decl.ts, 8, 12))
34+
>o : Symbol(o, Decl(decl.ts, 8, 19))
35+
>a : Symbol(a, Decl(decl.ts, 2, 5))
36+
37+
export const o4 = (o: keyof (A['a'])) => {}
38+
>o4 : Symbol(o4, Decl(decl.ts, 10, 12))
39+
>o : Symbol(o, Decl(decl.ts, 10, 19))
40+
>A : Symbol(A, Decl(decl.ts, 0, 14))
41+
42+
=== main.ts ===
43+
import * as d from './decl'
44+
>d : Symbol(d, Decl(main.ts, 0, 6))
45+
46+
export const f = {...d}
47+
>f : Symbol(f, Decl(main.ts, 2, 12))
48+
>d : Symbol(d, Decl(main.ts, 0, 6))
49+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//// [tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts] ////
2+
3+
=== decl.ts ===
4+
const u = "X";
5+
>u : "X"
6+
> : ^^^
7+
>"X" : "X"
8+
> : ^^^
9+
10+
type A = { a: { b : "value of b", notNecessary: typeof u }}
11+
>A : A
12+
> : ^
13+
>a : { b: "value of b"; notNecessary: typeof u; }
14+
> : ^^^^^ ^^^^^^^^^^^^^^^^ ^^^
15+
>b : "value of b"
16+
> : ^^^^^^^^^^^^
17+
>notNecessary : "X"
18+
> : ^^^
19+
>u : "X"
20+
> : ^^^
21+
22+
const a = { a: "value of a", notNecessary: u } as const
23+
>a : { readonly a: "value of a"; readonly notNecessary: "X"; }
24+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
>{ a: "value of a", notNecessary: u } as const : { readonly a: "value of a"; readonly notNecessary: "X"; }
26+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27+
>{ a: "value of a", notNecessary: u } : { readonly a: "value of a"; readonly notNecessary: "X"; }
28+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29+
>a : "value of a"
30+
> : ^^^^^^^^^^^^
31+
>"value of a" : "value of a"
32+
> : ^^^^^^^^^^^^
33+
>notNecessary : "X"
34+
> : ^^^
35+
>u : "X"
36+
> : ^^^
37+
38+
39+
export const o1 = (o: A['a']['b']) => {}
40+
>o1 : (o: A["a"]["b"]) => void
41+
> : ^ ^^ ^^^^^^^^^
42+
>(o: A['a']['b']) => {} : (o: A["a"]["b"]) => void
43+
> : ^ ^^ ^^^^^^^^^
44+
>o : "value of b"
45+
> : ^^^^^^^^^^^^
46+
47+
export const o2 = (o: (typeof a)['a']) => {}
48+
>o2 : (o: (typeof a)["a"]) => void
49+
> : ^ ^^^ ^ ^^^^^^^^^
50+
>(o: (typeof a)['a']) => {} : (o: (typeof a)["a"]) => void
51+
> : ^ ^^^ ^ ^^^^^^^^^
52+
>o : "value of a"
53+
> : ^^^^^^^^^^^^
54+
>a : { readonly a: "value of a"; readonly notNecessary: "X"; }
55+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56+
57+
export const o3 = (o: typeof a['a']) => {}
58+
>o3 : (o: (typeof a)["a"]) => void
59+
> : ^ ^^^ ^ ^^^^^^^^^
60+
>(o: typeof a['a']) => {} : (o: (typeof a)["a"]) => void
61+
> : ^ ^^^ ^ ^^^^^^^^^
62+
>o : "value of a"
63+
> : ^^^^^^^^^^^^
64+
>a : { readonly a: "value of a"; readonly notNecessary: "X"; }
65+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66+
67+
export const o4 = (o: keyof (A['a'])) => {}
68+
>o4 : (o: keyof A["a"]) => void
69+
> : ^ ^^ ^^^^^^^^^
70+
>(o: keyof (A['a'])) => {} : (o: keyof A["a"]) => void
71+
> : ^ ^^ ^^^^^^^^^
72+
>o : "b" | "notNecessary"
73+
> : ^^^^^^^^^^^^^^^^^^^^
74+
75+
=== main.ts ===
76+
import * as d from './decl'
77+
>d : typeof d
78+
> : ^^^^^^^^
79+
80+
export const f = {...d}
81+
>f : { o1: (o: "value of b") => void; o2: (o: "value of a") => void; o3: (o: "value of a") => void; o4: (o: "b" | "notNecessary") => void; }
82+
> : ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
83+
>{...d} : { o1: (o: "value of b") => void; o2: (o: "value of a") => void; o3: (o: "value of a") => void; o4: (o: "b" | "notNecessary") => void; }
84+
> : ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85+
>d : typeof d
86+
> : ^^^^^^^^
87+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @declaration: true
2+
// @target: esnext
3+
4+
// @filename: decl.ts
5+
const u = "X";
6+
type A = { a: { b : "value of b", notNecessary: typeof u }}
7+
const a = { a: "value of a", notNecessary: u } as const
8+
9+
10+
export const o1 = (o: A['a']['b']) => {}
11+
12+
export const o2 = (o: (typeof a)['a']) => {}
13+
export const o3 = (o: typeof a['a']) => {}
14+
15+
export const o4 = (o: keyof (A['a'])) => {}
16+
17+
// @filename: main.ts
18+
import * as d from './decl'
19+
20+
export const f = {...d}

0 commit comments

Comments
 (0)