Skip to content

Allow duplicate fields #2158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 2, 2021
8 changes: 8 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 Down Expand Up @@ -307,6 +311,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 +337,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 Down
4 changes: 4 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 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
107 changes: 93 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,83 @@ export class Resolver extends DiagnosticEmitter {
}
}
if (!fieldType) break; // did report above
if (existingField !== null) {
// visibility checks
/*
existingField visibility on top
+==================+=========+===========+=========+
| Visibility Table | Private | Protected | Public |
+==================+=========+===========+=========+
| Private | error | error | error |
+------------------+---------+-----------+---------+
| Protected | error | allowed | error |
+------------------+---------+-----------+---------+
| Public | error | allowed | allowed |
+------------------+---------+-----------+---------+
*/

let baseClass = <Class>base;

// handle cases row-by-row
if (fieldPrototype.is(CommonFlags.PRIVATE)) {
if (existingField.is(CommonFlags.PRIVATE)) {
this.errorRelated(
DiagnosticCode.Types_have_separate_declarations_of_a_private_property_0,
fieldPrototype.identifierNode.range, existingField.identifierNode.range,
fieldPrototype.name
);
} else {
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 (fieldPrototype.is(CommonFlags.PROTECTED)) {
if (existingField.is(CommonFlags.PRIVATE)) {
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 (!existingField.is(CommonFlags.PROTECTED)) {
// may be implicitly public
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 {
// fieldPrototype is public here
if (existingField.is(CommonFlags.PRIVATE)) {
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
);
}
}

// assignability
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
13 changes: 13 additions & 0 deletions tests/compiler/duplicate-field-errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"asc_flags": [
],
"stderr": [
"TS2442: Types have separate declarations of a private property 'privPriv'.",
"TS2325: Property 'privProt' is private in type 'duplicate-field-errors/A' but not in type 'duplicate-field-errors/B'.",
"TS2325: Property 'privPub' is private in type 'duplicate-field-errors/A' but not in type 'duplicate-field-errors/B'.",
"TS2325: Property 'protPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
"TS2325: Property 'pubPriv' is private in type 'duplicate-field-errors/B' but not in type 'duplicate-field-errors/A'.",
"TS2444: Property 'pubProt' is protected in type 'duplicate-field-errors/B' but public in type 'duplicate-field-errors/A'.",
"TS2300: Duplicate identifier 'method'."
]
}
38 changes: 38 additions & 0 deletions tests/compiler/duplicate-field-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class A {
private privPriv: i32;
private privProt: i32;
private privPub: i32;

// --

protected protPriv: i32;
public pubPriv: i32;

// --

public pubProt: i32;

// --

method(): void {}
}

export class B extends A {
private privPriv: i32;
protected privProt: i32;
public privPub: i32;

// --

private protPriv: i32;
private pubPriv: i32;

// --

protected pubProt: i32;

// --

public method: i32;
}

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