Skip to content

Commit a924cc7

Browse files
authored
Allow redeclaration of compatible class fields (#2158)
1 parent a001103 commit a924cc7

10 files changed

+5775
-21
lines changed

src/diagnosticMessages.generated.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export enum DiagnosticCode {
124124
Generic_type_0_requires_1_type_argument_s = 2314,
125125
Type_0_is_not_generic = 2315,
126126
Type_0_is_not_assignable_to_type_1 = 2322,
127+
Property_0_is_private_in_type_1_but_not_in_type_2 = 2325,
127128
Index_signature_is_missing_in_type_0 = 2329,
128129
_this_cannot_be_referenced_in_current_location = 2332,
129130
_this_cannot_be_referenced_in_constructor_arguments = 2333,
@@ -149,8 +150,11 @@ export enum DiagnosticCode {
149150
Duplicate_function_implementation = 2393,
150151
This_overload_signature_is_not_compatible_with_its_implementation_signature = 2394,
151152
Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local = 2395,
153+
Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2 = 2416,
152154
A_class_can_only_implement_an_interface = 2422,
153155
A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434,
156+
Types_have_separate_declarations_of_a_private_property_0 = 2442,
157+
Property_0_is_protected_in_type_1_but_public_in_type_2 = 2444,
154158
Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses = 2445,
155159
Variable_0_used_before_its_declaration = 2448,
156160
Cannot_redeclare_block_scoped_variable_0 = 2451,
@@ -309,6 +313,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
309313
case 2314: return "Generic type '{0}' requires {1} type argument(s).";
310314
case 2315: return "Type '{0}' is not generic.";
311315
case 2322: return "Type '{0}' is not assignable to type '{1}'.";
316+
case 2325: return "Property '{0}' is private in type '{1}' but not in type '{2}'.";
312317
case 2329: return "Index signature is missing in type '{0}'.";
313318
case 2332: return "'this' cannot be referenced in current location.";
314319
case 2333: return "'this' cannot be referenced in constructor arguments.";
@@ -334,8 +339,11 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
334339
case 2393: return "Duplicate function implementation.";
335340
case 2394: return "This overload signature is not compatible with its implementation signature.";
336341
case 2395: return "Individual declarations in merged declaration '{0}' must be all exported or all local.";
342+
case 2416: return "Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.";
337343
case 2422: return "A class can only implement an interface.";
338344
case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged.";
345+
case 2442: return "Types have separate declarations of a private property '{0}'.";
346+
case 2444: return "Property '{0}' is protected in type '{1}' but public in type '{2}'.";
339347
case 2445: return "Property '{0}' is protected and only accessible within class '{1}' and its subclasses.";
340348
case 2448: return "Variable '{0}' used before its declaration.";
341349
case 2451: return "Cannot redeclare block-scoped variable '{0}'";

src/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"Generic type '{0}' requires {1} type argument(s).": 2314,
123123
"Type '{0}' is not generic.": 2315,
124124
"Type '{0}' is not assignable to type '{1}'.": 2322,
125+
"Property '{0}' is private in type '{1}' but not in type '{2}'.": 2325,
125126
"Index signature is missing in type '{0}'.": 2329,
126127
"'this' cannot be referenced in current location.": 2332,
127128
"'this' cannot be referenced in constructor arguments.": 2333,
@@ -147,8 +148,11 @@
147148
"Duplicate function implementation.": 2393,
148149
"This overload signature is not compatible with its implementation signature.": 2394,
149150
"Individual declarations in merged declaration '{0}' must be all exported or all local.": 2395,
151+
"Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.": 2416,
150152
"A class can only implement an interface.": 2422,
151153
"A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434,
154+
"Types have separate declarations of a private property '{0}'.": 2442,
155+
"Property '{0}' is protected in type '{1}' but public in type '{2}'.": 2444,
152156
"Property '{0}' is protected and only accessible within class '{1}' and its subclasses.": 2445,
153157
"Variable '{0}' used before its declaration.": 2448,
154158
"Cannot redeclare block-scoped variable '{0}'" : 2451,

src/program.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,13 +1513,6 @@ export class Program extends DiagnosticEmitter {
15131513
}
15141514
}
15151515
}
1516-
} else {
1517-
this.errorRelated(
1518-
DiagnosticCode.Duplicate_identifier_0,
1519-
thisMember.identifierNode.range,
1520-
baseMember.identifierNode.range,
1521-
baseMember.identifierNode.text
1522-
);
15231516
}
15241517
}
15251518
}

