Skip to content

Commit bde20c4

Browse files
committed
Merge pull request #6885 from AbubakerB/constructorAccessibility
Constructor Visibility
2 parents a8633ee + de23e2f commit bde20c4

38 files changed

+1184
-135
lines changed

src/compiler/checker.ts

+116-23
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,16 @@ namespace ts {
17201720
return result;
17211721
}
17221722

1723+
function visibilityToString(flags: NodeFlags) {
1724+
if (flags === NodeFlags.Private) {
1725+
return "private";
1726+
}
1727+
if (flags === NodeFlags.Protected) {
1728+
return "protected";
1729+
}
1730+
return "public";
1731+
}
1732+
17231733
function getTypeAliasForTypeLiteral(type: Type): Symbol {
17241734
if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) {
17251735
let node = type.symbol.declarations[0].parent;
@@ -5907,16 +5917,20 @@ namespace ts {
59075917

59085918
const sourceSignatures = getSignaturesOfType(source, kind);
59095919
const targetSignatures = getSignaturesOfType(target, kind);
5910-
if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length &&
5911-
isAbstractConstructorType(source) && !isAbstractConstructorType(target)) {
5912-
// An abstract constructor type is not assignable to a non-abstract constructor type
5920+
if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) {
5921+
if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) {
5922+
// An abstract constructor type is not assignable to a non-abstract constructor type
59135923
// as it would otherwise be possible to new an abstract class. Note that the assignability
5914-
// check we perform for an extends clause excludes construct signatures from the target,
5915-
// so this check never proceeds.
5916-
if (reportErrors) {
5917-
reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type);
5924+
// check we perform for an extends clause excludes construct signatures from the target,
5925+
// so this check never proceeds.
5926+
if (reportErrors) {
5927+
reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type);
5928+
}
5929+
return Ternary.False;
5930+
}
5931+
if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) {
5932+
return Ternary.False;
59185933
}
5919-
return Ternary.False;
59205934
}
59215935

59225936
let result = Ternary.True;
@@ -6059,6 +6073,36 @@ namespace ts {
60596073
}
60606074
return Ternary.True;
60616075
}
6076+
6077+
function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) {
6078+
if (!sourceSignature.declaration || !targetSignature.declaration) {
6079+
return true;
6080+
}
6081+
6082+
const sourceAccessibility = sourceSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected);
6083+
const targetAccessibility = targetSignature.declaration.flags & (NodeFlags.Private | NodeFlags.Protected);
6084+
6085+
// A public, protected and private signature is assignable to a private signature.
6086+
if (targetAccessibility === NodeFlags.Private) {
6087+
return true;
6088+
}
6089+
6090+
// A public and protected signature is assignable to a protected signature.
6091+
if (targetAccessibility === NodeFlags.Protected && sourceAccessibility !== NodeFlags.Private) {
6092+
return true;
6093+
}
6094+
6095+
// Only a public signature is assignable to public signature.
6096+
if (targetAccessibility !== NodeFlags.Protected && !sourceAccessibility) {
6097+
return true;
6098+
}
6099+
6100+
if (reportErrors) {
6101+
reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility));
6102+
}
6103+
6104+
return false;
6105+
}
60626106
}
60636107

