Skip to content
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,10 @@ 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_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 +173,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_add_a_declare_modifier_or_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 +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,10 @@ 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 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 +361,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, add a 'declare' modifier or 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
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,10 @@
"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 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 +171,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, add a 'declare' modifier or 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
3 changes: 3 additions & 0 deletions src/extra/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,9 @@ export class ASTBuilder {

serializeAccessModifiers(node: DeclarationStatement): void {
var sb = this.sb;
if (node.is(CommonFlags.DECLARE)) {
sb.push("declare ");
}
if (node.is(CommonFlags.PUBLIC)) {
sb.push("public ");
} else if (node.is(CommonFlags.PRIVATE)) {
Expand Down
53 changes: 53 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,7 @@ export class Parser extends DiagnosticEmitter {
): Node | null {

// before:
// 'declare'?
// ('public' | 'private' | 'protected')?
// ('static' | 'abstract')?
// 'readonly'?
Expand Down Expand Up @@ -1870,6 +1871,39 @@ export class Parser extends DiagnosticEmitter {
// implemented methods are virtual
if (isInterface) flags |= CommonFlags.VIRTUAL;

var declareStart = 0;
var declareEnd = 0;
var contextIsAmbient = parent.is(CommonFlags.AMBIENT);
if (tn.peek() == Token.DECLARE) {
let state = tn.mark();
tn.next();
if (tn.peek() != Token.COLON) { // modifier
tn.discard(state);
if (isInterface) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(), "declare"
);
} else {
if (contextIsAmbient) {
this.error(
DiagnosticCode.A_declare_modifier_cannot_be_used_in_an_already_ambient_context,
tn.range()
); // recoverable
} else {
flags |= CommonFlags.DECLARE | CommonFlags.AMBIENT;
declareStart = tn.tokenPos;
declareEnd = tn.pos;
}
}
if (!startPos) startPos = tn.tokenPos;
} else { // identifier
tn.reset(state);
}
} else if (contextIsAmbient) {
flags |= CommonFlags.AMBIENT;
}

var accessStart = 0;
var accessEnd = 0;
if (tn.skip(Token.PUBLIC)) {
Expand Down Expand Up @@ -1999,6 +2033,12 @@ export class Parser extends DiagnosticEmitter {
tn.range(readonlyStart, readonlyEnd), "readonly"
); // recoverable
}
if (flags & CommonFlags.DECLARE) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(declareStart, declareEnd), "declare"
); // recoverable
}
} else {
tn.reset(state);
}
Expand Down Expand Up @@ -2108,6 +2148,13 @@ export class Parser extends DiagnosticEmitter {

// method: '(' Parameters (':' Type)? '{' Statement* '}' ';'?
if (tn.skip(Token.OPENPAREN)) {
if (flags & CommonFlags.DECLARE) {
this.error(
DiagnosticCode._0_modifier_cannot_be_used_here,
tn.range(declareStart, declareEnd), "declare"
); // recoverable
}

let signatureStart = tn.tokenPos;
let parameters = this.parseParameters(tn, isConstructor);
if (!parameters) return null;
Expand Down Expand Up @@ -2291,6 +2338,12 @@ export class Parser extends DiagnosticEmitter {
}
let initializer: Expression | null = null;
if (tn.skip(Token.EQUALS)) {
if (flags & CommonFlags.AMBIENT) {
this.error(
DiagnosticCode.Initializers_are_not_allowed_in_ambient_contexts,
tn.range()
); // recoverable
}
initializer = this.parseExpression(tn);
if (!initializer) return null;
}
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
61 changes: 51 additions & 10 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3092,17 +3092,18 @@ 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?
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));
assert(baseField.kind == ElementKind.FIELD);
existingField = <Field>baseField;
}
}
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;
}
}
if (existingField !== null && !existingField.is(CommonFlags.PRIVATE)) {
fieldType = existingField.type;
}
if (!fieldType) {
if (reportMode == ReportMode.REPORT) {
Expand Down Expand Up @@ -3130,6 +3131,46 @@ export class Resolver extends DiagnosticEmitter {
}
}
if (!fieldType) break; // did report above
if (existingField !== null) {
// visibility checks
let thisFieldIsPublic = fieldPrototype.isPublic;
let existingFieldIsPublic = existingField.isPublic;
let baseClass = <Class>base;

if (thisFieldIsPublic && !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 (!thisFieldIsPublic && 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 (!thisFieldIsPublic && !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 && !fieldPrototype.is(CommonFlags.DECLARE)) {
this.errorRelated(
DiagnosticCode.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_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;
Expand Down
11 changes: 11 additions & 0 deletions tests/compiler/duplicate-field-errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"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, add a 'declare' modifier or remove the redundant declaration."
]
}
26 changes: 26 additions & 0 deletions tests/compiler/duplicate-field-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class A {
private a: i32;
public b: i32;
private c: i32;
public d: i64;
public e: i32;
constructor(a: i32, b: i32, c: i32, d: i64, e: i32) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
}

export class B extends A {
public a: i32;
private b: i32;
private c: i32;
public d: i32;
public e: i32;
constructor(a: i32, b: i32, c: i32, d: i32, e: i32) {
super(a, b, c, d, e);
}
}

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