Skip to content

Commit 0cedeb4

Browse files
author
John Messerly
committed
simplify constructors, fixes #564
we still have an init method, for better or worse, to break out of ES6 restrictions The idea here is default Dart constructor (really an initialization method) is always called `new` instead of the class name. We already do this for unnamed factory. [email protected] Review URL: https://codereview.chromium.org/1965213003 .
1 parent 5901249 commit 0cedeb4

23 files changed

+1189
-1198
lines changed

pkg/dev_compiler/lib/runtime/dart_sdk.js

Lines changed: 999 additions & 1004 deletions
Large diffs are not rendered by default.

pkg/dev_compiler/lib/src/compiler/code_generator.dart

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -754,14 +754,13 @@ class CodeGenerator extends GeneralizingAstVisitor
754754
JS.Statement visitEnumDeclaration(EnumDeclaration node) {
755755
var element = node.element;
756756
var type = element.type;
757-
var name = js.string(type.name);
758757

759758
// Generate a class per section 13 of the spec.
760759
// TODO(vsm): Generate any accompanying metadata
761760

762761
// Create constructor and initialize index
763-
var constructor = new JS.Method(
764-
name, js.call('function(index) { this.index = index; }') as JS.Fun);
762+
var constructor = new JS.Method(_propertyName('new'),
763+
js.call('function(index) { this.index = index; }') as JS.Fun);
765764
var fields = new List<FieldElement>.from(
766765
element.fields.where((f) => f.type == type));
767766

@@ -916,7 +915,28 @@ class CodeGenerator extends GeneralizingAstVisitor
916915
// Iff no constructor is specified for a class C, it implicitly has a
917916
// default constructor `C() : super() {}`, unless C is class Object.
918917
var jsMethods = <JS.Method>[];
919-
if (ctors.isEmpty && !isObject) {
918+
if (isObject) {
919+
// Implements Dart constructor behavior.
920+
//
921+
// Because of ES6 constructor restrictions (`this` is not available until
922+
// `super` is called), we cannot emit an actual ES6 `constructor` on our
923+
// classes and preserve the Dart initialization order.
924+
//
925+
// Instead we use the same trick as named constructors, and do them as
926+
// instance methods that perform initialization.
927+
//
928+
// Therefore, dart:core Object gets the one real `constructor` and
929+
// immediately bounces to the `new() { ... }` initializer, letting us
930+
// bypass the ES6 restrictions.
931+
//
932+
// TODO(jmesserly): we'll need to rethink this.
933+
// See <https://github.com/dart-lang/dev_compiler/issues/51>.
934+
// This level of indirection will hurt performance.
935+
jsMethods.add(new JS.Method(
936+
_propertyName('constructor'),
937+
js.call('function(...args) { return this.new.apply(this, args); }')
938+
as JS.Fun));
939+
} else if (ctors.isEmpty) {
920940
jsMethods.add(_emitImplicitConstructor(node, fields, virtualFields));
921941
}
922942

@@ -1117,7 +1137,7 @@ class CodeGenerator extends GeneralizingAstVisitor
11171137
for (ConstructorDeclaration member in ctors) {
11181138
if (member.name != null && member.factoryKeyword == null) {
11191139
body.add(js.statement('dart.defineNamedConstructor(#, #);',
1120-
[className, _emitMemberName(member.name.name, isStatic: true)]));
1140+
[className, _constructorName(member.element)]));
11211141
}
11221142
}
11231143
}
@@ -1320,15 +1340,14 @@ class CodeGenerator extends GeneralizingAstVisitor
13201340
var superCall = _superConstructorCall(node.element);
13211341
if (fields.isEmpty && superCall == null) return null;
13221342

1323-
dynamic body = _initializeFields(node, fields, virtualFields);
1343+
var initFields = _initializeFields(node, fields, virtualFields);
1344+
List<JS.Statement> body = [initFields];
13241345
if (superCall != null) {
1325-
body = [
1326-
[body, superCall]
1327-
];
1346+
body.add(superCall);
13281347
}
13291348
var name = _constructorName(node.element.unnamedConstructor);
13301349
return annotate(
1331-
new JS.Method(name, js.call('function() { #; }', body) as JS.Fun),
1350+
new JS.Method(name, js.call('function() { #; }', [body]) as JS.Fun),
13321351
node,
13331352
node.element);
13341353
}
@@ -1382,36 +1401,10 @@ class CodeGenerator extends GeneralizingAstVisitor
13821401
}
13831402

13841403
// Code generation for Object's constructor.
1385-
JS.Block body;
1386-
if (isObject &&
1387-
node.body is EmptyFunctionBody &&
1388-
node.constKeyword != null &&
1389-
node.name == null) {
1390-
// Implements Dart constructor behavior. Because of V8 `super`
1391-
// [constructor restrictions]
1392-
// (https://code.google.com/p/v8/issues/detail?id=3330#c65)
1393-
// we cannot currently emit actual ES6 constructors with super calls.
1394-
// Instead we use the same trick as named constructors, and do them as
1395-
// instance methods that perform initialization.
1396-
// TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8
1397-
// settles. See <https://github.com/dart-lang/dev_compiler/issues/51>.
1398-
// Performance of this pattern is likely to be bad.
1399-
name = _propertyName('constructor');
1400-
// Mark the parameter as no-rename.
1401-
body = js.statement('''{
1402-
// Get the class name for this instance.
1403-
let name = this.constructor.name;
1404-
// Call the default constructor.
1405-
let result = void 0;
1406-
if (name in this) result = this[name].apply(this, arguments);
1407-
return result === void 0 ? this : result;
1408-
}''') as JS.Block;
1409-
} else {
1410-
var savedFunction = _currentFunction;
1411-
_currentFunction = node.body;
1412-
body = _emitConstructorBody(node, fields, virtualFields);
1413-
_currentFunction = savedFunction;
1414-
}
1404+
var savedFunction = _currentFunction;
1405+
_currentFunction = node.body;
1406+
var body = _emitConstructorBody(node, fields, virtualFields);
1407+
_currentFunction = savedFunction;
14151408