60646108
// Return true if the given type is the constructor type for an abstract class
@@ -10166,6 +10210,9 @@ namespace ts {
1016610210
// that the user will not add any.
1016710211
const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
1016810212
if (constructSignatures.length) {
10213+
if (!isConstructorAccessible(node, constructSignatures[0])) {
10214+
return resolveErrorCall(node);
10215+
}
1016910216
return resolveCall(node, constructSignatures, candidatesOutArray);
1017010217
}
1017110218

@@ -10186,6 +10233,36 @@ namespace ts {
1018610233
return resolveErrorCall(node);
1018710234
}
1018810235

10236+
function isConstructorAccessible(node: NewExpression, signature: Signature) {
10237+
if (!signature || !signature.declaration) {
10238+
return true;
10239+
}
10240+
10241+
const declaration = signature.declaration;
10242+
const flags = declaration.flags;
10243+
10244+
// Public constructor is accessible.
10245+
if (!(flags & (NodeFlags.Private | NodeFlags.Protected))) {
10246+
return true;
10247+
}
10248+
10249+
const declaringClassDeclaration = <ClassLikeDeclaration>getClassLikeDeclarationOfSymbol(declaration.parent.symbol);
10250+
const declaringClass = <InterfaceType>getDeclaredTypeOfSymbol(declaration.parent.symbol);
10251+
10252+
// A private or protected constructor can only be instantiated within it's own class
10253+
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
10254+
if (flags & NodeFlags.Private) {
10255+
error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
10256+
}
10257+
if (flags & NodeFlags.Protected) {
10258+
error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass));
10259+
}
10260+
return false;
10261+
}
10262+
10263+
return true;
10264+
}
10265+
1018910266
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[]): Signature {
1019010267
const tagType = checkExpression(node.tag);
1019110268
const apparentType = getApparentType(tagType);
@@ -12123,7 +12200,7 @@ namespace ts {
1212312200
error(o.name, Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient);
1212412201
}
1212512202
else if (deviation & (NodeFlags.Private | NodeFlags.Protected)) {
12126-
error(o.name, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected);
12203+
error(o.name || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected);
1212712204
}
1212812205
else if (deviation & NodeFlags.Abstract) {
1212912206
error(o.name, Diagnostics.Overload_signatures_must_all_be_abstract_or_not_abstract);
@@ -13992,6 +14069,7 @@ namespace ts {
1399214069
if (baseTypes.length && produceDiagnostics) {
1399314070
const baseType = baseTypes[0];
1399414071
const staticBaseType = getBaseConstructorTypeOfClass(type);
14072+
checkBaseTypeAccessibility(staticBaseType, baseTypeNode);
1399514073
checkSourceElement(baseTypeNode.expression);
1399614074
if (baseTypeNode.typeArguments) {
1399714075
forEach(baseTypeNode.typeArguments, checkSourceElement);
@@ -14047,6 +14125,19 @@ namespace ts {
1404714125
}
1404814126
}
1404914127

14128+
function checkBaseTypeAccessibility(type: ObjectType, node: ExpressionWithTypeArguments) {
14129+
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
14130+
if (signatures.length) {
14131+
const declaration = signatures[0].declaration;
14132+
if (declaration && declaration.flags & NodeFlags.Private) {
14133+
const typeClassDeclaration = <ClassLikeDeclaration>getClassLikeDeclarationOfSymbol(type.symbol);
14134+
if (!isNodeWithinClass(node, typeClassDeclaration)) {
14135+
error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, (<Identifier>node.expression).text);
14136+
}
14137+
}
14138+
}
14139+
}
14140+
1405014141
function getTargetSymbol(s: Symbol) {
1405114142
// if symbol is instantiated its flags are not copied from the 'target'
1405214143
// so we'll need to get back original 'target' symbol to work with correct set of flags
@@ -15370,6 +15461,18 @@ namespace ts {
1537015461
return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments;
1537115462
}
1537215463

15464+
function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) {
15465+
while (true) {
15466+
node = getContainingClass(node);
15467+
if (!node) {
15468+
return false;
15469+
}
15470+
if (node === classDeclaration) {
15471+
return true;
15472+
}
15473+
}
15474+
}
15475+
1537315476
function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment {
1537415477
while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) {
1537515478
nodeOnRightSide = <QualifiedName>nodeOnRightSide.parent;
@@ -16253,16 +16356,12 @@ namespace ts {
1625316356
case SyntaxKind.PublicKeyword:
1625416357
case SyntaxKind.ProtectedKeyword:
1625516358
case SyntaxKind.PrivateKeyword:
16256-
let text: string;
16257-
if (modifier.kind === SyntaxKind.PublicKeyword) {
16258-
text = "public";
16259-
}
16260-
else if (modifier.kind === SyntaxKind.ProtectedKeyword) {
16261-
text = "protected";
16359+
let text = visibilityToString(modifierToFlag(modifier.kind));
16360+
16361+
if (modifier.kind === SyntaxKind.ProtectedKeyword) {
1626216362
lastProtected = modifier;
1626316363
}
16264-
else {
16265-
text = "private";
16364+
else if (modifier.kind === SyntaxKind.PrivateKeyword) {
1626616365
lastPrivate = modifier;
1626716366
}
1626816367

@@ -16413,12 +16512,6 @@ namespace ts {
1641316512
if (flags & NodeFlags.Abstract) {
1641416513
return grammarErrorOnNode(lastStatic, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract");
1641516514
}
16416-
else if (flags & NodeFlags.Protected) {
16417-
return grammarErrorOnNode(lastProtected, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "protected");
16418-
}
16419-
else if (flags & NodeFlags.Private) {
16420-
return grammarErrorOnNode(lastPrivate, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "private");
16421-
}
1642216515
else if (flags & NodeFlags.Async) {
1642316516
return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async");
1642416517
}

src/compiler/declarationEmitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,7 @@ namespace ts {
13131313
if (node.kind === SyntaxKind.FunctionDeclaration) {
13141314
emitModuleElementDeclarationFlags(node);
13151315
}
1316-
else if (node.kind === SyntaxKind.MethodDeclaration) {
1316+
else if (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.Constructor) {
13171317
emitClassMemberDeclarationFlags(node.flags);
13181318
}
13191319
if (node.kind === SyntaxKind.FunctionDeclaration) {

src/compiler/diagnosticMessages.json

+17-1
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,22 @@
18271827
"category": "Error",
18281828
"code": 2671
18291829
},
1830+
"Cannot assign a '{0}' constructor type to a '{1}' constructor type.": {
1831+
"category": "Error",
1832+
"code": 2672
1833+
},
1834+
"Constructor of class '{0}' is private and only accessible within the class declaration.": {
1835+
"category": "Error",
1836+
"code": 2673
1837+
},
1838+
"Constructor of class '{0}' is protected and only accessible within the class declaration.": {
1839+
"category": "Error",
1840+
"code": 2674
1841+
},
1842+
"Cannot extend a class '{0}'. Class constructor is marked as private.": {
1843+
"category": "Error",
1844+
"code": 2675
1845+
},
18301846
"Import declaration '{0}' is using private name '{1}'.": {
18311847
"category": "Error",
18321848
"code": 4000
@@ -2785,4 +2801,4 @@
27852801
"category": "Error",
27862802
"code": 17009
27872803
}
2788-
}
2804+
}

tests/baselines/reference/Protected3.errors.txt

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/conformance/parser/ecmascript5/Protected/Protected3.ts ===
2+
class C {
3+
>C : Symbol(C, Decl(Protected3.ts, 0, 0))
4+
5+
protected constructor() { }
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/conformance/parser/ecmascript5/Protected/Protected3.ts ===
2+
class C {
3+
>C : C
4+
5+
protected constructor() { }
6+
}
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
1-
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(6,5): error TS1089: 'private' modifier cannot appear on a constructor declaration.
2-
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(10,5): error TS1089: 'protected' modifier cannot appear on a constructor declaration.
3-
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(23,9): error TS1089: 'private' modifier cannot appear on a constructor declaration.
4-
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(27,9): error TS1089: 'protected' modifier cannot appear on a constructor declaration.
1+
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(15,9): error TS2673: Constructor of class 'D' is private and only accessible within the class declaration.
2+
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(16,9): error TS2674: Constructor of class 'E' is protected and only accessible within the class declaration.
3+
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(32,13): error TS2673: Constructor of class 'D<T>' is private and only accessible within the class declaration.
4+
tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts(33,13): error TS2674: Constructor of class 'E<T>' is protected and only accessible within the class declaration.
55

66

77
==== tests/cases/conformance/classes/constructorDeclarations/classConstructorAccessibility.ts (4 errors) ====
8+
89
class C {
910
public constructor(public x: number) { }
1011
}
1112

1213
class D {
13-
private constructor(public x: number) { } // error
14-
~~~~~~~
15-
!!! error TS1089: 'private' modifier cannot appear on a constructor declaration.
14+
private constructor(public x: number) { }
1615
}
1716

1817
class E {
19-
protected constructor(public x: number) { } // error
20-
~~~~~~~~~
21-
!!! error TS1089: 'protected' modifier cannot appear on a constructor declaration.
18+
protected constructor(public x: number) { }
2219
}
2320

2421
var c = new C(1);
25-
var d = new D(1);
26-
var e = new E(1);
22+
var d = new D(1); // error
23+
~~~~~~~~
24+
!!! error TS2673: Constructor of class 'D' is private and only accessible within the class declaration.
25+
var e = new E(1); // error
26+
~~~~~~~~
27+
!!! error TS2674: Constructor of class 'E' is protected and only accessible within the class declaration.
2728

2829
module Generic {
2930
class C<T> {
3031
public constructor(public x: T) { }
3132
}
3233

3334
class D<T> {
34-
private constructor(public x: T) { } // error
35-
~~~~~~~
36-
!!! error TS1089: 'private' modifier cannot appear on a constructor declaration.
35+
private constructor(public x: T) { }
3736
}
3837

3938
class E<T> {
40-
protected constructor(public x: T) { } // error
41-
~~~~~~~~~
42-
!!! error TS1089: 'protected' modifier cannot appear on a constructor declaration.
39+
protected constructor(public x: T) { }
4340
}
4441

4542
var c = new C(1);
46-
var d = new D(1);
47-
var e = new E(1);
43+
var d = new D(1); // error
44+
~~~~~~~~
45+
!!! error TS2673: Constructor of class 'D<T>' is private and only accessible within the class declaration.
46+
var e = new E(1); // error
47+
~~~~~~~~
48+
!!! error TS2674: Constructor of class 'E<T>' is protected and only accessible within the class declaration.
4849
}
4950

0 commit comments

Comments
 (0)