From 6149b414693aa7b55371afaa1a45e6db23af7932 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 10 May 2018 11:16:27 -0700 Subject: [PATCH] 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 --- src/compiler/checker.ts | 28 +++++++++-- src/compiler/emitter.ts | 50 ++++++++++++++----- src/compiler/factory.ts | 9 ++-- src/compiler/transformers/declarations.ts | 9 +++- src/compiler/types.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/conditionalTypes1.types | 12 ++--- .../declarationEmitNestedGenerics.js | 25 ++++++++++ .../declarationEmitNestedGenerics.symbols | 34 +++++++++++++ .../declarationEmitNestedGenerics.types | 38 ++++++++++++++ .../compiler/declarationEmitNestedGenerics.ts | 10 ++++ 12 files changed, 192 insertions(+), 27 deletions(-) create mode 100644 tests/baselines/reference/declarationEmitNestedGenerics.js create mode 100644 tests/baselines/reference/declarationEmitNestedGenerics.symbols create mode 100644 tests/baselines/reference/declarationEmitNestedGenerics.types create mode 100644 tests/cases/compiler/declarationEmitNestedGenerics.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fa1efdb7bb9..edefd6bf9dd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2746,7 +2746,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) { @@ -3118,7 +3118,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. @@ -3563,10 +3571,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; @@ -3799,6 +3818,7 @@ namespace ts { if (index === 0) { context.flags ^= NodeBuilderFlags.InInitialEntityName; } + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); identifier.symbol = symbol; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b4ff2617a0d..f4d1d85456d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1132,6 +1132,7 @@ namespace ts { } function emitMethodSignature(node: MethodSignature) { + pushNameGenerationScope(node); emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); emit(node.name); @@ -1140,6 +1141,7 @@ namespace ts { emitParameters(node, node.parameters); emitTypeAnnotation(node.type); writeSemicolon(); + popNameGenerationScope(node); } function emitMethodDeclaration(node: MethodDeclaration) { @@ -1167,15 +1169,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"); @@ -1184,6 +1189,7 @@ namespace ts { emitParameters(node, node.parameters); emitTypeAnnotation(node.type); writeSemicolon(); + popNameGenerationScope(node); } function emitIndexSignature(node: IndexSignatureDeclaration) { @@ -1216,12 +1222,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) { @@ -1248,6 +1256,7 @@ namespace ts { } function emitConstructorType(node: ConstructorTypeNode) { + pushNameGenerationScope(node); writeKeyword("new"); writeSpace(); emitTypeParameters(node, node.typeParameters); @@ -1256,6 +1265,7 @@ namespace ts { writePunctuation("=>"); writeSpace(); emit(node.type); + popNameGenerationScope(node); } function emitTypeQuery(node: TypeQueryNode) { @@ -3241,7 +3251,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 && (node).textSourceNode) { @@ -3415,7 +3425,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 @@ -3425,9 +3435,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)); } /** @@ -3444,7 +3454,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; } /** @@ -3504,10 +3514,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) { + reserveNameInNestedScopes(baseName); + } + else { + generatedNames.set(baseName, true); + } return baseName; } } @@ -3519,7 +3534,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++; @@ -3573,10 +3593,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(node); @@ -3611,7 +3636,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) ); } @@ -3631,7 +3657,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; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index bcbf7a1bed1..f8d96af5702 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -192,9 +192,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++; @@ -4246,6 +4248,7 @@ namespace ts { switch (member.kind) { case SyntaxKind.TypeQuery: case SyntaxKind.TypeOperator: + case SyntaxKind.InferType: return createParenthesizedType(member); } return parenthesizeElementTypeMember(member); diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 203620864cc..1a404d8262a 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -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 @@ -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)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f8986ec40c2..2933d7b5047 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3074,7 +3074,7 @@ namespace ts { // Options NoTruncation = 1 << 0, // Don't truncate result WriteArrayAsGenericType = 1 << 1, // Write Array 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 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 diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 96749475081..c5956abdb83 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1883,6 +1883,7 @@ declare namespace ts { None = 0, NoTruncation = 1, WriteArrayAsGenericType = 2, + GenerateNamesForShadowedTypeParams = 4, UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4203c56bf95..052b5c2e196 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1883,6 +1883,7 @@ declare namespace ts { None = 0, NoTruncation = 1, WriteArrayAsGenericType = 2, + GenerateNamesForShadowedTypeParams = 4, UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, diff --git a/tests/baselines/reference/conditionalTypes1.types b/tests/baselines/reference/conditionalTypes1.types index 90d2703d034..699808c1f78 100644 --- a/tests/baselines/reference/conditionalTypes1.types +++ b/tests/baselines/reference/conditionalTypes1.types @@ -927,9 +927,9 @@ type T52 = IsNever; // false >IsNever : IsNever function f22(x: T extends (infer U)[] ? U[] : never) { ->f22 : (x: T extends infer U[] ? U[] : never) => void +>f22 : (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 @@ -937,14 +937,14 @@ function f22(x: T extends (infer U)[] ? U[] : never) { 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(x: T extends (infer U)[] ? U[] : never) { ->f23 : (x: T extends infer U[] ? U[] : never) => void +>f23 : (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 @@ -952,7 +952,7 @@ function f23(x: T extends (infer U)[] ? U[] : never) { 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 } diff --git a/tests/baselines/reference/declarationEmitNestedGenerics.js b/tests/baselines/reference/declarationEmitNestedGenerics.js new file mode 100644 index 00000000000..04d74e3f8db --- /dev/null +++ b/tests/baselines/reference/declarationEmitNestedGenerics.js @@ -0,0 +1,25 @@ +//// [declarationEmitNestedGenerics.ts] +function f(p: T) { + let g: (x: T) => typeof p = null as any; + return g; +} + +function g(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(p: T): (x: T_1) => T; +declare function g(x: T): T extends (infer T_1)[] ? T_1 : T; diff --git a/tests/baselines/reference/declarationEmitNestedGenerics.symbols b/tests/baselines/reference/declarationEmitNestedGenerics.symbols new file mode 100644 index 00000000000..b2f657e6f08 --- /dev/null +++ b/tests/baselines/reference/declarationEmitNestedGenerics.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/declarationEmitNestedGenerics.ts === +function f(p: T) { +>f : Symbol(f, Decl(declarationEmitNestedGenerics.ts, 0, 0)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 0, 11)) +>p : Symbol(p, Decl(declarationEmitNestedGenerics.ts, 0, 14)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 0, 11)) + + let g: (x: T) => typeof p = null as any; +>g : Symbol(g, Decl(declarationEmitNestedGenerics.ts, 1, 7)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 1, 12)) +>x : Symbol(x, Decl(declarationEmitNestedGenerics.ts, 1, 15)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 1, 12)) +>p : Symbol(p, Decl(declarationEmitNestedGenerics.ts, 0, 14)) + + return g; +>g : Symbol(g, Decl(declarationEmitNestedGenerics.ts, 1, 7)) +} + +function g(x: T) { +>g : Symbol(g, Decl(declarationEmitNestedGenerics.ts, 3, 1)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 5, 11)) +>x : Symbol(x, Decl(declarationEmitNestedGenerics.ts, 5, 14)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 5, 11)) + + let y: typeof x extends (infer T)[] ? T : typeof x = null as any; +>y : Symbol(y, Decl(declarationEmitNestedGenerics.ts, 6, 7)) +>x : Symbol(x, Decl(declarationEmitNestedGenerics.ts, 5, 14)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 6, 34)) +>T : Symbol(T, Decl(declarationEmitNestedGenerics.ts, 6, 34)) +>x : Symbol(x, Decl(declarationEmitNestedGenerics.ts, 5, 14)) + + return y; +>y : Symbol(y, Decl(declarationEmitNestedGenerics.ts, 6, 7)) +} diff --git a/tests/baselines/reference/declarationEmitNestedGenerics.types b/tests/baselines/reference/declarationEmitNestedGenerics.types new file mode 100644 index 00000000000..778ecd8290b --- /dev/null +++ b/tests/baselines/reference/declarationEmitNestedGenerics.types @@ -0,0 +1,38 @@ +=== tests/cases/compiler/declarationEmitNestedGenerics.ts === +function f(p: T) { +>f : (p: T) => (x: T) => T +>T : T +>p : T +>T : T + + let g: (x: T) => typeof p = null as any; +>g : (x: T) => T +>T : T +>x : T +>T : T +>p : T +>null as any : any +>null : null + + return g; +>g : (x: T) => T +} + +function g(x: T) { +>g : (x: T) => T extends (infer T)[] ? T : T +>T : T +>x : T +>T : T + + let y: typeof x extends (infer T)[] ? T : typeof x = null as any; +>y : T extends (infer T)[] ? T : T +>x : T +>T : T +>T : T +>x : T +>null as any : any +>null : null + + return y; +>y : T extends (infer T)[] ? T : T +} diff --git a/tests/cases/compiler/declarationEmitNestedGenerics.ts b/tests/cases/compiler/declarationEmitNestedGenerics.ts new file mode 100644 index 00000000000..9d7f953ab2d --- /dev/null +++ b/tests/cases/compiler/declarationEmitNestedGenerics.ts @@ -0,0 +1,10 @@ +// @declaration: true +function f(p: T) { + let g: (x: T) => typeof p = null as any; + return g; +} + +function g(x: T) { + let y: typeof x extends (infer T)[] ? T : typeof x = null as any; + return y; +} \ No newline at end of file