Skip to content

Commit 6149b41

Browse files
authored
Generate names for type parameter declarations in inferred types (#23902)
* Generate names for type parameter declarations in inferred types * Fix lint * Merge functions, make overload private * Handle some edge cases better (nodes in differing files than current emit) * Account for transformed nodes
1 parent 1b796ed commit 6149b41

12 files changed

+192
-27
lines changed

src/compiler/checker.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -2746,7 +2746,7 @@ namespace ts {
27462746
* @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible
27472747
*/
27482748
function isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult {
2749-
if (symbol && enclosingDeclaration && !(symbol.flags & SymbolFlags.TypeParameter)) {
2749+
if (symbol && enclosingDeclaration) {
27502750
const initialSymbol = symbol;
27512751
let meaningToLook = meaning;
27522752
while (symbol) {
@@ -3118,7 +3118,15 @@ namespace ts {
31183118
}
31193119
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
31203120
if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) {
3121-
return createInferTypeNode(createTypeParameterDeclaration(getNameOfSymbolAsWritten(type.symbol)));
3121+
return createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined));
3122+
}
3123+
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
3124+
type.flags & TypeFlags.TypeParameter &&
3125+
length(type.symbol.declarations) &&
3126+
isTypeParameterDeclaration(type.symbol.declarations[0]) &&
3127+
typeParameterShadowsNameInScope(type, context) &&
3128+
!isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) {
3129+
return createTypeReferenceNode(getGeneratedNameForNode((type.symbol.declarations[0] as TypeParameterDeclaration).name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes), /*typeArguments*/ undefined);
31223130
}
31233131
const name = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false) : createIdentifier("?");
31243132
// Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter.
@@ -3563,10 +3571,21 @@ namespace ts {
35633571
return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments);
35643572
}
35653573