14161409
// We generate constructors as initializer methods in the class;
14171410
// this allows use of `super` for instance methods/properties.
@@ -1424,16 +1417,11 @@ class CodeGenerator extends GeneralizingAstVisitor
14241417

14251418
JS.Expression _constructorName(ConstructorElement ctor) {
14261419
var name = ctor.name;
1427-
if (name != '') {
1428-
return _emitMemberName(name, isStatic: true);
1420+
if (name == '') {
1421+
// Default constructors (factory or not) use `new` as their name.
1422+
return _propertyName('new');
14291423
}
1430-
1431-
// Factory default constructors use `new` as their name, for readability
1432-
// Other default constructors use the class name, as they aren't called
1433-
// from call sites, but rather from Object's constructor.
1434-
// TODO(jmesserly): revisit in the context of Dart metaclasses, and cleaning
1435-
// up constructors to integrate more closely with ES6.
1436-
return _propertyName(ctor.isFactory ? 'new' : ctor.enclosingElement.name);
1424+
return _emitMemberName(name, isStatic: true);
14371425
}
14381426

14391427
JS.Block _emitConstructorBody(
@@ -1487,30 +1475,40 @@ class CodeGenerator extends GeneralizingAstVisitor
14871475
@override
14881476
JS.Statement visitRedirectingConstructorInvocation(
14891477
RedirectingConstructorInvocation node) {
1490-
var name = _constructorName(node.staticElement);
1491-
return js.statement('this.#(#);', [name, _visit(node.argumentList)]);
1478+
var ctor = node.staticElement;
1479+
var cls = ctor.enclosingElement as ClassElement;
1480+
// We can't dispatch to the constructor with `this.new` as that might hit a
1481+
// derived class constructor with the same name.
1482+
return js.statement('#.prototype.#.call(this, #);', [
1483+
new JS.Identifier(cls.name),
1484+
_constructorName(ctor),
1485+
_visit(node.argumentList)
1486+
]);
14921487
}
14931488

14941489
JS.Statement _superConstructorCall(ClassElement element,
14951490
[SuperConstructorInvocation node]) {
1491+
if (element.supertype == null) {
1492+
assert(element.type.isObject || options.unsafeForceCompile);
1493+
return null;
1494+
}
1495+
14961496
ConstructorElement superCtor;
14971497
if (node != null) {
14981498
superCtor = node.staticElement;
14991499
} else {
15001500
// Get the supertype's unnamed constructor.
15011501
superCtor = element.supertype.element.unnamedConstructor;
1502-
if (superCtor == null) {
1503-
// This will only happen if the code has errors:
1504-
// we're trying to generate an implicit constructor for a type where
1505-
// we don't have a default constructor in the supertype.
1506-
assert(options.unsafeForceCompile);
1507-
return null;
1508-
}
15091502
}
15101503

15111504
if (superCtor == null) {
1512-
print('Error generating: ${element.displayName}');
1505+
// This will only happen if the code has errors:
1506+
// we're trying to generate an implicit constructor for a type where
1507+
// we don't have a default constructor in the supertype.
1508+
assert(options.unsafeForceCompile);
1509+
return null;
15131510
}
1511+
15141512
if (superCtor.name == '' && !_shouldCallUnnamedSuperCtor(element)) {
15151513
return null;
15161514
}
@@ -3071,7 +3069,6 @@ class CodeGenerator extends GeneralizingAstVisitor
30713069
JS.Expression emitNew() {
30723070
JS.Expression ctor;
30733071
bool isFactory = false;
3074-
// var element = node.staticElement;
30753072
if (element == null) {
30763073
// TODO(jmesserly): this only happens if we had a static error.
30773074
// Should we generate a throw instead?

pkg/dev_compiler/test/browser/language_tests.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@
470470
// newer SDKs.
471471
'html_escape_test': skip_fail,
472472

473+
// TODO(jmesserly): seems to fail or pass based on SDK version?
474+
'html_escape_test': ['skip'],
475+
473476
// TODO(rnystrom): If this test is enabled, karma gets confused and
474477
// disconnects randomly.
475478
'json_lib_test': skip_fail,

pkg/dev_compiler/test/browser/runtime_tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ suite('instanceOf', () => {
531531

532532
test('dsend', () => {
533533
class Tester extends core.Object {
534-
Tester() {
534+
new() {
535535
this.f = dart.fn(x => x, core.int, [core.int]);
536536
this.me = this;
537537
}

pkg/dev_compiler/test/codegen/expect/BenchmarkBase.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ dart_library.library('BenchmarkBase', null, /* Imports */[
3333
names: ['equals', 'listEquals']
3434
});
3535
BenchmarkBase$.BenchmarkBase = class BenchmarkBase extends core.Object {
36-
BenchmarkBase(name) {
36+
new(name) {
3737
this.name = name;
3838
}
3939
run() {}
@@ -77,7 +77,7 @@ dart_library.library('BenchmarkBase', null, /* Imports */[
7777
}
7878
};
7979
dart.setSignature(BenchmarkBase$.BenchmarkBase, {
80-
constructors: () => ({BenchmarkBase: [BenchmarkBase$.BenchmarkBase, [core.String]]}),
80+
constructors: () => ({new: [BenchmarkBase$.BenchmarkBase, [core.String]]}),
8181
methods: () => ({
8282
run: [dart.void, []],
8383
warmup: [dart.void, []],

0 commit comments

Comments
 (0)