Skip to content

Implement calls to 'super()' #445

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 12 commits into from
Jan 31, 2019
3 changes: 2 additions & 1 deletion src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export function nodeIsCallable(kind: NodeKind): bool {
case NodeKind.CALL:
case NodeKind.ELEMENTACCESS:
case NodeKind.PARENTHESIZED:
case NodeKind.PROPERTYACCESS: return true;
case NodeKind.PROPERTYACCESS:
case NodeKind.SUPER: return true;
}
return false;
}
Expand Down
89 changes: 87 additions & 2 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,8 @@ export class Compiler extends DiagnosticEmitter {
if (isConstructor) {
let nativeSizeType = this.options.nativeSizeType;
assert(instance.is(CommonFlags.INSTANCE));
let parent = assert(instance.parent);
assert(parent.kind == ElementKind.CLASS);

// implicitly return `this` if the constructor doesn't always return on its own
if (!flow.is(FlowFlags.RETURNS)) {
Expand All @@ -1105,14 +1107,20 @@ export class Compiler extends DiagnosticEmitter {

// if not all branches are guaranteed to allocate, also append a conditional allocation
} else {
let parent = assert(instance.parent);
assert(parent.kind == ElementKind.CLASS);
stmts.push(module.createTeeLocal(0,
this.makeConditionalAllocate(<Class>parent, declaration.name)
));
}
}

// check that super has been called if this is a derived class
if ((<Class>parent).base && !flow.is(FlowFlags.CALLS_SUPER)) {
this.error(
DiagnosticCode.Constructors_for_derived_classes_must_contain_a_super_call,
instance.prototype.declaration.range
);
}

// make sure all branches return
} else if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) {
this.error(
Expand Down Expand Up @@ -5230,6 +5238,43 @@ export class Compiler extends DiagnosticEmitter {
break;
}

case ElementKind.CLASS: {

// call to `super()`
if (expression.expression.kind == NodeKind.SUPER) {
if (!currentFunction.is(CommonFlags.CONSTRUCTOR)) {
this.error(
DiagnosticCode.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors,
expression.range
);
return module.createUnreachable();
}

let classInstance = assert(currentFunction.parent);
assert(classInstance.kind == ElementKind.CLASS);
let expr = this.compileSuperInstantiate(<Class>classInstance, expression.arguments, expression);
this.currentType = Type.void;

// check that super() is called before allocation is performed (incl. in super arguments)
let flow = currentFunction.flow;
if (flow.isAny(
FlowFlags.ALLOCATES |
FlowFlags.CONDITIONALLY_ALLOCATES
)) {
this.error(
DiagnosticCode._super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class,
expression.range
);
return module.createUnreachable();
}
flow.set(FlowFlags.ALLOCATES | FlowFlags.CALLS_SUPER);

let thisLocal = assert(this.currentFunction.flow.getScopedLocal("this"));
return module.createSetLocal(thisLocal.index, expr);
}
// otherwise fall-through
}

// not supported
default: {
this.error(
Expand Down Expand Up @@ -6027,6 +6072,15 @@ export class Compiler extends DiagnosticEmitter {
return module.createUnreachable();
}
case NodeKind.SUPER: {
if (currentFunction.is(CommonFlags.CONSTRUCTOR)) {
if (!currentFunction.flow.is(FlowFlags.CALLS_SUPER)) {
// TS1034 in the parser effectively limits this to property accesses
this.error(
DiagnosticCode._super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class,
expression.range
);
}
}
let flow = currentFunction.flow;
if (flow.is(FlowFlags.INLINE_CONTEXT)) {
let scopedThis = flow.getScopedLocal("this");
Expand Down Expand Up @@ -6658,6 +6712,37 @@ export class Compiler extends DiagnosticEmitter {
return expr;
}

compileSuperInstantiate(classInstance: Class, argumentExpressions: Expression[], reportNode: Node): ExpressionRef {
// traverse to the top-most visible constructor (except the current one)
var currentClassInstance: Class | null = classInstance.base;
var constructorInstance: Function | null = null;
while (currentClassInstance) {
constructorInstance = currentClassInstance.constructorInstance;
if (constructorInstance) break; // TODO: check visibility
currentClassInstance = currentClassInstance.base;
}

// if a constructor is present, allocate the necessary memory for `this` and call it
var expr: ExpressionRef;
if (constructorInstance) {
expr = this.compileCallDirect(constructorInstance, argumentExpressions, reportNode,
this.makeAllocate(classInstance, reportNode)
);

// otherwise simply allocate a new instance and initialize its fields
} else {
if (argumentExpressions.length) {
this.error(
DiagnosticCode.Expected_0_arguments_but_got_1,
reportNode.range, "0", argumentExpressions.length.toString(10)
);
}
expr = this.makeAllocate(classInstance, reportNode);
}
this.currentType = classInstance.type;
return expr;
}

compileParenthesizedExpression(
expression: ParenthesizedExpression,
contextualType: Type
Expand Down
14 changes: 13 additions & 1 deletion src/diagnosticMessages.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export enum DiagnosticCode {
Unexpected_end_of_text = 1126,
Invalid_character = 1127,
_case_or_default_expected = 1130,
_super_must_be_followed_by_an_argument_list_or_member_access = 1034,
A_declare_modifier_cannot_be_used_in_an_already_ambient_context = 1038,
Type_argument_expected = 1140,
String_literal_expected = 1141,
Expand Down Expand Up @@ -94,13 +95,16 @@ export enum DiagnosticCode {
Index_signature_is_missing_in_type_0 = 2329,
_this_cannot_be_referenced_in_current_location = 2332,
_super_can_only_be_referenced_in_a_derived_class = 2335,
Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors = 2337,
Property_0_does_not_exist_on_type_1 = 2339,
Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349,
Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature = 2351,
A_function_whose_declared_type_is_not_void_must_return_a_value = 2355,
The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357,
The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364,
Operator_0_cannot_be_applied_to_types_1_and_2 = 2365,
A_super_call_must_be_the_first_statement_in_the_constructor = 2376,
Constructors_for_derived_classes_must_contain_a_super_call = 2377,
_get_and_set_accessor_must_have_the_same_type = 2380,
Constructor_implementation_is_missing = 2390,
Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391,
Expand All @@ -124,7 +128,9 @@ export enum DiagnosticCode {
Required_type_parameters_may_not_follow_optional_type_parameters = 2706,
File_0_not_found = 6054,
Numeric_separators_are_not_allowed_here = 6188,
Multiple_consecutive_numeric_separators_are_not_permitted = 6189
Multiple_consecutive_numeric_separators_are_not_permitted = 6189,
_super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class = 17009,
_super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class = 17011
}

/** Translates a diagnostic code to its respective string. */
Expand Down Expand Up @@ -189,6 +195,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 1126: return "Unexpected end of text.";
case 1127: return "Invalid character.";
case 1130: return "'case' or 'default' expected.";
case 1034: return "'super' must be followed by an argument list or member access.";
case 1038: return "A 'declare' modifier cannot be used in an already ambient context.";
case 1140: return "Type argument expected.";
case 1141: return "String literal expected.";
Expand Down Expand Up @@ -217,13 +224,16 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2329: return "Index signature is missing in type '{0}'.";
case 2332: return "'this' cannot be referenced in current location.";
case 2335: return "'super' can only be referenced in a derived class.";
case 2337: return "Super calls are not permitted outside constructors or in nested functions inside constructors.";
case 2339: return "Property '{0}' does not exist on type '{1}'.";
case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.";
case 2351: return "Cannot use 'new' with an expression whose type lacks a construct signature.";
case 2355: return "A function whose declared type is not 'void' must return a value.";
case 2357: return "The operand of an increment or decrement operator must be a variable or a property access.";
case 2364: return "The left-hand side of an assignment expression must be a variable or a property access.";
case 2365: return "Operator '{0}' cannot be applied to types '{1}' and '{2}'.";
case 2376: return "A 'super' call must be the first statement in the constructor.";
case 2377: return "Constructors for derived classes must contain a 'super' call.";
case 2380: return "'get' and 'set' accessor must have the same type.";
case 2390: return "Constructor implementation is missing.";
case 2391: return "Function implementation is missing or not immediately following the declaration.";
Expand All @@ -248,6 +258,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 6054: return "File '{0}' not found.";
case 6188: return "Numeric separators are not allowed here.";
case 6189: return "Multiple consecutive numeric separators are not permitted.";
case 17009: return "'super' must be called before accessing 'this' in the constructor of a derived class.";
case 17011: return "'super' must be called before accessing a property of 'super' in the constructor of a derived class.";
default: return "";
}
}
8 changes: 7 additions & 1 deletion src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"Unexpected end of text.": 1126,
"Invalid character.": 1127,
"'case' or 'default' expected.": 1130,
"'super' must be followed by an argument list or member access.": 1034,
"A 'declare' modifier cannot be used in an already ambient context.": 1038,
"Type argument expected.": 1140,
"String literal expected.": 1141,
Expand Down Expand Up @@ -88,13 +89,16 @@
"Index signature is missing in type '{0}'.": 2329,
"'this' cannot be referenced in current location.": 2332,
"'super' can only be referenced in a derived class.": 2335,
"Super calls are not permitted outside constructors or in nested functions inside constructors.": 2337,
"Property '{0}' does not exist on type '{1}'.": 2339,
"Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349,
"Cannot use 'new' with an expression whose type lacks a construct signature.": 2351,
"A function whose declared type is not 'void' must return a value.": 2355,
"The operand of an increment or decrement operator must be a variable or a property access.": 2357,
"The left-hand side of an assignment expression must be a variable or a property access.": 2364,
"Operator '{0}' cannot be applied to types '{1}' and '{2}'.": 2365,
"A 'super' call must be the first statement in the constructor.": 2376,
"Constructors for derived classes must contain a 'super' call.": 2377,
"'get' and 'set' accessor must have the same type.": 2380,
"Constructor implementation is missing.": 2390,
"Function implementation is missing or not immediately following the declaration.": 2391,
Expand All @@ -119,5 +123,7 @@

"File '{0}' not found.": 6054,
"Numeric separators are not allowed here.": 6188,
"Multiple consecutive numeric separators are not permitted.": 6189
"Multiple consecutive numeric separators are not permitted.": 6189,
"'super' must be called before accessing 'this' in the constructor of a derived class.": 17009,
"'super' must be called before accessing a property of 'super' in the constructor of a derived class.": 17011
}
6 changes: 6 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,12 @@ export class Parser extends DiagnosticEmitter {
return Node.createConstructorExpression(tn.range(startPos, tn.pos));
}
case Token.SUPER: {
if (tn.peek() != Token.DOT && tn.nextToken != Token.OPENPAREN) {
this.error(
DiagnosticCode._super_must_be_followed_by_an_argument_list_or_member_access,
tn.range()
);
}
return Node.createSuperExpression(tn.range(startPos, tn.pos));
}
case Token.STRINGLITERAL: {
Expand Down
19 changes: 11 additions & 8 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3041,26 +3041,28 @@ export const enum FlowFlags {
CONTINUES = 1 << 4,
/** This branch always allocates. Constructors only. */
ALLOCATES = 1 << 5,
/** This branch always calls super. Constructors only. */
CALLS_SUPER = 1 << 6,

// conditional

/** This branch conditionally returns in a child branch. */
CONDITIONALLY_RETURNS = 1 << 6,
CONDITIONALLY_RETURNS = 1 << 7,
/** This branch conditionally throws in a child branch. */
CONDITIONALLY_THROWS = 1 << 7,
CONDITIONALLY_THROWS = 1 << 8,
/** This branch conditionally breaks in a child branch. */
CONDITIONALLY_BREAKS = 1 << 8,
CONDITIONALLY_BREAKS = 1 << 9,
/** This branch conditionally continues in a child branch. */
CONDITIONALLY_CONTINUES = 1 << 9,
CONDITIONALLY_CONTINUES = 1 << 10,
/** This branch conditionally allocates in a child branch. Constructors only. */
CONDITIONALLY_ALLOCATES = 1 << 10,
CONDITIONALLY_ALLOCATES = 1 << 11,

// special

/** This branch is part of inlining a function. */
INLINE_CONTEXT = 1 << 11,
INLINE_CONTEXT = 1 << 12,
/** This branch explicitly requests no bounds checking. */
UNCHECKED_CONTEXT = 1 << 12,
UNCHECKED_CONTEXT = 1 << 13,

// masks

Expand All @@ -3076,7 +3078,8 @@ export const enum FlowFlags {
| FlowFlags.THROWS
| FlowFlags.BREAKS
| FlowFlags.CONTINUES
| FlowFlags.ALLOCATES,
| FlowFlags.ALLOCATES
| FlowFlags.CALLS_SUPER,

/** Any conditional flag. */
ANY_CONDITIONAL = FlowFlags.CONDITIONALLY_RETURNS
Expand Down
10 changes: 9 additions & 1 deletion src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1242,8 +1242,16 @@ export class Resolver extends DiagnosticEmitter {

// Lay out fields in advance
case ElementKind.FIELD_PROTOTYPE: {
if (!instance.members) instance.members = new Map();
let fieldDeclaration = (<FieldPrototype>member).declaration;
if (!instance.members) instance.members = new Map();
else if (instance.members.has(member.simpleName)) {
this.error(
DiagnosticCode.Duplicate_identifier_0,
fieldDeclaration.name.range,
member.simpleName
);
break;
}
let fieldType: Type | null = null;
// TODO: handle duplicate non-private fields
if (!fieldDeclaration.type) {
Expand Down
Loading