diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5295df7b8b..a7ca6769082 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2279,7 +2279,7 @@ namespace ts { } function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string { - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.ignoreErrors); + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.ignoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); Debug.assert(typeNode !== undefined, "should always get typenode?"); const options = { removeComments: true }; const writer = createTextWriter(""); @@ -2360,10 +2360,8 @@ namespace ts { } function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { - const inElementType = context.flags & NodeBuilderFlags.InElementType; - const inFirstTypeArgument = context.flags & NodeBuilderFlags.InFirstTypeArgument; const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; - context.flags &= ~(NodeBuilderFlags.StateClearingFlags); + context.flags &= ~(NodeBuilderFlags.InTypeAlias); if (!type) { context.encounteredError = true; @@ -2443,15 +2441,15 @@ namespace ts { if (!inTypeAlias && type.aliasSymbol && isSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { const name = symbolToTypeReferenceName(type.aliasSymbol); - const typeArgumentNodes = toTypeArgumentNodes(type.aliasTypeArguments, context); + const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments, context); return createTypeReferenceNode(name, typeArgumentNodes); } if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; - const typeNodes = types && mapToTypeNodeArray(types, context, /*addInElementTypeFlag*/ true, /*addInFirstTypeArgumentFlag*/ false); + const typeNodes = types && mapToTypeNodeArray(types, context); if (typeNodes && typeNodes.length > 0) { const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); - return inElementType ? createParenthesizedType(unionOrIntersectionTypeNode) : unionOrIntersectionTypeNode; + return unionOrIntersectionTypeNode; } else { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { @@ -2467,15 +2465,11 @@ namespace ts { } if (type.flags & TypeFlags.Index) { const indexedType = (type).type; - context.flags |= NodeBuilderFlags.InElementType; const indexTypeNode = typeToTypeNodeHelper(indexedType, context); - Debug.assert(!(context.flags & NodeBuilderFlags.InElementType)); return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - context.flags |= NodeBuilderFlags.InElementType; const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); - Debug.assert(!(context.flags & NodeBuilderFlags.InElementType)); const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } @@ -2561,17 +2555,14 @@ namespace ts { if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const signature = resolved.callSignatures[0]; const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context); - return shouldAddParenthesisAroundFunctionType(signature, context) ? - createParenthesizedType(signatureNode) : - signatureNode; + return signatureNode; } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { const signature = resolved.constructSignatures[0]; const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context); - return shouldAddParenthesisAroundFunctionType(signature, context) ? - createParenthesizedType(signatureNode) : - signatureNode; + return signatureNode; } } @@ -2583,19 +2574,6 @@ namespace ts { return setEmitFlags(typeLiteralNode, EmitFlags.SingleLine); } - function shouldAddParenthesisAroundFunctionType(callSignature: Signature, context: NodeBuilderContext) { - if (inElementType) { - return true; - } - else if (inFirstTypeArgument) { - // Add parenthesis around function type for the first type argument to avoid ambiguity - const typeParameters = callSignature.target && (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature) ? - callSignature.target.typeParameters : callSignature.typeParameters; - return typeParameters && typeParameters.length !== 0; - } - return false; - } - function createTypeQueryNodeFromSymbol(symbol: Symbol, symbolFlags: SymbolFlags) { const entityName = symbolToName(symbol, context, symbolFlags, /*expectsIdentifier*/ false); return createTypeQueryNode(entityName); @@ -2615,15 +2593,13 @@ namespace ts { return createTypeReferenceNode("Array", [typeArgumentNode]); } - context.flags |= NodeBuilderFlags.InElementType; const elementType = typeToTypeNodeHelper(typeArguments[0], context); - Debug.assert(!(context.flags & NodeBuilderFlags.InElementType)); - return createArrayTypeNode(elementType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { if (typeArguments.length > 0) { - const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)), context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ false); + const slice = typeArguments.slice(0, getTypeReferenceArity(type)); + const tupleConstituentNodes = slice && mapToTypeNodeArray(slice, context); if (tupleConstituentNodes && tupleConstituentNodes.length > 0) { return createTupleTypeNode(tupleConstituentNodes); } @@ -2649,7 +2625,8 @@ namespace ts { // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const typeArgumentNodes = createNodeArray(toTypeArgumentNodes(typeArguments.slice(start, i), context)); + const slice = typeArguments.slice(start, i); + const typeArgumentNodes = slice && createNodeArray(mapToTypeNodeArray(slice, context)); const namePart = symbolToTypeReferenceName(parent); (namePart.kind === SyntaxKind.Identifier ? namePart : namePart.right).typeArguments = typeArgumentNodes; @@ -2680,7 +2657,7 @@ namespace ts { if (some(typeArguments)) { const typeParameterCount = (type.target.typeParameters || emptyArray).length; const slice = typeArguments && typeArguments.slice(i, typeParameterCount); - typeArgumentNodes = toTypeArgumentNodes(slice, context); + typeArgumentNodes = slice && mapToTypeNodeArray(slice, context); } if (typeArgumentNodes) { @@ -2763,28 +2740,17 @@ namespace ts { } } - function mapToTypeNodeArray(types: Type[], context: NodeBuilderContext, addInElementTypeFlag: boolean, addInFirstTypeArgumentFlag: boolean): TypeNode[] { + function mapToTypeNodeArray(types: Type[], context: NodeBuilderContext): TypeNode[] { const result = []; - Debug.assert(!(context.flags & NodeBuilderFlags.InElementType), "should be unset at the beginning of the helper"); for (let i = 0; i < types.length; ++i) { const type = types[i]; - if (addInElementTypeFlag) { - context.flags |= NodeBuilderFlags.InElementType; - } - if (i === 0 && addInFirstTypeArgumentFlag) { - context.flags |= NodeBuilderFlags.InFirstTypeArgument; - } const typeNode = typeToTypeNodeHelper(type, context); if (typeNode) { result.push(typeNode); } } - Debug.assert(!(context.flags & NodeBuilderFlags.InElementType), "should be unset at the end of the helper"); - return result; - } - function toTypeArgumentNodes(typeArguments: Type[], context: NodeBuilderContext) { - return typeArguments && mapToTypeNodeArray(typeArguments, context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ true); + return result; } function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration { @@ -2909,7 +2875,7 @@ namespace ts { Debug.assert(chain && 0 <= index && index < chain.length); const symbol = chain[index]; let typeParameterNodes: TypeNode[] | undefined; - if (index > 0) { + if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index > 0) { const parentSymbol = chain[index - 1]; let typeParameters: TypeParameter[]; if (getCheckFlags(symbol) & CheckFlags.Instantiated) { @@ -2921,11 +2887,9 @@ namespace ts { typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); } } + if (typeParameters && typeParameters.length > 0) { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowTypeParameterInQualifiedName)) { - context.encounteredError = true; - } - typeParameterNodes = toTypeArgumentNodes(typeParameters, context); + typeParameterNodes = mapToTypeNodeArray(typeParameters, context); } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 094ad8cb8cf..18e71f94ee3 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -506,7 +506,7 @@ namespace ts { export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: TypeNode[] | undefined) { const node = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; node.typeName = asName(typeName); - node.typeArguments = asNodeArray(typeArguments); + node.typeArguments = typeArguments && parenthesizeTypeParameters(typeArguments); return node; } @@ -559,7 +559,7 @@ namespace ts { export function createArrayTypeNode(elementType: TypeNode) { const node = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; - node.elementType = elementType; + node.elementType = parenthesizeElementTypeMember(elementType); return node; } @@ -599,7 +599,7 @@ namespace ts { export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]) { const node = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; - node.types = createNodeArray(types); + node.types = parenthesizeElementTypeMembers(types); return node; } @@ -628,7 +628,7 @@ namespace ts { export function createTypeOperatorNode(type: TypeNode) { const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; node.operator = SyntaxKind.KeyOfKeyword; - node.type = type; + node.type = parenthesizeElementTypeMember(type); return node; } @@ -638,7 +638,7 @@ namespace ts { export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { const node = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; - node.objectType = objectType; + node.objectType = parenthesizeElementTypeMember(objectType); node.indexType = indexType; return node; } @@ -3595,7 +3595,7 @@ namespace ts { return expression; } - function parenthesizeElementTypeMember(member: TypeNode) { + export function parenthesizeElementTypeMember(member: TypeNode) { switch (member.kind) { case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: @@ -3603,11 +3603,26 @@ namespace ts { case SyntaxKind.ConstructorType: return createParenthesizedType(member); } + return member; } - function parenthesizeElementTypeMembers(members: NodeArray) { + + export function parenthesizeElementTypeMembers(members: TypeNode[]) { + // TODO: does this lose `originalNode` ptr? return createNodeArray(members.map(parenthesizeElementTypeMember)); } + export function parenthesizeTypeParameters(typeParameters: TypeNode[]) { + if (typeParameters && typeParameters.length > 0) { + const nodeArray = createNodeArray(typeParameters); + const firstEntry = nodeArray[0]; + if (isFunctionOrConstructor(firstEntry) && firstEntry.typeParameters) { + nodeArray[0] = createParenthesizedType(firstEntry); + } + + return nodeArray; + } + } + /** * Clones a series of not-emitted expressions with a new inner expression. * diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e7e6f9ad1af..cf204d158a1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2590,25 +2590,20 @@ namespace ts { WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) SuppressAnyReturnType = 1 << 8, // If the return type is any-like, don't offer a return type. + WriteTypeParametersInQualifiedName = 1 << 9, // Error handling AllowThisInObjectLiteral = 1 << 10, AllowQualifedNameInPlaceOfIdentifier = 1 << 11, - AllowTypeParameterInQualifiedName = 1 << 12, AllowAnonymousIdentifier = 1 << 13, AllowEmptyUnionOrIntersection = 1 << 14, AllowEmptyTuple = 1 << 15, - ignoreErrors = AllowThisInObjectLiteral | AllowQualifedNameInPlaceOfIdentifier | AllowTypeParameterInQualifiedName | AllowAnonymousIdentifier | AllowEmptyUnionOrIntersection | AllowEmptyTuple, + ignoreErrors = AllowThisInObjectLiteral | AllowQualifedNameInPlaceOfIdentifier | AllowAnonymousIdentifier | AllowEmptyUnionOrIntersection | AllowEmptyTuple, // State inObjectTypeLiteral = 1 << 20, - InElementType = 1 << 21, // Writing an array or union element type - InFirstTypeArgument = 1 << 22, // Writing first type argument of the instantiated type InTypeAlias = 1 << 23, // Writing type in type alias declaration - - /** Flags that should not be passed on to sub-nodes of the current node being built. */ - StateClearingFlags = InElementType | InFirstTypeArgument | InTypeAlias } export interface SymbolDisplayBuilder { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 825465085c8..80daf383639 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -881,6 +881,16 @@ namespace ts { return false; } + export function isFunctionOrConstructor(node: Node): node is FunctionTypeNode | ConstructorTypeNode { + switch (node.kind) { + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return true; + } + + return false; + } + export function introducesArgumentsExoticObject(node: Node) { switch (node.kind) { case SyntaxKind.MethodDeclaration: