Skip to content

Generate names for type parameter declarations in inferred types #23902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2731,7 +2731,7 @@ namespace ts {
* @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible
*/
function isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult {
if (symbol && enclosingDeclaration && !(symbol.flags & SymbolFlags.TypeParameter)) {
if (symbol && enclosingDeclaration) {
const initialSymbol = symbol;
let meaningToLook = meaning;
while (symbol) {
Expand Down Expand Up @@ -3098,7 +3098,15 @@ namespace ts {
}
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) {
return createInferTypeNode(createTypeParameterDeclaration(getNameOfSymbolAsWritten(type.symbol)));
return createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined));
}
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
type.flags & TypeFlags.TypeParameter &&
length(type.symbol.declarations) &&
isTypeParameterDeclaration(type.symbol.declarations[0]) &&
typeParameterShadowsNameInScope(type, context) &&
!isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) {
return createTypeReferenceNode(getGeneratedNameForNode((type.symbol.declarations[0] as TypeParameterDeclaration).name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes), /*typeArguments*/ undefined);
}
const name = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false) : createIdentifier("?");
// Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter.
Expand Down Expand Up @@ -3543,10 +3551,21 @@ namespace ts {
return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments);
}

function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode): TypeParameterDeclaration {
function typeParameterShadowsNameInScope(type: TypeParameter, context: NodeBuilderContext) {
return !!resolveName(context.enclosingDeclaration, type.symbol.escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, type.symbol.escapedName, /*isUse*/ false);
}

function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration {
const savedContextFlags = context.flags;
context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic
const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
const shouldUseGeneratedName =
context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
type.symbol.declarations[0] &&
isTypeParameterDeclaration(type.symbol.declarations[0]) &&
typeParameterShadowsNameInScope(type, context);
const name = shouldUseGeneratedName
? getGeneratedNameForNode((type.symbol.declarations[0] as TypeParameterDeclaration).name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes)
: symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true);
const defaultParameter = getDefaultFromTypeParameter(type);
const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context);
context.flags = savedContextFlags;
Expand Down Expand Up @@ -3738,6 +3757,7 @@ namespace ts {
if (index === 0) {
context.flags ^= NodeBuilderFlags.InInitialEntityName;
}

const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping);
identifier.symbol = symbol;

Expand Down
50 changes: 38 additions & 12 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ namespace ts {
}

