Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 36 additions & 38 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37056,7 +37056,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// that the user will not add any.
const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
if (constructSignatures.length) {
if (!isConstructorAccessible(node, constructSignatures[0])) {
const accessibilityError = getConstructorAccessibilityError(node, constructSignatures, ModifierFlags.NonPublicAccessibilityModifier);
if (accessibilityError) {
if (accessibilityError.kind & ModifierFlags.Private) {
error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(accessibilityError.declaringClass));
}
if (accessibilityError.kind & ModifierFlags.Protected) {
error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(accessibilityError.declaringClass));
}
return resolveErrorCall(node);
}
// If the expression is a class of abstract type, or an abstract construct signature,
Expand Down Expand Up @@ -37137,41 +37144,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType);
}

function isConstructorAccessible(node: NewExpression, signature: Signature) {
if (!signature || !signature.declaration) {
return true;
}

const declaration = signature.declaration;
const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier);
function getConstructorAccessibilityError(node: Expression, signatures: readonly Signature[], modifiersMask: ModifierFlags) {
for (const signature of signatures) {
if (!signature.declaration) {
continue;
}
const declaration = signature.declaration;
const modifiers = getSelectedEffectiveModifierFlags(declaration, modifiersMask);

// (1) Public constructors and (2) constructor functions are always accessible.
if (!modifiers || declaration.kind !== SyntaxKind.Constructor) {
return true;
}
// (1) Public constructors and (2) constructor functions are always accessible.
if (!modifiers || declaration.kind !== SyntaxKind.Constructor) {
continue;
}

const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!;
const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol) as InterfaceType;
const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!;

// A private or protected constructor can only be instantiated within its own class (or a subclass, for protected)
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
const containingClass = getContainingClass(node);
if (containingClass && modifiers & ModifierFlags.Protected) {
const containingType = getTypeOfNode(containingClass);
if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) {
return true;
// A private or protected constructor can only be instantiated within its own class (or a subclass, for protected)
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
const containingClass = getContainingClass(node);
if (containingClass && modifiers & ModifierFlags.Protected) {
const containingType = getTypeOfNode(containingClass);
if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) {
continue;
}
}
return {
kind: modifiers,
declaringClass: getDeclaredTypeOfSymbol(declaration.parent.symbol),
};
}
if (modifiers & ModifierFlags.Private) {
error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
}
if (modifiers & ModifierFlags.Protected) {
error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
}
return false;
}

return true;
return undefined;
}

function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain; relatedMessage: DiagnosticMessage | undefined; } {
Expand Down Expand Up @@ -47349,14 +47352,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) {
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
if (signatures.length) {
const declaration = signatures[0].declaration;
if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) {
const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!;
if (!isNodeWithinClass(node, typeClassDeclaration)) {
error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol));
}
}
const accessibilityError = getConstructorAccessibilityError(node, signatures, ModifierFlags.Private);
if (accessibilityError) {
error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(accessibilityError.declaringClass.symbol));
}
}

Expand Down
162 changes: 162 additions & 0 deletions tests/baselines/reference/extendPrivateConstructorClass2.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
extendPrivateConstructorClass2.ts(10,1): error TS2673: Constructor of class 'A1' is private and only accessible within the class declaration.
extendPrivateConstructorClass2.ts(11,24): error TS2675: Cannot extend a class 'A1'. Class constructor is marked as private.
extendPrivateConstructorClass2.ts(22,1): error TS2673: Constructor of class 'B2' is private and only accessible within the class declaration.
extendPrivateConstructorClass2.ts(23,24): error TS2675: Cannot extend a class 'B2'. Class constructor is marked as private.
extendPrivateConstructorClass2.ts(33,26): error TS2675: Cannot extend a class 'j1'. Class constructor is marked as private.
extendPrivateConstructorClass2.ts(79,26): error TS2510: Base constructors must all have the same return type.
extendPrivateConstructorClass2.ts(98,22): error TS2510: Base constructors must all have the same return type.
extendPrivateConstructorClass2.ts(98,22): error TS2675: Cannot extend a class 'j10'. Class constructor is marked as private.
extendPrivateConstructorClass2.ts(129,22): error TS2675: Cannot extend a class 'j14'. Class constructor is marked as private.


==== extendPrivateConstructorClass2.ts (9 errors) ====
class A1 {
private constructor(arg: string) {}
}
class B1 {
constructor(arg: number) {}
}

declare const Cls1: typeof A1 & typeof B1;

new Cls1(42); // error
~~~~~~~~~~~~
!!! error TS2673: Constructor of class 'A1' is private and only accessible within the class declaration.
class Derived1 extends Cls1 {} // error
~~~~
!!! error TS2675: Cannot extend a class 'A1'. Class constructor is marked as private.

class A2 {
constructor(arg: string) {}
}
class B2 {
private constructor(arg: number) {}
}

declare const Cls2: typeof A2 & typeof B2;

new Cls2(42); // error
~~~~~~~~~~~~
!!! error TS2673: Constructor of class 'B2' is private and only accessible within the class declaration.
class Derived2 extends Cls2 {} // error
~~~~
!!! error TS2675: Cannot extend a class 'B2'. Class constructor is marked as private.

// https://github.com/microsoft/TypeScript/issues/62614
declare abstract class j1 {
private constructor(...args: any[]);
}
declare abstract class j2 {
private constructor(...args: any[]);
}
declare const jS: typeof j1 & typeof j2;
declare class j0 extends jS {} // error
~~
!!! error TS2675: Cannot extend a class 'j1'. Class constructor is marked as private.

abstract class j3 {
private constructor(...args: any[]) {}
method1() {
abstract class j4 {
private constructor(...args: any[]) {}
method2() {
const jS: typeof j3 & typeof j4 = null!;

// bizarre but ok
class j0 extends jS {
method1() {}
method2() {}
}
}
}
}
}

abstract class j5 {
private constructor(...args: any[]) {}
method1() {
abstract class j6 {
private constructor(...args: any[]) {}
method2() {}
}
const jS: typeof j5 & typeof j6 = null!;

// bizarre but ok too given the base is a result of a mixin
class j0 extends jS {
method1() {}
method2() {}
}
}
}

abstract class j7 {
private constructor(arg: string) {}
method1() {
abstract class j8 {
private constructor(arg: number) {}
method2() {
const jS: typeof j7 & typeof j8 = null!;

// error
class j0 extends jS {
~~
!!! error TS2510: Base constructors must all have the same return type.
method1() {}
method2() {}
}
}
}
}
}

abstract class j9 {
private constructor(arg: string) {}
method1() {
abstract class j10 {
private constructor(arg: number) {}
method2() {}
}
const jS: typeof j9 & typeof j10 = null!;

// error
class j0 extends jS {
~~
!!! error TS2510: Base constructors must all have the same return type.
~~
!!! error TS2675: Cannot extend a class 'j10'. Class constructor is marked as private.
method1() {}
method2() {}
}
}
}

abstract class j11 {
private constructor(arg: string) {}
static {
abstract class j12 {
private constructor(arg: number) {}
static {
const jS: typeof j11 & typeof j12 = null!;

// ok
class j0 extends jS {}
}
}
}
}

abstract class j13 {
private constructor(arg: string) {}
static {
abstract class j14 {
private constructor(arg: number) {}
}
const jS: typeof j13 & typeof j14 = null!;

// error
class j0 extends jS {}
~~
!!! error TS2675: Cannot extend a class 'j14'. Class constructor is marked as private.
}
}

Loading