3566-
function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode): TypeParameterDeclaration {
3574+
function typeParameterShadowsNameInScope(type: TypeParameter, context: NodeBuilderContext) {
3575+
return !!resolveName(context.enclosingDeclaration, type.symbol.escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, type.symbol.escapedName, /*isUse*/ false);
3576+
}
3577+
3578+
function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration {
35673579
const savedContextFlags = context.flags;
35683580
context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic
3569-
const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
3581+
const shouldUseGeneratedName =
3582+
context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
3583+
type.symbol.declarations[0] &&
3584+
isTypeParameterDeclaration(type.symbol.declarations[0]) &&
3585+
typeParameterShadowsNameInScope(type, context);
3586+
const name = shouldUseGeneratedName
3587+
? getGeneratedNameForNode((type.symbol.declarations[0] as TypeParameterDeclaration).name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes)
3588+
: symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
35703589
const defaultParameter = getDefaultFromTypeParameter(type);
35713590
const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context);
35723591
context.flags = savedContextFlags;
@@ -3799,6 +3818,7 @@ namespace ts {
37993818
if (index === 0) {
38003819
context.flags ^= NodeBuilderFlags.InInitialEntityName;
38013820
}
3821+
38023822
const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
38033823
identifier.symbol = symbol;
38043824

src/compiler/emitter.ts

+38-12
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,7 @@ namespace ts {
11321132
}
11331133

11341134
function emitMethodSignature(node: MethodSignature) {
1135+
pushNameGenerationScope(node);
11351136
emitDecorators(node, node.decorators);
11361137
emitModifiers(node, node.modifiers);
11371138
emit(node.name);
@@ -1140,6 +1141,7 @@ namespace ts {
11401141
emitParameters(node, node.parameters);
11411142
emitTypeAnnotation(node.type);
11421143
writeSemicolon();
1144+
popNameGenerationScope(node);
11431145
}
11441146

11451147
function emitMethodDeclaration(node: MethodDeclaration) {
@@ -1167,15 +1169,18 @@ namespace ts {
11671169
}
11681170

11691171
function emitCallSignature(node: CallSignatureDeclaration) {
1172+
pushNameGenerationScope(node);
11701173
emitDecorators(node, node.decorators);
11711174
emitModifiers(node, node.modifiers);
11721175
emitTypeParameters(node, node.typeParameters);
11731176
emitParameters(node, node.parameters);
11741177
emitTypeAnnotation(node.type);
11751178
writeSemicolon();
1179+
popNameGenerationScope(node);
11761180
}
11771181

11781182
function emitConstructSignature(node: ConstructSignatureDeclaration) {
1183+
pushNameGenerationScope(node);
11791184
emitDecorators(node, node.decorators);
11801185
emitModifiers(node, node.modifiers);
11811186
writeKeyword("new");
@@ -1184,6 +1189,7 @@ namespace ts {
11841189
emitParameters(node, node.parameters);
11851190
emitTypeAnnotation(node.type);
11861191
writeSemicolon();
1192+
popNameGenerationScope(node);
11871193
}
11881194

11891195
function emitIndexSignature(node: IndexSignatureDeclaration) {
@@ -1216,12 +1222,14 @@ namespace ts {
12161222
}
12171223

12181224
function emitFunctionType(node: FunctionTypeNode) {
1225+
pushNameGenerationScope(node);
12191226
emitTypeParameters(node, node.typeParameters);
12201227
emitParametersForArrow(node, node.parameters);
12211228
writeSpace();
12221229
writePunctuation("=>");
12231230
writeSpace();
12241231
emit(node.type);
1232+
popNameGenerationScope(node);
12251233
}
12261234

12271235
function emitJSDocFunctionType(node: JSDocFunctionType) {
@@ -1248,6 +1256,7 @@ namespace ts {
12481256
}
12491257

12501258
function emitConstructorType(node: ConstructorTypeNode) {
1259+
pushNameGenerationScope(node);
12511260
writeKeyword("new");
12521261
writeSpace();
12531262
emitTypeParameters(node, node.typeParameters);
@@ -1256,6 +1265,7 @@ namespace ts {
12561265
writePunctuation("=>");
12571266
writeSpace();
12581267
emit(node.type);
1268+
popNameGenerationScope(node);
12591269
}
12601270

12611271
function emitTypeQuery(node: TypeQueryNode) {
@@ -3241,7 +3251,7 @@ namespace ts {
32413251
if (isGeneratedIdentifier(node)) {
32423252
return generateName(node);
32433253
}
3244-
else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent)) {
3254+
else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) {
32453255
return idText(node);
32463256
}
32473257
else if (node.kind === SyntaxKind.StringLiteral && (<StringLiteral>node).textSourceNode) {
@@ -3415,7 +3425,7 @@ namespace ts {
34153425
if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) {
34163426
// Node names generate unique names based on their original node
34173427
// and are cached based on that node's id.
3418-
return generateNameCached(getNodeForGeneratedName(name));
3428+
return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags);
34193429
}
34203430
else {
34213431
// Auto, Loop, and Unique names are cached based on their unique
@@ -3425,9 +3435,9 @@ namespace ts {
34253435
}
34263436
}
34273437

3428-
function generateNameCached(node: Node) {
3438+
function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) {
34293439
const nodeId = getNodeId(node);
3430-
return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node));
3440+
return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags));
34313441
}
34323442

34333443
/**
@@ -3444,7 +3454,7 @@ namespace ts {
34443454
* Returns a value indicating whether a name is unique globally or within the current file.
34453455
*/
34463456
function isFileLevelUniqueName(name: string) {
3447-
return ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName);
3457+
return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true;
34483458
}
34493459

34503460
/**
@@ -3504,10 +3514,15 @@ namespace ts {
35043514
* makeUniqueName are guaranteed to never conflict.
35053515
* If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1'
35063516
*/
3507-
function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean): string {
3517+
function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string {
35083518
if (optimistic) {
35093519
if (checkFn(baseName)) {
3510-
generatedNames.set(baseName, true);
3520+
if (scoped) {
3521+
reserveNameInNestedScopes(baseName);
3522+
}
3523+
else {
3524+
generatedNames.set(baseName, true);
3525+
}
35113526
return baseName;
35123527
}
35133528
}
@@ -3519,7 +3534,12 @@ namespace ts {
35193534
while (true) {
35203535
const generatedName = baseName + i;
35213536
if (checkFn(generatedName)) {
3522-
generatedNames.set(generatedName, true);
3537+
if (scoped) {
3538+
reserveNameInNestedScopes(generatedName);
3539+
}
3540+
else {
3541+
generatedNames.set(generatedName, true);
3542+
}
35233543
return generatedName;
35243544
}
35253545
i++;
@@ -3573,10 +3593,15 @@ namespace ts {
35733593
/**
35743594
* Generates a unique name from a node.
35753595
*/
3576-
function generateNameForNode(node: Node): string {
3596+
function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string {
35773597
switch (node.kind) {
35783598
case SyntaxKind.Identifier:
3579-
return makeUniqueName(getTextOfNode(node));
3599+
return makeUniqueName(
3600+
getTextOfNode(node),
3601+
isUniqueName,
3602+
!!(flags & GeneratedIdentifierFlags.Optimistic),
3603+
!!(flags & GeneratedIdentifierFlags.ReservedInNestedScopes)
3604+
);
35803605
case SyntaxKind.ModuleDeclaration:
35813606
case SyntaxKind.EnumDeclaration:
35823607
return generateNameForModuleOrEnum(<ModuleDeclaration | EnumDeclaration>node);
@@ -3611,7 +3636,8 @@ namespace ts {
36113636
return makeUniqueName(
36123637
idText(name),
36133638
(name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName,
3614-
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic)
3639+
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic),
3640+
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)
36153641
);
36163642
}
36173643

@@ -3631,7 +3657,7 @@ namespace ts {
36313657
// if "node" is a different generated name (having a different
36323658
// "autoGenerateId"), use it and stop traversing.
36333659
if (isIdentifier(node)
3634-
&& node.autoGenerateFlags === GeneratedIdentifierFlags.Node
3660+
&& !!(node.autoGenerateFlags & GeneratedIdentifierFlags.Node)
36353661
&& node.autoGenerateId !== autoGenerateId) {
36363662
break;
36373663
}

src/compiler/factory.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,11 @@ namespace ts {
192192
}
193193

194194
/** Create a unique name generated for a node. */
195-
export function getGeneratedNameForNode(node: Node): Identifier {
196-
const name = createIdentifier("");
197-
name.autoGenerateFlags = GeneratedIdentifierFlags.Node;
195+
export function getGeneratedNameForNode(node: Node): Identifier;
196+
/* @internal */ export function getGeneratedNameForNode(node: Node, flags: GeneratedIdentifierFlags): Identifier; // tslint:disable-line unified-signatures
197+
export function getGeneratedNameForNode(node: Node, flags?: GeneratedIdentifierFlags): Identifier {
198+
const name = createIdentifier(isIdentifier(node) ? idText(node) : "");
199+
name.autoGenerateFlags = GeneratedIdentifierFlags.Node | flags;
198200
name.autoGenerateId = nextAutoGenerateId;
199201
name.original = node;
200202
nextAutoGenerateId++;
@@ -4246,6 +4248,7 @@ namespace ts {
42464248
switch (member.kind) {
42474249
case SyntaxKind.TypeQuery:
42484250
case SyntaxKind.TypeOperator:
4251+
case SyntaxKind.InferType:
42494252
return createParenthesizedType(member);
42504253
}
42514254
return parenthesizeElementTypeMember(member);

src/compiler/transformers/declarations.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ namespace ts {
99
return result.diagnostics;
1010
}
1111

12-
const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | NodeBuilderFlags.UseTypeOfFunction | NodeBuilderFlags.UseStructuralFallback | NodeBuilderFlags.AllowEmptyTuple;
12+
const declarationEmitNodeBuilderFlags =
13+
NodeBuilderFlags.MultilineObjectLiterals |
14+
TypeFormatFlags.WriteClassExpressionAsTypeLiteral |
15+
NodeBuilderFlags.UseTypeOfFunction |
16+
NodeBuilderFlags.UseStructuralFallback |
17+
NodeBuilderFlags.AllowEmptyTuple |
18+
NodeBuilderFlags.GenerateNamesForShadowedTypeParams;
1319

1420
/**
1521
* Transforms a ts file into a .d.ts file
@@ -95,6 +101,7 @@ namespace ts {
95101
}
96102

97103
function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
104+
if (symbol.flags & SymbolFlags.TypeParameter) return;
98105
handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true));
99106
recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning));
100107
}

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3074,7 +3074,7 @@ namespace ts {
30743074
// Options
30753075
NoTruncation = 1 << 0, // Don't truncate result
30763076
WriteArrayAsGenericType = 1 << 1, // Write Array<T> instead T[]
3077-
// empty space
3077+
GenerateNamesForShadowedTypeParams = 1 << 2, // When a type parameter T is shadowing another T, generate a name for it so it can still be referenced
30783078
UseStructuralFallback = 1 << 3, // When an alias cannot be named by its symbol, rather than report an error, fallback to a structural printout if possible
30793079
// empty space
30803080
WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature

tests/baselines/reference/api/tsserverlibrary.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,7 @@ declare namespace ts {
18831883
None = 0,
18841884
NoTruncation = 1,
18851885
WriteArrayAsGenericType = 2,
1886+
GenerateNamesForShadowedTypeParams = 4,
18861887
UseStructuralFallback = 8,
18871888
WriteTypeArgumentsOfSignature = 32,
18881889
UseFullyQualifiedType = 64,

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,7 @@ declare namespace ts {
18831883
None = 0,
18841884
NoTruncation = 1,
18851885
WriteArrayAsGenericType = 2,
1886+
GenerateNamesForShadowedTypeParams = 4,
18861887
UseStructuralFallback = 8,
18871888
WriteTypeArgumentsOfSignature = 32,
18881889
UseFullyQualifiedType = 64,

tests/baselines/reference/conditionalTypes1.types

+6-6
Original file line numberDiff line numberDiff line change
@@ -927,32 +927,32 @@ type T52 = IsNever<any>; // false
927927
>IsNever : IsNever<T>
928928

929929
function f22<T>(x: T extends (infer U)[] ? U[] : never) {
930-
>f22 : <T>(x: T extends infer U[] ? U[] : never) => void
930+
>f22 : <T>(x: T extends (infer U)[] ? U[] : never) => void
931931
>T : T
932-
>x : T extends infer U[] ? U[] : never
932+
>x : T extends (infer U)[] ? U[] : never
933933
>T : T
934934
>U : U
935935
>U : U
936936

937937
let e = x[0]; // {}
938938
>e : {}
939939
>x[0] : {}
940-
>x : T extends infer U[] ? U[] : never
940+
>x : T extends (infer U)[] ? U[] : never
941941
>0 : 0
942942
}
943943

944944
function f23<T extends string[]>(x: T extends (infer U)[] ? U[] : never) {
945-
>f23 : <T extends string[]>(x: T extends infer U[] ? U[] : never) => void
945+
>f23 : <T extends string[]>(x: T extends (infer U)[] ? U[] : never) => void
946946
>T : T
947-
>x : T extends infer U[] ? U[] : never
947+
>x : T extends (infer U)[] ? U[] : never
948948
>T : T
949949
>U : U
950950
>U : U
951951

952952
let e = x[0]; // string
953953
>e : string
954954
>x[0] : string
955-
>x : T extends infer U[] ? U[] : never
955+
>x : T extends (infer U)[] ? U[] : never
956956
>0 : 0
957957
}
958958

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [declarationEmitNestedGenerics.ts]
2+
function f<T>(p: T) {
3+
let g: <T>(x: T) => typeof p = null as any;
4+
return g;
5+
}
6+
7+
function g<T>(x: T) {
8+
let y: typeof x extends (infer T)[] ? T : typeof x = null as any;
9+
return y;
10+
}
11+
12+
//// [declarationEmitNestedGenerics.js]
13+
function f(p) {
14+
var g = null;
15+
return g;
16+
}
17+
function g(x) {
18+
var y = null;
19+
return y;
20+
}
21+
22+
23+
//// [declarationEmitNestedGenerics.d.ts]
24+
declare function f<T>(p: T): <T_1>(x: T_1) => T;
25+
declare function g<T>(x: T): T extends (infer T_1)[] ? T_1 : T;

0 commit comments

Comments
 (0)