diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 07ec5023d5b..1ce5b51c66d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5889,16 +5889,20 @@ namespace ts { const sourceSignatures = getSignaturesOfType(source, kind); const targetSignatures = getSignaturesOfType(target, kind); - if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length && - isAbstractConstructorType(source) && !isAbstractConstructorType(target)) { - // An abstract constructor type is not assignable to a non-abstract constructor type - // as it would otherwise be possible to new an abstract class. Note that the assignablity - // check we perform for an extends clause excludes construct signatures from the target, - // so this check never proceeds. - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); + if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { + if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) { + // An abstract constructor type is not assignable to a non-abstract constructor type + // as it would otherwise be possible to new an abstract class. Note that the assignablity + // check we perform for an extends clause excludes construct signatures from the target, + // so this check never proceeds. + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); + } + return Ternary.False; + } + if (!constructorRelatedTo(sourceSignatures[0], targetSignatures[0], reportErrors)) { + return Ternary.False; } - return Ternary.False; } let result = Ternary.True; @@ -6052,6 +6056,32 @@ namespace ts { } return Ternary.True; } + + function constructorRelatedTo(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { + if (sourceSignature && targetSignature && sourceSignature.declaration && targetSignature.declaration) { + const sourceAccessibility = sourceSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected); + const targetAccessibility = targetSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected); + + const isRelated = sourceAccessibility === targetAccessibility; + if (!isRelated && reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, flagsToString(sourceAccessibility), flagsToString(targetAccessibility)); + } + + return isRelated; + } + + return true; + + function flagsToString(flags: NodeFlags) { + if (flags === NodeFlags.Private) { + return "private"; + } + if (flags === NodeFlags.Protected) { + return "protected"; + } + return "public"; + } + } } // Return true if the given type is the constructor type for an abstract class @@ -10103,6 +10133,9 @@ namespace ts { // that the user will not add any. const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); if (constructSignatures.length) { + if (!isConstructorAccessible(node, constructSignatures[0])) { + return resolveErrorCall(node); + } return resolveCall(node, constructSignatures, candidatesOutArray); } @@ -10123,6 +10156,37 @@ namespace ts { return resolveErrorCall(node); } + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; + } + + const declaration = signature.declaration; + const flags = declaration.flags; + + // Public constructor is accessible. + if (!(flags & (NodeFlags.Private | NodeFlags.Protected))) { + return true; + } + + const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol); + const enclosingClassDeclaration = getContainingClass(node); + const enclosingClass = enclosingClassDeclaration ? getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingClassDeclaration)) : undefined; + + // A private or protected constructor can only be instantiated within it's own class + if (declaringClass !== enclosingClass) { + if (flags & NodeFlags.Private) { + error(node, Diagnostics.Constructor_of_type_0_is_private_and_only_accessible_within_class_1, signatureToString(signature), typeToString(declaringClass)); + } + if (flags & NodeFlags.Protected) { + error(node, Diagnostics.Constructor_of_type_0_is_protected_and_only_accessible_within_class_1, signatureToString(signature), typeToString(declaringClass)); + } + return false; + } + + return true; + } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[]): Signature { const tagType = checkExpression(node.tag); const apparentType = getApparentType(tagType); @@ -12059,7 +12123,7 @@ namespace ts { error(o.name, Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); } else if (deviation & (NodeFlags.Private | NodeFlags.Protected)) { - error(o.name, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); + error(o.name || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); } else if (deviation & NodeFlags.Abstract) { error(o.name, Diagnostics.Overload_signatures_must_all_be_abstract_or_not_abstract); @@ -13928,6 +13992,7 @@ namespace ts { if (baseTypes.length && produceDiagnostics) { const baseType = baseTypes[0]; const staticBaseType = getBaseConstructorTypeOfClass(type); + checkBaseTypeAccessibility(staticBaseType, baseTypeNode); checkSourceElement(baseTypeNode.expression); if (baseTypeNode.typeArguments) { forEach(baseTypeNode.typeArguments, checkSourceElement); @@ -13983,6 +14048,16 @@ namespace ts { } } + function checkBaseTypeAccessibility(type: ObjectType, node: ExpressionWithTypeArguments) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length) { + const declaration = signatures[0].declaration; + if (declaration && declaration.flags & NodeFlags.Private) { + error(node, Diagnostics.Cannot_extend_private_class_0, (node.expression).text); + } + } + } + function getTargetSymbol(s: Symbol) { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags @@ -16348,12 +16423,6 @@ namespace ts { if (flags & NodeFlags.Abstract) { return grammarErrorOnNode(lastStatic, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); } - else if (flags & NodeFlags.Protected) { - return grammarErrorOnNode(lastProtected, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "protected"); - } - else if (flags & NodeFlags.Private) { - return grammarErrorOnNode(lastPrivate, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "private"); - } else if (flags & NodeFlags.Async) { return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c848ebc3a10..2ee388ff42e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1823,6 +1823,22 @@ "category": "Error", "code": 2671 }, + "Cannot assign a '{0}' constructor type to a '{1}' constructor type.": { + "category": "Error", + "code": 2672 + }, + "Constructor of type '{0}' is private and only accessible within class '{1}'.": { + "category": "Error", + "code": 2673 + }, + "Constructor of type '{0}' is protected and only accessible within class '{1}'.": { + "category": "Error", + "code": 2674 + }, + "Cannot extend private class '{0}'.": { + "category": "Error", + "code": 2675 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", "code": 4000