function emitMethodSignature(node: MethodSignature) {
pushNameGenerationScope(node);
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emit(node.name);
Expand All @@ -1083,6 +1084,7 @@ namespace ts {
emitParameters(node, node.parameters);
emitTypeAnnotation(node.type);
writeSemicolon();
popNameGenerationScope(node);
}

function emitMethodDeclaration(node: MethodDeclaration) {
Expand Down Expand Up @@ -1110,15 +1112,18 @@ namespace ts {
}

function emitCallSignature(node: CallSignatureDeclaration) {
pushNameGenerationScope(node);
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
emitTypeParameters(node, node.typeParameters);
emitParameters(node, node.parameters);
emitTypeAnnotation(node.type);
writeSemicolon();
popNameGenerationScope(node);
}

function emitConstructSignature(node: ConstructSignatureDeclaration) {
pushNameGenerationScope(node);
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
writeKeyword("new");
Expand All @@ -1127,6 +1132,7 @@ namespace ts {
emitParameters(node, node.parameters);
emitTypeAnnotation(node.type);
writeSemicolon();
popNameGenerationScope(node);
}

function emitIndexSignature(node: IndexSignatureDeclaration) {
Expand Down Expand Up @@ -1159,12 +1165,14 @@ namespace ts {
}

function emitFunctionType(node: FunctionTypeNode) {
pushNameGenerationScope(node);
emitTypeParameters(node, node.typeParameters);
emitParametersForArrow(node, node.parameters);
writeSpace();
writePunctuation("=>");
writeSpace();
emit(node.type);
popNameGenerationScope(node);
}

function emitJSDocFunctionType(node: JSDocFunctionType) {
Expand All @@ -1191,6 +1199,7 @@ namespace ts {
}

function emitConstructorType(node: ConstructorTypeNode) {
pushNameGenerationScope(node);
writeKeyword("new");
writeSpace();
emitTypeParameters(node, node.typeParameters);
Expand All @@ -1199,6 +1208,7 @@ namespace ts {
writePunctuation("=>");
writeSpace();
emit(node.type);
popNameGenerationScope(node);
}

function emitTypeQuery(node: TypeQueryNode) {
Expand Down Expand Up @@ -3182,7 +3192,7 @@ namespace ts {
if (isGeneratedIdentifier(node)) {
return generateName(node);
}
else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent)) {
else if (isIdentifier(node) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) {
return idText(node);
}
else if (node.kind === SyntaxKind.StringLiteral && (<StringLiteral>node).textSourceNode) {
Expand Down Expand Up @@ -3356,7 +3366,7 @@ namespace ts {
if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) {
// Node names generate unique names based on their original node
// and are cached based on that node's id.
return generateNameCached(getNodeForGeneratedName(name));
return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags);
}
else {
// Auto, Loop, and Unique names are cached based on their unique
Expand All @@ -3366,9 +3376,9 @@ namespace ts {
}
}

function generateNameCached(node: Node) {
function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) {
const nodeId = getNodeId(node);
return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node));
return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags));
}

/**
Expand All @@ -3385,7 +3395,7 @@ namespace ts {
* Returns a value indicating whether a name is unique globally or within the current file.
*/
function isFileLevelUniqueName(name: string) {
return ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName);
return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true;
}

/**
Expand Down Expand Up @@ -3445,10 +3455,15 @@ namespace ts {
* makeUniqueName are guaranteed to never conflict.
* If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1'
*/
function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean): string {
function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string {
if (optimistic) {
if (checkFn(baseName)) {
generatedNames.set(baseName, true);
if (scoped) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything that goes in generatedNames is already reserved throughout the entire generated output, so scoping these doesn't seem to make any sense. Why was this needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scoped causes it to go into reservedNames instead of generatedNames; this allows, eg, T_1 to be reused in adjacent scopes.

reserveNameInNestedScopes(baseName);
}
else {
generatedNames.set(baseName, true);
}
return baseName;
}
}
Expand All @@ -3460,7 +3475,12 @@ namespace ts {
while (true) {
const generatedName = baseName + i;
if (checkFn(generatedName)) {
generatedNames.set(generatedName, true);
if (scoped) {
reserveNameInNestedScopes(generatedName);
}
else {
generatedNames.set(generatedName, true);
}
return generatedName;
}
i++;
Expand Down Expand Up @@ -3514,10 +3534,15 @@ namespace ts {
/**
* Generates a unique name from a node.
*/
function generateNameForNode(node: Node): string {
function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string {
switch (node.kind) {
case SyntaxKind.Identifier:
return makeUniqueName(getTextOfNode(node));
return makeUniqueName(
getTextOfNode(node),
isUniqueName,
!!(flags & GeneratedIdentifierFlags.Optimistic),
!!(flags & GeneratedIdentifierFlags.ReservedInNestedScopes)
);
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
return generateNameForModuleOrEnum(<ModuleDeclaration | EnumDeclaration>node);
Expand Down Expand Up @@ -3552,7 +3577,8 @@ namespace ts {
return makeUniqueName(
idText(name),
(name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName,
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic)
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic),
!!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)
);
}

Expand All @@ -3572,7 +3598,7 @@ namespace ts {
// if "node" is a different generated name (having a different
// "autoGenerateId"), use it and stop traversing.
if (isIdentifier(node)
&& node.autoGenerateFlags === GeneratedIdentifierFlags.Node
&& !!(node.autoGenerateFlags & GeneratedIdentifierFlags.Node)
&& node.autoGenerateId !== autoGenerateId) {
break;
}
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,11 @@ namespace ts {
}

/** Create a unique name generated for a node. */
export function getGeneratedNameForNode(node: Node): Identifier {
const name = createIdentifier("");
name.autoGenerateFlags = GeneratedIdentifierFlags.Node;
export function getGeneratedNameForNode(node: Node): Identifier;
/* @internal */ export function getGeneratedNameForNode(node: Node, flags: GeneratedIdentifierFlags): Identifier; // tslint:disable-line unified-signatures
export function getGeneratedNameForNode(node: Node, flags?: GeneratedIdentifierFlags): Identifier {
const name = createIdentifier(isIdentifier(node) ? idText(node) : "");
name.autoGenerateFlags = GeneratedIdentifierFlags.Node | flags;
name.autoGenerateId = nextAutoGenerateId;
name.original = node;
nextAutoGenerateId++;
Expand Down Expand Up @@ -4228,6 +4230,7 @@ namespace ts {
switch (member.kind) {
case SyntaxKind.TypeQuery:
case SyntaxKind.TypeOperator:
case SyntaxKind.InferType:
return createParenthesizedType(member);
}
return parenthesizeElementTypeMember(member);
Expand Down
9 changes: 8 additions & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ namespace ts {
return result.diagnostics;
}

const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | NodeBuilderFlags.UseTypeOfFunction | NodeBuilderFlags.UseStructuralFallback | NodeBuilderFlags.AllowEmptyTuple;
const declarationEmitNodeBuilderFlags =
NodeBuilderFlags.MultilineObjectLiterals |
TypeFormatFlags.WriteClassExpressionAsTypeLiteral |
NodeBuilderFlags.UseTypeOfFunction |
NodeBuilderFlags.UseStructuralFallback |
NodeBuilderFlags.AllowEmptyTuple |
NodeBuilderFlags.GenerateNamesForShadowedTypeParams;

/**
* Transforms a ts file into a .d.ts file
Expand Down Expand Up @@ -95,6 +101,7 @@ namespace ts {
}

function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) {
if (symbol.flags & SymbolFlags.TypeParameter) return;
handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true));
recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning));
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3031,7 +3031,7 @@ namespace ts {
// Options
NoTruncation = 1 << 0, // Don't truncate result
WriteArrayAsGenericType = 1 << 1, // Write Array<T> instead T[]
// empty space
GenerateNamesForShadowedTypeParams = 1 << 2, // When a type parameter T is shadowing another T, generate a name for it so it can still be referenced
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need a flag?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't wanna bother renaming the conflicts in quickinfo and signature help, at least in my opinion.

UseStructuralFallback = 1 << 3, // When an alias cannot be named by its symbol, rather than report an error, fallback to a structural printout if possible
// empty space
WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,7 @@ declare namespace ts {
None = 0,
NoTruncation = 1,
WriteArrayAsGenericType = 2,
GenerateNamesForShadowedTypeParams = 4,
UseStructuralFallback = 8,
WriteTypeArgumentsOfSignature = 32,
UseFullyQualifiedType = 64,
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,7 @@ declare namespace ts {
None = 0,
NoTruncation = 1,
WriteArrayAsGenericType = 2,
GenerateNamesForShadowedTypeParams = 4,
UseStructuralFallback = 8,
WriteTypeArgumentsOfSignature = 32,
UseFullyQualifiedType = 64,
Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/conditionalTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -927,32 +927,32 @@ type T52 = IsNever<any>; // false
>IsNever : IsNever<T>

function f22<T>(x: T extends (infer U)[] ? U[] : never) {
>f22 : <T>(x: T extends infer U[] ? U[] : never) => void
>f22 : <T>(x: T extends (infer U)[] ? U[] : never) => void
>T : T
>x : T extends infer U[] ? U[] : never
>x : T extends (infer U)[] ? U[] : never
>T : T
>U : U
>U : U

let e = x[0]; // {}
>e : {}
>x[0] : {}
>x : T extends infer U[] ? U[] : never
>x : T extends (infer U)[] ? U[] : never
>0 : 0
}

function f23<T extends string[]>(x: T extends (infer U)[] ? U[] : never) {
>f23 : <T extends string[]>(x: T extends infer U[] ? U[] : never) => void
>f23 : <T extends string[]>(x: T extends (infer U)[] ? U[] : never) => void
>T : T
>x : T extends infer U[] ? U[] : never
>x : T extends (infer U)[] ? U[] : never
>T : T
>U : U
>U : U

let e = x[0]; // string
>e : string
>x[0] : string
>x : T extends infer U[] ? U[] : never
>x : T extends (infer U)[] ? U[] : never
>0 : 0
}

Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/declarationEmitNestedGenerics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [declarationEmitNestedGenerics.ts]
function f<T>(p: T) {
let g: <T>(x: T) => typeof p = null as any;
return g;
}

function g<T>(x: T) {
let y: typeof x extends (infer T)[] ? T : typeof x = null as any;
return y;
}

//// [declarationEmitNestedGenerics.js]
function f(p) {
var g = null;
return g;
}
function g(x) {
var y = null;
return y;
}


//// [declarationEmitNestedGenerics.d.ts]
declare function f<T>(p: T): <T_1>(x: T_1) => T;
declare function g<T>(x: T): T extends (infer T_1)[] ? T_1 : T;
Loading