Skip to content
10 changes: 10 additions & 0 deletions src/diagnosticMessages.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export enum DiagnosticCode {
Generic_type_0_requires_1_type_argument_s = 2314,
Type_0_is_not_generic = 2315,
Type_0_is_not_assignable_to_type_1 = 2322,
Property_0_is_private_in_type_1_but_not_in_type_2 = 2325,
Index_signature_is_missing_in_type_0 = 2329,
_this_cannot_be_referenced_in_current_location = 2332,
_this_cannot_be_referenced_in_constructor_arguments = 2333,
Expand All @@ -148,8 +149,11 @@ export enum DiagnosticCode {
Duplicate_function_implementation = 2393,
This_overload_signature_is_not_compatible_with_its_implementation_signature = 2394,
Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local = 2395,
Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2 = 2416,
A_class_can_only_implement_an_interface = 2422,
A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434,
Types_have_separate_declarations_of_a_private_property_0 = 2442,
Property_0_is_protected_in_type_1_but_public_in_type_2 = 2444,
Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses = 2445,
Variable_0_used_before_its_declaration = 2448,
Cannot_redeclare_block_scoped_variable_0 = 2451,
Expand All @@ -170,6 +174,7 @@ export enum DiagnosticCode {
Expected_0_type_arguments_but_got_1 = 2558,
Property_0_has_no_initializer_and_is_not_assigned_in_the_constructor_before_this_is_used_or_returned = 2564,
Property_0_is_used_before_being_assigned = 2565,
Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_remove_the_redundant_declaration = 2612,
A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums = 2651,
Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration = 2673,
Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration = 2674,
Expand Down Expand Up @@ -307,6 +312,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2314: return "Generic type '{0}' requires {1} type argument(s).";
case 2315: return "Type '{0}' is not generic.";
case 2322: return "Type '{0}' is not assignable to type '{1}'.";
case 2325: return "Property '{0}' is private in type '{1}' but not in type '{2}'.";
case 2329: return "Index signature is missing in type '{0}'.";
case 2332: return "'this' cannot be referenced in current location.";
case 2333: return "'this' cannot be referenced in constructor arguments.";
Expand All @@ -332,8 +338,11 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2393: return "Duplicate function implementation.";
case 2394: return "This overload signature is not compatible with its implementation signature.";
case 2395: return "Individual declarations in merged declaration '{0}' must be all exported or all local.";
case 2416: return "Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.";
case 2422: return "A class can only implement an interface.";
case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged.";
case 2442: return "Types have separate declarations of a private property '{0}'.";
case 2444: return "Property '{0}' is protected in type '{1}' but public in type '{2}'.";
case 2445: return "Property '{0}' is protected and only accessible within class '{1}' and its subclasses.";
case 2448: return "Variable '{0}' used before its declaration.";
case 2451: return "Cannot redeclare block-scoped variable '{0}'";
Expand All @@ -354,6 +363,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2558: return "Expected {0} type arguments, but got {1}.";
case 2564: return "Property '{0}' has no initializer and is not assigned in the constructor before 'this' is used or returned.";
case 2565: return "Property '{0}' is used before being assigned.";
case 2612: return "Property '{0}' will overwrite the base property in '{1}'. If this is intentional, add an initializer. Otherwise, remove the redundant declaration.";
case 2651: return "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.";
case 2673: return "Constructor of class '{0}' is private and only accessible within the class declaration.";
case 2674: return "Constructor of class '{0}' is protected and only accessible within the class declaration.";
Expand Down
5 changes: 5 additions & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"Generic type '{0}' requires {1} type argument(s).": 2314,
"Type '{0}' is not generic.": 2315,
"Type '{0}' is not assignable to type '{1}'.": 2322,
"Property '{0}' is private in type '{1}' but not in type '{2}'.": 2325,
"Index signature is missing in type '{0}'.": 2329,
"'this' cannot be referenced in current location.": 2332,
"'this' cannot be referenced in constructor arguments.": 2333,
Expand All @@ -146,8 +147,11 @@
"Duplicate function implementation.": 2393,
"This overload signature is not compatible with its implementation signature.": 2394,
"Individual declarations in merged declaration '{0}' must be all exported or all local.": 2395,
"Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.": 2416,
"A class can only implement an interface.": 2422,
"A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434,
"Types have separate declarations of a private property '{0}'.": 2442,
"Property '{0}' is protected in type '{1}' but public in type '{2}'.": 2444,
"Property '{0}' is protected and only accessible within class '{1}' and its subclasses.": 2445,
"Variable '{0}' used before its declaration.": 2448,
"Cannot redeclare block-scoped variable '{0}'" : 2451,
Expand All @@ -168,6 +172,7 @@
"Expected {0} type arguments, but got {1}.": 2558,
"Property '{0}' has no initializer and is not assigned in the constructor before 'this' is used or returned.": 2564,
"Property '{0}' is used before being assigned.": 2565,
"Property '{0}' will overwrite the base property in '{1}'. If this is intentional, add an initializer. Otherwise, remove the redundant declaration.": 2612,
"A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": 2651,
"Constructor of class '{0}' is private and only accessible within the class declaration.": 2673,
"Constructor of class '{0}' is protected and only accessible within the class declaration.": 2674,
Expand Down
7 changes: 0 additions & 7 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1513,13 +1513,6 @@ export class Program extends DiagnosticEmitter {
}
}
}
} else {
this.errorRelated(
DiagnosticCode.Duplicate_identifier_0,
thisMember.identifierNode.range,
baseMember.identifierNode.range,
baseMember.identifierNode.text
);
}
}
}
Expand Down
86 changes: 72 additions & 14 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3092,18 +3092,26 @@ export class Resolver extends DiagnosticEmitter {
let fieldPrototype = <FieldPrototype>member;
let fieldTypeNode = fieldPrototype.typeNode;
let fieldType: Type | null = null;
// TODO: handle duplicate non-private fields specifically?
if (!fieldTypeNode) {
if (base) {
let baseMembers = base.members;
if (baseMembers !== null && baseMembers.has(fieldPrototype.name)) {
let baseField = assert(baseMembers.get(fieldPrototype.name));
if (!baseField.is(CommonFlags.PRIVATE)) {
assert(baseField.kind == ElementKind.FIELD);
fieldType = (<Field>baseField).type;
}
let existingField: Field | null = null;
if (base) {
let baseMembers = base.members;
if (baseMembers !== null && baseMembers.has(fieldPrototype.name)) {
let baseField = assert(baseMembers.get(fieldPrototype.name));
if (baseField.kind == ElementKind.FIELD) {
existingField = <Field>baseField;
} else {
this.errorRelated(
DiagnosticCode.Duplicate_identifier_0,
fieldPrototype.identifierNode.range, baseField.identifierNode.range,
fieldPrototype.name
);
}
}
}
if (!fieldTypeNode) {
if (existingField !== null && !existingField.is(CommonFlags.PRIVATE)) {
fieldType = existingField.type;
}
if (!fieldType) {
if (reportMode == ReportMode.REPORT) {
this.error(
Expand All @@ -3130,12 +3138,62 @@ export class Resolver extends DiagnosticEmitter {
}
}
if (!fieldType) break; // did report above
if (existingField !== null) {
// visibility checks
let baseClass = <Class>base;
let thisFieldIsPubOrProt = !fieldPrototype.is(CommonFlags.PRIVATE);
let existingFieldIsPublic = existingField.isPublic;

if (thisFieldIsPubOrProt && !existingFieldIsPublic) {
this.errorRelated(
DiagnosticCode.Property_0_is_private_in_type_1_but_not_in_type_2,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name, baseClass.internalName, instance.internalName
);
} else if (fieldPrototype.is(CommonFlags.PROTECTED) && existingFieldIsPublic) {
this.errorRelated(
DiagnosticCode.Property_0_is_protected_in_type_1_but_public_in_type_2,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name, instance.internalName, baseClass.internalName
);
} else if (!thisFieldIsPubOrProt && existingFieldIsPublic) {
this.errorRelated(
DiagnosticCode.Property_0_is_private_in_type_1_but_not_in_type_2,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name, instance.internalName, baseClass.internalName
);
} else if (!thisFieldIsPubOrProt && !existingFieldIsPublic) {
this.errorRelated(
DiagnosticCode.Types_have_separate_declarations_of_a_private_property_0,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name
);
}
// assignability checks
else if (fieldType.equals(existingField.type) && fieldPrototype.initializerNode === null) {
this.errorRelated(
DiagnosticCode.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_remove_the_redundant_declaration,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name, baseClass.internalName
);
} else if (!fieldType.isStrictlyAssignableTo(existingField.type)) {
this.errorRelated(
DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name, instance.internalName, baseClass.internalName
);
}
}
let fieldInstance = new Field(fieldPrototype, instance, fieldType);
assert(isPowerOf2(fieldType.byteSize));
let mask = fieldType.byteSize - 1;
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
fieldInstance.memoryOffset = memoryOffset;
memoryOffset += fieldType.byteSize;
if (existingField !== null) {
fieldInstance.memoryOffset = existingField.memoryOffset;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a vague suspicion that this can lead to issues, since typically inherited fields share the same flow flags for example, which is not the case anymore when duplicating the field. I don't see a case right now where this would trigger for sure (perhaps field initialization checks in ctors, hmm), though, but leaving a note here in case we ever encounter something along those lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like me to insert some sort of comment there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's fine without, just wanted to leave the note so you and me are aware in case :)

} else {
let mask = fieldType.byteSize - 1;
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
fieldInstance.memoryOffset = memoryOffset;
memoryOffset += fieldType.byteSize;
}
instance.add(memberName, fieldInstance); // reports
break;
}
Expand Down
14 changes: 14 additions & 0 deletions tests/compiler/duplicate-field-errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"asc_flags": [
],
"stderr": [
"TS2325: Property 'a' is private in type 'duplicate-field-errors/A' but not in type 'duplicate-field-errors/B'.",
"TS2325: Property 'b' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
"TS2442: Types have separate declarations of a private property 'c'.",
"TS2416: Property 'd' in type 'duplicate-field-errors/B' is not assignable to the same property in base type 'duplicate-field-errors/A'.",
"TS2612: Property 'e' will overwrite the base property in 'duplicate-field-errors/A'. If this is intentional, add an initializer. Otherwise, remove the redundant declaration.",
"TS2300: Duplicate identifier 'g'.",
"TS2444: Property 'f' is protected in type 'duplicate-field-errors/B' but public in type 'duplicate-field-errors/A'.",
"EOF"
]
}
33 changes: 33 additions & 0 deletions tests/compiler/duplicate-field-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class A {
private a: i32;
public b: i32;
private c: i32;
public d: i64;
public e: i32;
public f: i32;
constructor(a: i32, b: i32, c: i32, d: i64, e: i32, f: i32) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
}
g(): void {}
}

export class B extends A {
public a: i32;
private b: i32;
private c: i32;
public d: i32;
public e: i32;
public g: i32;
protected f: i32;
constructor(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) {
super(a, b, c, d, e, f);
ERROR("EOF");
}
}


4 changes: 4 additions & 0 deletions tests/compiler/duplicate-fields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"asc_flags": [
]
}
Loading