diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e24c707d2d7..5af3c9e9c51 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24172,41 +24172,10 @@ namespace ts { switch (node.kind) { case SyntaxKind.IntersectionType: case SyntaxKind.UnionType: - let commonEntityName: EntityName | undefined; - for (let typeNode of (node).types) { - while (typeNode.kind === SyntaxKind.ParenthesizedType) { - typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be - } - if (typeNode.kind === SyntaxKind.NeverKeyword) { - continue; // Always elide `never` from the union/intersection if possible - } - if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { - continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks - } - const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); - if (!individualEntityName) { - // Individual is something like string number - // So it would be serialized to either that type or object - // Safe to return here - return undefined; - } + return getEntityNameForDecoratorMetadataFromTypeList((node).types); - if (commonEntityName) { - // Note this is in sync with the transformation that happens for type node. - // Keep this in sync with serializeUnionOrIntersectionType - // Verify if they refer to same entity and is identifier - // return undefined if they dont match because we would emit object - if (!isIdentifier(commonEntityName) || - !isIdentifier(individualEntityName) || - commonEntityName.escapedText !== individualEntityName.escapedText) { - return undefined; - } - } - else { - commonEntityName = individualEntityName; - } - } - return commonEntityName; + case SyntaxKind.ConditionalType: + return getEntityNameForDecoratorMetadataFromTypeList([(node).trueType, (node).falseType]); case SyntaxKind.ParenthesizedType: return getEntityNameForDecoratorMetadata((node).type); @@ -24217,6 +24186,44 @@ namespace ts { } } + function getEntityNameForDecoratorMetadataFromTypeList(types: ReadonlyArray): EntityName | undefined { + let commonEntityName: EntityName | undefined; + for (let typeNode of types) { + while (typeNode.kind === SyntaxKind.ParenthesizedType) { + typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be + } + if (typeNode.kind === SyntaxKind.NeverKeyword) { + continue; // Always elide `never` from the union/intersection if possible + } + if (!strictNullChecks && (typeNode.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { + continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks + } + const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); + if (!individualEntityName) { + // Individual is something like string number + // So it would be serialized to either that type or object + // Safe to return here + return undefined; + } + + if (commonEntityName) { + // Note this is in sync with the transformation that happens for type node. + // Keep this in sync with serializeUnionOrIntersectionType + // Verify if they refer to same entity and is identifier + // return undefined if they dont match because we would emit object + if (!isIdentifier(commonEntityName) || + !isIdentifier(individualEntityName) || + commonEntityName.escapedText !== individualEntityName.escapedText) { + return undefined; + } + } + else { + commonEntityName = individualEntityName; + } + } + return commonEntityName; + } + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { const typeNode = getEffectiveTypeAnnotationNode(node); return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 5917a365ddf..700c0c8af25 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1928,7 +1928,10 @@ namespace ts { case SyntaxKind.IntersectionType: case SyntaxKind.UnionType: - return serializeUnionOrIntersectionType(node); + return serializeTypeList((node).types); + + case SyntaxKind.ConditionalType: + return serializeTypeList([(node).trueType, (node).falseType]); case SyntaxKind.TypeQuery: case SyntaxKind.TypeOperator: @@ -1941,6 +1944,7 @@ namespace ts { case SyntaxKind.ImportType: break; + default: return Debug.failBadSyntaxKind(node); } @@ -1948,11 +1952,11 @@ namespace ts { return createIdentifier("Object"); } - function serializeUnionOrIntersectionType(node: UnionOrIntersectionTypeNode): SerializedTypeNode { + function serializeTypeList(types: ReadonlyArray): SerializedTypeNode { // Note when updating logic here also update getEntityNameForDecoratorMetadata // so that aliases can be marked as referenced let serializedUnion: SerializedTypeNode | undefined; - for (let typeNode of node.types) { + for (let typeNode of types) { while (typeNode.kind === SyntaxKind.ParenthesizedType) { typeNode = (typeNode as ParenthesizedTypeNode).type; // Skip parens if need be } @@ -1998,6 +2002,11 @@ namespace ts { const kind = resolver.getTypeReferenceSerializationKind(node.typeName, currentNameScope || currentLexicalScope); switch (kind) { case TypeReferenceSerializationKind.Unknown: + // From conditional type type reference that cannot be resolved is Similar to any or unknown + if (findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && (n.parent.trueType === n || n.parent.falseType === n))) { + return createIdentifier("Object"); + } + const serialized = serializeEntityNameAsExpressionFallback(node.typeName); const temp = createTempVariable(hoistVariableDeclaration); return createConditional( diff --git a/tests/baselines/reference/decoratorMetadataConditionalType.js b/tests/baselines/reference/decoratorMetadataConditionalType.js new file mode 100644 index 00000000000..e94c56d35f1 --- /dev/null +++ b/tests/baselines/reference/decoratorMetadataConditionalType.js @@ -0,0 +1,39 @@ +//// [decoratorMetadataConditionalType.ts] +declare function d(): PropertyDecorator; +abstract class BaseEntity { + @d() + public attributes: T extends { attributes: infer A } ? A : undefined; +} +class C { + @d() + x: number extends string ? false : true; +} + +//// [decoratorMetadataConditionalType.js] +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var BaseEntity = /** @class */ (function () { + function BaseEntity() { + } + __decorate([ + d(), + __metadata("design:type", Object) + ], BaseEntity.prototype, "attributes"); + return BaseEntity; +}()); +var C = /** @class */ (function () { + function C() { + } + __decorate([ + d(), + __metadata("design:type", Boolean) + ], C.prototype, "x"); + return C; +}()); diff --git a/tests/baselines/reference/decoratorMetadataConditionalType.symbols b/tests/baselines/reference/decoratorMetadataConditionalType.symbols new file mode 100644 index 00000000000..251a2bd77ba --- /dev/null +++ b/tests/baselines/reference/decoratorMetadataConditionalType.symbols @@ -0,0 +1,28 @@ +=== tests/cases/compiler/decoratorMetadataConditionalType.ts === +declare function d(): PropertyDecorator; +>d : Symbol(d, Decl(decoratorMetadataConditionalType.ts, 0, 0)) +>PropertyDecorator : Symbol(PropertyDecorator, Decl(lib.es5.d.ts, --, --)) + +abstract class BaseEntity { +>BaseEntity : Symbol(BaseEntity, Decl(decoratorMetadataConditionalType.ts, 0, 40)) +>T : Symbol(T, Decl(decoratorMetadataConditionalType.ts, 1, 26)) + + @d() +>d : Symbol(d, Decl(decoratorMetadataConditionalType.ts, 0, 0)) + + public attributes: T extends { attributes: infer A } ? A : undefined; +>attributes : Symbol(BaseEntity.attributes, Decl(decoratorMetadataConditionalType.ts, 1, 30)) +>T : Symbol(T, Decl(decoratorMetadataConditionalType.ts, 1, 26)) +>attributes : Symbol(attributes, Decl(decoratorMetadataConditionalType.ts, 3, 34)) +>A : Symbol(A, Decl(decoratorMetadataConditionalType.ts, 3, 52)) +>A : Symbol(A, Decl(decoratorMetadataConditionalType.ts, 3, 52)) +} +class C { +>C : Symbol(C, Decl(decoratorMetadataConditionalType.ts, 4, 1)) + + @d() +>d : Symbol(d, Decl(decoratorMetadataConditionalType.ts, 0, 0)) + + x: number extends string ? false : true; +>x : Symbol(C.x, Decl(decoratorMetadataConditionalType.ts, 5, 9)) +} diff --git a/tests/baselines/reference/decoratorMetadataConditionalType.types b/tests/baselines/reference/decoratorMetadataConditionalType.types new file mode 100644 index 00000000000..f06b00ef5c0 --- /dev/null +++ b/tests/baselines/reference/decoratorMetadataConditionalType.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/decoratorMetadataConditionalType.ts === +declare function d(): PropertyDecorator; +>d : () => PropertyDecorator + +abstract class BaseEntity { +>BaseEntity : BaseEntity + + @d() +>d() : PropertyDecorator +>d : () => PropertyDecorator + + public attributes: T extends { attributes: infer A } ? A : undefined; +>attributes : T extends { attributes: infer A; } ? A : undefined +>attributes : A +} +class C { +>C : C + + @d() +>d() : PropertyDecorator +>d : () => PropertyDecorator + + x: number extends string ? false : true; +>x : true +>false : false +>true : true +} diff --git a/tests/cases/compiler/decoratorMetadataConditionalType.ts b/tests/cases/compiler/decoratorMetadataConditionalType.ts new file mode 100644 index 00000000000..6c28586b1cb --- /dev/null +++ b/tests/cases/compiler/decoratorMetadataConditionalType.ts @@ -0,0 +1,12 @@ +// @experimentalDecorators: true +// @emitDecoratorMetadata: true + +declare function d(): PropertyDecorator; +abstract class BaseEntity { + @d() + public attributes: T extends { attributes: infer A } ? A : undefined; +} +class C { + @d() + x: number extends string ? false : true; +} \ No newline at end of file