diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bd2425f8eb3..9babfdec339 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3474,8 +3474,10 @@ namespace ts { return emptyArray; } - // Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and - // maps primitive types and type parameters are to their apparent types. + /** + * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and + * maps primitive types and type parameters are to their apparent types. + */ function getSignaturesOfType(type: Type, kind: SignatureKind): Signature[] { return getSignaturesOfStructuredType(getApparentType(type), kind); } @@ -5090,30 +5092,24 @@ namespace ts { let result = Ternary.True; let saveErrorInfo = errorInfo; - // Because the "abstractness" of a class is the same across all construct signatures - // (internally we are checking the corresponding declaration), it is enough to perform - // the check and report an error once over all pairs of source and target construct signatures. - let sourceSig = sourceSignatures[0]; - // Note that in an extends-clause, targetSignatures is stripped, so the check never proceeds. - let targetSig = targetSignatures[0]; - if (sourceSig && targetSig) { - let sourceErasedSignature = getErasedSignature(sourceSig); - let targetErasedSignature = getErasedSignature(targetSig); - let sourceReturnType = sourceErasedSignature && getReturnTypeOfSignature(sourceErasedSignature); - let targetReturnType = targetErasedSignature && getReturnTypeOfSignature(targetErasedSignature); + if (kind === SignatureKind.Construct) { + // Only want to compare the construct signatures for abstractness guarantees. + + // Because the "abstractness" of a class is the same across all construct signatures + // (internally we are checking the corresponding declaration), it is enough to perform + // the check and report an error once over all pairs of source and target construct signatures. + // + // sourceSig and targetSig are (possibly) undefined. + // + // Note that in an extends-clause, targetSignatures is stripped, so the check never proceeds. + let sourceSig = sourceSignatures[0]; + let targetSig = targetSignatures[0]; - let sourceReturnDecl = sourceReturnType && sourceReturnType.symbol && getDeclarationOfKind(sourceReturnType.symbol, SyntaxKind.ClassDeclaration); - let targetReturnDecl = targetReturnType && targetReturnType.symbol && getDeclarationOfKind(targetReturnType.symbol, SyntaxKind.ClassDeclaration); - let sourceIsAbstract = sourceReturnDecl && sourceReturnDecl.flags & NodeFlags.Abstract; - let targetIsAbstract = targetReturnDecl && targetReturnDecl.flags & NodeFlags.Abstract; - - if (sourceIsAbstract && !targetIsAbstract) { - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); - } - return Ternary.False; + result &= abstractSignatureRelatedTo(source, sourceSig, target, targetSig); + if (result !== Ternary.True) { + return result; } } @@ -5137,6 +5133,40 @@ namespace ts { } } return result; + + function abstractSignatureRelatedTo(source: Type, sourceSig: Signature, target: Type, targetSig: Signature) { + if (sourceSig && targetSig) { + + let sourceDecl = source.symbol && getDeclarationOfKind(source.symbol, SyntaxKind.ClassDeclaration); + let targetDecl = target.symbol && getDeclarationOfKind(target.symbol, SyntaxKind.ClassDeclaration); + + if (!sourceDecl) { + // If the source object isn't itself a class declaration, it can be freely assigned, regardless + // of whether the constructed object is abstract or not. + return Ternary.True; + } + + let sourceErasedSignature = getErasedSignature(sourceSig); + let targetErasedSignature = getErasedSignature(targetSig); + + let sourceReturnType = sourceErasedSignature && getReturnTypeOfSignature(sourceErasedSignature); + let targetReturnType = targetErasedSignature && getReturnTypeOfSignature(targetErasedSignature); + + let sourceReturnDecl = sourceReturnType && sourceReturnType.symbol && getDeclarationOfKind(sourceReturnType.symbol, SyntaxKind.ClassDeclaration); + let targetReturnDecl = targetReturnType && targetReturnType.symbol && getDeclarationOfKind(targetReturnType.symbol, SyntaxKind.ClassDeclaration); + let sourceIsAbstract = sourceReturnDecl && sourceReturnDecl.flags & NodeFlags.Abstract; + let targetIsAbstract = targetReturnDecl && targetReturnDecl.flags & NodeFlags.Abstract; + + if (sourceIsAbstract && !(targetIsAbstract && targetDecl)) { + // if target isn't a class-declaration type, then it can be new'd, so we forbid the assignment. + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); + } + return Ternary.False; + } + } + return Ternary.True; + } } function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary { diff --git a/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.errors.txt b/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.errors.txt new file mode 100644 index 00000000000..c6c242396d8 --- /dev/null +++ b/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.errors.txt @@ -0,0 +1,19 @@ +tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts(7,1): error TS2322: Type 'typeof A' is not assignable to type 'new () => A'. + Cannot assign an abstract constructor type to a non-abstract constructor type. +tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts(8,1): error TS2322: Type 'string' is not assignable to type 'new () => A'. + + +==== tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts (2 errors) ==== + abstract class A { } + + // var AA: typeof A; + var AAA: new() => A; + + // AA = A; // okay + AAA = A; // error. + ~~~ +!!! error TS2322: Type 'typeof A' is not assignable to type 'new () => A'. +!!! error TS2322: Cannot assign an abstract constructor type to a non-abstract constructor type. + AAA = "asdf"; + ~~~ +!!! error TS2322: Type 'string' is not assignable to type 'new () => A'. \ No newline at end of file diff --git a/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.js b/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.js new file mode 100644 index 00000000000..3d8fd404afc --- /dev/null +++ b/tests/baselines/reference/classAbstractAssignabilityConstructorFunction.js @@ -0,0 +1,21 @@ +//// [classAbstractAssignabilityConstructorFunction.ts] +abstract class A { } + +// var AA: typeof A; +var AAA: new() => A; + +// AA = A; // okay +AAA = A; // error. +AAA = "asdf"; + +//// [classAbstractAssignabilityConstructorFunction.js] +var A = (function () { + function A() { + } + return A; +})(); +// var AA: typeof A; +var AAA; +// AA = A; // okay +AAA = A; // error. +AAA = "asdf"; diff --git a/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts b/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts new file mode 100644 index 00000000000..4a6f6cc3d61 --- /dev/null +++ b/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts @@ -0,0 +1,8 @@ +abstract class A { } + +// var AA: typeof A; +var AAA: new() => A; + +// AA = A; // okay +AAA = A; // error. +AAA = "asdf"; \ No newline at end of file