diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 14c05d93246..4f0c732c4b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2180,54 +2180,105 @@ namespace ts { writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) ? "any" : (type).intrinsicName); + return; } - else if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + + if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { if (inObjectTypeLiteral) { writer.reportInaccessibleThisError(); } writer.writeKeyword("this"); + return; } - else if (getObjectFlags(type) & ObjectFlags.Reference) { + + if (getObjectFlags(type) & ObjectFlags.Reference) { writeTypeReference(type, nextFlags); + return; } - else if (type.flags & TypeFlags.EnumLiteral) { + + if (type.flags & TypeFlags.EnumLiteral) { buildSymbolDisplay(getParentOfSymbol(type.symbol), writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); writePunctuation(writer, SyntaxKind.DotToken); appendSymbolNameOnly(type.symbol, writer); + return; } - else if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.Enum | TypeFlags.TypeParameter)) { + if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.Enum | TypeFlags.TypeParameter)) { // The specified symbol flags need to be reinterpreted as type flags buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); + return; } - else if (!(flags & TypeFormatFlags.InTypeAlias) && ((getObjectFlags(type) & ObjectFlags.Anonymous && !(type).target) || type.flags & TypeFlags.UnionOrIntersection) && type.aliasSymbol && + + if (!(flags & TypeFormatFlags.InTypeAlias) && + (getObjectFlags(type) & ObjectFlags.Anonymous || type.flags & TypeFlags.UnionOrIntersection) && + type.aliasSymbol && isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { - // We emit inferred type as type-alias at the current localtion if all the following is true - // the input type is has alias symbol that is accessible - // the input type is a union, intersection or anonymous type that is fully instantiated (if not we want to keep dive into) - // e.g.: export type Bar = () => [X, Y]; - // export type Foo = Bar; - // export const y = (x: Foo) => 1 // we want to emit as ...x: () => [any, string]) + // We emit inferred type as type-alias if type is not in type-alias declaration, existed accessible alias-symbol, type is anonymous or union or intersection. + // However, if the type is an anonymous type with type arguments, we need to perform additional check. + + // 1) No type arguments, just emit type-alias as is + // 2) Existed type arguments, check if the type arguments full fill all type parameters of the alias-symbol by + // checking whether the target's aliasTypeArguments has the same size as type's aliasTypeArguments: + // i.e + // type Foo = { + // foo(): Foo + // }; + // function foo() { + // return {} as Foo; + // } + // Should be emitted as + // declare type Foo = { + // foo(): Foo; + // }; + // declare function foo(): Foo; + const typeArguments = type.aliasTypeArguments; - writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, typeArguments ? typeArguments.length : 0, nextFlags); + if (!(type).target) { + // The given type has no target type then just write out its alias and tyep argument. + writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, typeArguments ? typeArguments.length : 0, nextFlags); + return; + + } + else { + // The given type has target type. Then check hat the type contains same number of type arguments as its target type indicating that + // the given type has instantiate all type parameter. Then just emit type-alias using the current type argument. + // i.e + // type Foo = { foo(): Foo } + // function bar() { return {} as Foo; + // should be emitted as + // declare type Foo = { foo(): Foo; } + // declare function bar(): Foo + if (typeArguments && typeArguments.length === (type).target.aliasTypeArguments.length) { + writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, typeArguments.length, nextFlags); + return; + } + // Otherwise type-alias only partially full fill type parameter (as below example), we will want to serialize the type-alias + // export type Bar = () => [X, Y]; + // export type Foo = Bar; + // export const y = (x: Foo) => 1 // this should be emit as "export declare const y: (x: () => [any, string]) => number;" + } } - else if (type.flags & TypeFlags.UnionOrIntersection) { + + if (type.flags & TypeFlags.UnionOrIntersection) { writeUnionOrIntersectionType(type, nextFlags); + return; } - else if (getObjectFlags(type) & ObjectFlags.Anonymous) { + if (getObjectFlags(type) & ObjectFlags.Anonymous) { writeAnonymousType(type, nextFlags); + return; } - else if (type.flags & TypeFlags.StringOrNumberLiteral) { + + if (type.flags & TypeFlags.StringOrNumberLiteral) { writer.writeStringLiteral(literalTypeToString(type)); + return; } - else { - // Should never get here - // { ... } - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writeSpace(writer); - writePunctuation(writer, SyntaxKind.DotDotDotToken); - writeSpace(writer); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - } + + // Should never get here + // { ... } + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writeSpace(writer); + writePunctuation(writer, SyntaxKind.DotDotDotToken); + writeSpace(writer); + writePunctuation(writer, SyntaxKind.CloseBraceToken); } function writeTypeList(types: Type[], delimiter: SyntaxKind) { @@ -2330,7 +2381,9 @@ namespace ts { else if (contains(symbolStack, symbol)) { // If type is an anonymous type literal in a type alias declaration, use type alias name const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { + // We only want to use type-alias here if the typeAlias is not a generic one. (i.e it doesn't have a target type) + // If it is a generic type-alias just write out "any" + if (typeAlias && !(type).target) { // The specified symbol flags need to be reinterpreted as type flags buildSymbolDisplay(typeAlias, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, flags); }