src/resolver.ts

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3092,18 +3092,26 @@ export class Resolver extends DiagnosticEmitter {
30923092
let fieldPrototype = <FieldPrototype>member;
30933093
let fieldTypeNode = fieldPrototype.typeNode;
30943094
let fieldType: Type | null = null;
3095-
// TODO: handle duplicate non-private fields specifically?
3096-
if (!fieldTypeNode) {
3097-
if (base) {
3098-
let baseMembers = base.members;
3099-
if (baseMembers !== null && baseMembers.has(fieldPrototype.name)) {
3100-
let baseField = assert(baseMembers.get(fieldPrototype.name));
3101-
if (!baseField.is(CommonFlags.PRIVATE)) {
3102-
assert(baseField.kind == ElementKind.FIELD);
3103-
fieldType = (<Field>baseField).type;
3104-
}
3095+
let existingField: Field | null = null;
3096+
if (base) {
3097+
let baseMembers = base.members;
3098+
if (baseMembers !== null && baseMembers.has(fieldPrototype.name)) {
3099+
let baseField = assert(baseMembers.get(fieldPrototype.name));
3100+
if (baseField.kind == ElementKind.FIELD) {
3101+
existingField = <Field>baseField;
3102+
} else {
3103+
this.errorRelated(
3104+
DiagnosticCode.Duplicate_identifier_0,
3105+
fieldPrototype.identifierNode.range, baseField.identifierNode.range,
3106+
fieldPrototype.name
3107+
);
31053108
}
31063109
}
3110+
}
3111+
if (!fieldTypeNode) {
3112+
if (existingField !== null && !existingField.is(CommonFlags.PRIVATE)) {
3113+
fieldType = existingField.type;
3114+
}
31073115
if (!fieldType) {
31083116
if (reportMode == ReportMode.REPORT) {
31093117
this.error(
@@ -3130,12 +3138,83 @@ export class Resolver extends DiagnosticEmitter {
31303138
}
31313139
}
31323140
if (!fieldType) break; // did report above
3141+
if (existingField !== null) {
3142+
// visibility checks
3143+
/*
3144+
existingField visibility on top
3145+
+==================+=========+===========+=========+
3146+
| Visibility Table | Private | Protected | Public |
3147+
+==================+=========+===========+=========+
3148+
| Private | error | error | error |
3149+
+------------------+---------+-----------+---------+
3150+
| Protected | error | allowed | error |
3151+
+------------------+---------+-----------+---------+
3152+
| Public | error | allowed | allowed |
3153+
+------------------+---------+-----------+---------+
3154+
*/
3155+
3156+
let baseClass = <Class>base;
3157+
3158+
// handle cases row-by-row
3159+
if (fieldPrototype.is(CommonFlags.PRIVATE)) {
3160+
if (existingField.is(CommonFlags.PRIVATE)) {
3161+
this.errorRelated(
3162+
DiagnosticCode.Types_have_separate_declarations_of_a_private_property_0,
3163+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3164+
fieldPrototype.name
3165+
);
3166+
} else {
3167+
this.errorRelated(
3168+
DiagnosticCode.Property_0_is_private_in_type_1_but_not_in_type_2,
3169+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3170+
fieldPrototype.name, instance.internalName, baseClass.internalName
3171+
);
3172+
}
3173+
} else if (fieldPrototype.is(CommonFlags.PROTECTED)) {
3174+
if (existingField.is(CommonFlags.PRIVATE)) {
3175+
this.errorRelated(
3176+
DiagnosticCode.Property_0_is_private_in_type_1_but_not_in_type_2,
3177+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3178+
fieldPrototype.name, baseClass.internalName, instance.internalName
3179+
);
3180+
} else if (!existingField.is(CommonFlags.PROTECTED)) {
3181+
// may be implicitly public
3182+
this.errorRelated(
3183+
DiagnosticCode.Property_0_is_protected_in_type_1_but_public_in_type_2,
3184+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3185+
fieldPrototype.name, instance.internalName, baseClass.internalName
3186+
);
3187+
}
3188+
} else {
3189+
// fieldPrototype is public here
3190+
if (existingField.is(CommonFlags.PRIVATE)) {
3191+
this.errorRelated(
3192+
DiagnosticCode.Property_0_is_private_in_type_1_but_not_in_type_2,
3193+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3194+
fieldPrototype.name, baseClass.internalName, instance.internalName
3195+
);
3196+
}
3197+
}
3198+
3199+
// assignability
3200+
if (!fieldType.isStrictlyAssignableTo(existingField.type)) {
3201+
this.errorRelated(
3202+
DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
3203+
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
3204+
fieldPrototype.name, instance.internalName, baseClass.internalName
3205+
);
3206+
}
3207+
}
31333208
let fieldInstance = new Field(fieldPrototype, instance, fieldType);
31343209
assert(isPowerOf2(fieldType.byteSize));
3135-
let mask = fieldType.byteSize - 1;
3136-
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
3137-
fieldInstance.memoryOffset = memoryOffset;
3138-
memoryOffset += fieldType.byteSize;
3210+
if (existingField !== null) {
3211+
fieldInstance.memoryOffset = existingField.memoryOffset;
3212+
} else {
3213+
let mask = fieldType.byteSize - 1;
3214+
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
3215+
fieldInstance.memoryOffset = memoryOffset;
3216+
memoryOffset += fieldType.byteSize;
3217+
}
31393218
instance.add(memberName, fieldInstance); // reports
31403219
break;
31413220
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"asc_flags": [
3+
],
4+
"stderr": [
5+
"TS2442: Types have separate declarations of a private property 'privPriv'.",
6+
"TS2325: Property 'privProt' is private in type 'duplicate-field-errors/A' but not in type 'duplicate-field-errors/B'.",
7+
"TS2325: Property 'privPub' is private in type 'duplicate-field-errors/A' but not in type 'duplicate-field-errors/B'.",
8+
"TS2325: Property 'protPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
9+
"TS2325: Property 'pubPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
10+
"TS2444: Property 'pubProt' is protected in type 'duplicate-field-errors/B' but public in type 'duplicate-field-errors/A'.",
11+
"TS2300: Duplicate identifier 'method'."
12+
]
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class A {
2+
private privPriv: i32;
3+
private privProt: i32;
4+
private privPub: i32;
5+
6+
// --
7+
8+
protected protPriv: i32;
9+
public pubPriv: i32;
10+
11+
// --
12+
13+
public pubProt: i32;
14+
15+
// --
16+
17+
method(): void {}
18+
}
19+
20+
export class B extends A {
21+
private privPriv: i32;
22+
protected privProt: i32;
23+
public privPub: i32;
24+
25+
// --
26+
27+
private protPriv: i32;
28+
private pubPriv: i32;
29+
30+
// --
31+
32+
protected pubProt: i32;
33+
34+
// --
35+
36+
public method: i32;
37+
}
38+

tests/compiler/duplicate-fields.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"asc_flags": [
3+
]
4+
}

0 commit comments

Comments
 (0)