Skip to content

Commit

Permalink
🤖 Pick PR #58758 (Resolve keyof and index operations ...) into releas…
Browse files Browse the repository at this point in the history
…e-5.5 (#58767)

Co-authored-by: Titian Cernicova-Dragomir <[email protected]>
Co-authored-by: Daniel Rosenwasser <[email protected]>
  • Loading branch information
3 people authored Jun 3, 2024
1 parent 1065222 commit 2c50458
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 23 deletions.
87 changes: 64 additions & 23 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8601,7 +8601,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 = skipTypeParentheses(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);
Expand Down Expand Up @@ -8728,13 +8779,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)) {
Expand Down Expand Up @@ -8790,20 +8841,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);
Expand Down Expand Up @@ -8877,14 +8920,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;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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
> : ^^^^^^^^

20 changes: 20 additions & 0 deletions tests/cases/compiler/declarationEmitResolveTypesIfNotReusable.ts
Original file line number Diff line number Diff line change
@@ -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}

0 comments on commit 2c50458

Please sign in to comment.