Index: pkg/dev_compiler/lib/src/compiler/code_generator.dart |
diff --git a/pkg/dev_compiler/lib/src/compiler/code_generator.dart b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
index c52c77cffd8e3465c146b7d23ad4c4b93164939b..e9e17f69c49d497e846e5434f5d2318493cf1c7d 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
@@ -799,69 +799,49 @@ class CodeGenerator extends Object |
@override |
JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
- ClassElement element = node.element; |
- var supertype = element.supertype; |
- |
- // Forward all generative constructors from the base class. |
- var methods = <JS.Method>[]; |
- if (!supertype.isObject) { |
- for (var ctor in element.constructors) { |
- var parentCtor = supertype.lookUpConstructor(ctor.name, ctor.library); |
- // TODO(jmesserly): this avoids spread args for perf. Revisit. |
- var jsParams = <JS.Identifier>[]; |
- for (var p in ctor.parameters) { |
- if (p.parameterKind != ParameterKind.NAMED) { |
- jsParams.add(new JS.Identifier(p.name)); |
- } else { |
- jsParams.add(new JS.TemporaryId('namedArgs')); |
- break; |
- } |
- } |
- var fun = js.call('function(#) { super.#(#); }', |
- [jsParams, _constructorName(parentCtor), jsParams]) as JS.Fun; |
- methods.add(new JS.Method(_constructorName(ctor), fun)); |
- } |
- } |
+ ClassElement classElem = node.element; |
+ var supertype = classElem.supertype; |
- var typeFormals = element.typeParameters; |
+ var typeFormals = classElem.typeParameters; |
var isGeneric = typeFormals.isNotEmpty; |
- var className = isGeneric ? element.name : _emitTopLevelName(element); |
- JS.Statement declareInterfaces(JS.Statement decl) { |
- if (element.interfaces.isNotEmpty) { |
- var body = [decl]..add(js.statement('#[#.implements] = () => #;', [ |
- className, |
- _runtimeModule, |
- new JS.ArrayInitializer( |
- new List<JS.Expression>.from(element.interfaces.map(_emitType))) |
- ])); |
- decl = _statement(body); |
- } |
- return decl; |
- } |
- if (supertype.isObject && element.mixins.length == 1) { |
- // Special case where supertype is Object, and we mixin a single class. |
- // The resulting 'class' is a mixable class in this case. |
- var classExpr = _emitClassHeritage(element); |
- if (isGeneric) { |
- var classStmt = js.statement('const # = #;', [className, classExpr]); |
- return _defineClassTypeArguments( |
- element, typeFormals, declareInterfaces(classStmt)); |
+ // Special case where supertype is Object, and we mixin a single class. |
+ // The resulting 'class' is a mixable class in this case. |
+ bool isMixinAlias = supertype.isObject && classElem.mixins.length == 1; |
+ |
+ var classExpr = isMixinAlias |
+ ? _emitClassHeritage(classElem) |
+ : _emitClassExpression(classElem, []); |
+ var className = isGeneric |
+ ? new JS.Identifier(classElem.name) |
+ : _emitTopLevelName(classElem); |
+ var block = <JS.Statement>[]; |
+ |
+ if (isGeneric) { |
+ if (isMixinAlias) { |
+ block.add(js.statement('const # = #;', [className, classExpr])); |
} else { |
- var classStmt = js.statement('# = #;', [className, classExpr]); |
- return declareInterfaces(classStmt); |
+ block.add(new JS.ClassDeclaration(classExpr)); |
} |
+ } else { |
+ block.add(js.statement('# = #;', [className, classExpr])); |
+ } |
+ |
+ if (!isMixinAlias) _defineConstructors(classElem, className, [], block, []); |
+ |
+ if (classElem.interfaces.isNotEmpty) { |
+ block.add(js.statement('#[#.implements] = () => #;', [ |
+ className, |
+ _runtimeModule, |
+ new JS.ArrayInitializer(classElem.interfaces.map(_emitType).toList()) |
+ ])); |
} |
- var classExpr = _emitClassExpression(element, methods); |
if (isGeneric) { |
- var classStmt = new JS.ClassDeclaration(classExpr); |
return _defineClassTypeArguments( |
- element, typeFormals, declareInterfaces(classStmt)); |
- } else { |
- var classStmt = js.statement('# = #;', [className, classExpr]); |
- return declareInterfaces(classStmt); |
+ classElem, typeFormals, _statement(block)); |
} |
+ return _statement(block); |
} |
JS.Statement _emitJsType(Element e) { |
@@ -892,9 +872,6 @@ class CodeGenerator extends Object |
var staticFields = <FieldDeclaration>[]; |
var methods = <MethodDeclaration>[]; |
- // True if a "call" method or getter exists directly on this class. |
- // If so, we need to install a Function prototype. |
- bool isCallable = false; |
for (var member in node.members) { |
if (member is ConstructorDeclaration) { |
ctors.add(member); |
@@ -903,35 +880,9 @@ class CodeGenerator extends Object |
(member.isStatic ? staticFields : fields).add(member); |
} else if (member is MethodDeclaration) { |
methods.add(member); |
- if (member.name.name == 'call' && !member.isSetter) { |
- // |
- // Make sure "call" has a statically known function type: |
- // |
- // - if it's a method, then it does because all methods do, |
- // - if it's a getter, check the return type. |
- // |
- // Other cases like a getter returning dynamic/Object/Function will be |
- // handled at runtime by the dynamic call mechanism. So we only |
- // concern ourselves with statically known function types. |
- // |
- // For the same reason, we can ignore "noSuchMethod". |
- // call-implemented-by-nSM will be dispatched by dcall at runtime. |
- // |
- isCallable = !member.isGetter || member.returnType is FunctionType; |
- } |
} |
} |
- // True if a "call" method or getter exists directly or indirectly on this |
- // class. If so, we need special constructor handling. |
- bool isCallableTransitive = |
- classElem.lookUpMethod('call', currentLibrary) != null; |
- if (!isCallableTransitive) { |
- var callGetter = classElem.lookUpGetter('call', currentLibrary); |
- isCallableTransitive = |
- callGetter != null && callGetter.returnType is FunctionType; |
- } |
- |
JS.Expression className; |
if (classElem.typeParameters.isNotEmpty) { |
// Generic classes will be defined inside a function that closes over the |
@@ -945,8 +896,7 @@ class CodeGenerator extends Object |
_classProperties = |
new ClassPropertyModel.build(_extensionTypes, virtualFields, classElem); |
- var classExpr = _emitClassExpression( |
- classElem, _emitClassMethods(node, ctors, fields), |
+ var classExpr = _emitClassExpression(classElem, _emitClassMethods(node), |
fields: allFields); |
var body = <JS.Statement>[]; |
@@ -954,7 +904,8 @@ class CodeGenerator extends Object |
_emitSuperHelperSymbols(_superHelperSymbols, body); |
// Emit the class, e.g. `core.Object = class Object { ... }` |
- _defineClass(classElem, className, classExpr, isCallable, body); |
+ _defineClass(classElem, className, classExpr, body); |
+ _defineConstructors(classElem, className, fields, body, ctors); |
// Emit things that come after the ES6 `class ... { ... }`. |
var jsPeerNames = _getJSPeerNames(classElem); |
@@ -963,7 +914,6 @@ class CodeGenerator extends Object |
_emitClassTypeTests(classElem, className, body); |
- _defineNamedConstructors(ctors, body, className, isCallableTransitive); |
_emitVirtualFieldSymbols(classElem, body); |
_emitClassSignature(methods, allFields, classElem, ctors, className, body); |
_defineExtensionMembers(className, body); |
@@ -987,35 +937,6 @@ class CodeGenerator extends Object |
return _statement(body); |
} |
- /// Emits code to support a class with a "call" method and an unnamed |
- /// constructor. |
- /// |
- /// This ensures instances created by the unnamed constructor are functions. |
- /// Named constructors are handled elsewhere, see [_defineNamedConstructors]. |
- JS.Expression _emitCallableClass( |
- JS.ClassExpression classExpr, ConstructorElement unnamedCtor) { |
- var ctor = new JS.NamedFunction( |
- classExpr.name, _emitCallableClassConstructor(unnamedCtor)); |
- |
- // Name the constructor function the same as the class. |
- return _callHelper('callableClass(#, #)', [ctor, classExpr]); |
- } |
- |
- /// Emits a constructor that ensures instances of this class are callable as |
- /// functions in JavaScript. |
- JS.Fun _emitCallableClassConstructor(ConstructorElement ctor) { |
- return js.call( |
- r'''function (...args) { |
- function call(...args) { |
- return call.call.apply(call, args); |
- } |
- call.__proto__ = this.__proto__; |
- call.#.apply(call, args); |
- return call; |
- }''', |
- [_constructorName(ctor)]); |
- } |
- |
void _emitClassTypeTests(ClassElement classElem, JS.Expression className, |
List<JS.Statement> body) { |
if (classElem == objectClass) { |
@@ -1229,21 +1150,11 @@ class CodeGenerator extends Object |
} |
void _defineClass(ClassElement classElem, JS.Expression className, |
- JS.ClassExpression classExpr, bool isCallable, List<JS.Statement> body) { |
- JS.Expression callableClass; |
- if (isCallable && classElem.unnamedConstructor != null) { |
- callableClass = |
- _emitCallableClass(classExpr, classElem.unnamedConstructor); |
- } |
- |
+ JS.ClassExpression classExpr, List<JS.Statement> body) { |
if (classElem.typeParameters.isNotEmpty) { |
- if (callableClass != null) { |
- body.add(js.statement('const # = #;', [classExpr.name, callableClass])); |
- } else { |
- body.add(new JS.ClassDeclaration(classExpr)); |
- } |
+ body.add(new JS.ClassDeclaration(classExpr)); |
} else { |
- body.add(js.statement('# = #;', [className, callableClass ?? classExpr])); |
+ body.add(js.statement('# = #;', [className, classExpr])); |
} |
} |
@@ -1278,11 +1189,7 @@ class CodeGenerator extends Object |
// Generate a class per section 13 of the spec. |
// TODO(vsm): Generate any accompanying metadata |
- // Create constructor and initialize index |
- var constructor = new JS.Method(_propertyName('new'), |
- js.call('function(index) { this.index = index; }') as JS.Fun); |
- var fields = new List<FieldElement>.from( |
- element.fields.where((f) => f.type == type)); |
+ var fields = element.fields.where((f) => f.type == type).toList(); |
// Create toString() method |
var nameProperties = new List<JS.Property>(fields.length); |
@@ -1295,8 +1202,8 @@ class CodeGenerator extends Object |
js.call('function() { return #[this.index]; }', nameMap) as JS.Fun); |
// Create enum class |
- var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
- _emitClassHeritage(element), [constructor, toStringF]); |
+ var classExpr = new JS.ClassExpression( |
+ new JS.Identifier(type.name), _emitClassHeritage(element), [toStringF]); |
var id = _emitTopLevelName(element); |
// Emit metadata for synthetic enum index member. |
@@ -1311,6 +1218,9 @@ class CodeGenerator extends Object |
var result = [ |
js.statement('# = #', [id, classExpr]), |
+ js.statement( |
+ '(#.new = function(x) { this.index = x; }).prototype = #.prototype;', |
+ [id, id]), |
_callHelperStatement('setSignature(#, #);', [id, sig]) |
]; |
@@ -1451,48 +1361,32 @@ class CodeGenerator extends Object |
return jsMethods; |
} |
- List<JS.Method> _emitClassMethods(ClassDeclaration node, |
- List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
+ List<JS.Method> _emitClassMethods(ClassDeclaration node) { |
var element = resolutionMap.elementDeclaredByClassDeclaration(node); |
var type = element.type; |
- var isObject = type.isObject; |
var virtualFields = _classProperties.virtualFields; |
- // Iff no constructor is specified for a class C, it implicitly has a |
- // default constructor `C() : super() {}`, unless C is class Object. |
var jsMethods = <JS.Method>[]; |
- if (isObject) { |
- // Implements Dart constructor behavior. |
- // |
- // Because of ES6 constructor restrictions (`this` is not available until |
- // `super` is called), we cannot emit an actual ES6 `constructor` on our |
- // classes and preserve the Dart initialization order. |
- // |
- // Instead we use the same trick as named constructors, and do them as |
- // instance methods that perform initialization. |
- // |
- // Therefore, dart:core Object gets the one real `constructor` and |
- // immediately bounces to the `new() { ... }` initializer, letting us |
- // bypass the ES6 restrictions. |
- // |
- // TODO(jmesserly): we'll need to rethink this. |
- // See https://github.com/dart-lang/sdk/issues/28322. |
- // This level of indirection will hurt performance. |
+ bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null; |
+ bool hasIterator = false; |
+ |
+ if (type.isObject) { |
+ // Dart does not use ES6 constructors. |
+ // Add an error to catch any invalid usage. |
jsMethods.add(new JS.Method( |
_propertyName('constructor'), |
- js.call('function(...args) { return this.new.apply(this, args); }') |
- as JS.Fun)); |
- } else if (ctors.isEmpty) { |
- jsMethods.add(_emitImplicitConstructor(node, fields, virtualFields)); |
+ js.call( |
+ r'''function() { |
+ throw Error("use `new " + #.typeName(#.getReifiedType(this)) + |
+ ".new(...)` to create a Dart object"); |
+ }''', |
+ [_runtimeModule, _runtimeModule]))); |
} |
- |
- bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null; |
- |
- bool hasIterator = false; |
for (var m in node.members) { |
if (m is ConstructorDeclaration) { |
- jsMethods |
- .add(_emitConstructor(m, type, fields, virtualFields, isObject)); |
+ if (m.factoryKeyword != null && !_externalOrNative(m)) { |
+ jsMethods.add(_emitFactoryConstructor(m)); |
+ } |
} else if (m is MethodDeclaration) { |
jsMethods.add(_emitMethodDeclaration(type, m)); |
@@ -1540,6 +1434,43 @@ class CodeGenerator extends Object |
return jsMethods.where((m) => m != null).toList(growable: false); |
} |
+ /// Emits a Dart factory constructor to a JS static method. |
+ JS.Method _emitFactoryConstructor(ConstructorDeclaration node) { |
+ var element = node.element; |
+ var returnType = emitTypeRef(element.returnType); |
+ var name = _constructorName(element); |
+ JS.Fun fun; |
+ |
+ var redirect = node.redirectedConstructor; |
+ if (redirect != null) { |
+ // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
+ |
+ var newKeyword = redirect.staticElement.isFactory ? '' : 'new'; |
+ // Pass along all arguments verbatim, and let the callee handle them. |
+ // TODO(jmesserly): we'll need something different once we have |
+ // rest/spread support, but this should work for now. |
+ var params = |
+ _emitFormalParameterList(node.parameters, destructure: false); |
+ |
+ fun = new JS.Fun( |
+ params, |
+ js.statement( |
+ '{ return $newKeyword #(#); }', [_visit(redirect), params]), |
+ returnType: returnType); |
+ } else { |
+ // Normal factory constructor |
+ var body = <JS.Statement>[]; |
+ var init = _emitArgumentInitializers(node, constructor: true); |
+ if (init != null) body.add(init); |
+ body.add(_visit(node.body)); |
+ |
+ var params = _emitFormalParameterList(node.parameters); |
+ fun = new JS.Fun(params, new JS.Block(body), returnType: returnType); |
+ } |
+ |
+ return annotate(new JS.Method(name, fun, isStatic: true), node, element); |
+ } |
+ |
/// Given a class C that implements method M from interface I, but does not |
/// declare M, this will generate an implementation that forwards to |
/// noSuchMethod. |
@@ -1557,7 +1488,7 @@ class CodeGenerator extends Object |
/// |
/// eatFood(...args) { |
/// return core.bool.as(this.noSuchMethod( |
- /// new dart.InvocationImpl('eatFood', args))); |
+ /// new dart.InvocationImpl.new('eatFood', args))); |
/// } |
JS.Method _implementMockMember(ExecutableElement method, InterfaceType type) { |
var invocationProps = <JS.Property>[]; |
@@ -1592,7 +1523,8 @@ class CodeGenerator extends Object |
} |
} |
- var fnBody = js.call('this.noSuchMethod(new #.InvocationImpl(#, #, #))', [ |
+ var fnBody = |
+ js.call('this.noSuchMethod(new #.InvocationImpl.new(#, #, #))', [ |
_runtimeModule, |
_declareMemberName(method, useDisplayName: true), |
positionalArgs, |
@@ -1782,21 +1714,73 @@ class CodeGenerator extends Object |
return null; |
} |
- void _defineNamedConstructors(List<ConstructorDeclaration> ctors, |
- List<JS.Statement> body, JS.Expression className, bool isCallable) { |
- var code = isCallable |
- ? 'defineNamedConstructorCallable(#, #, #);' |
- : 'defineNamedConstructor(#, #)'; |
+ /// Defines all constructors for this class as ES5 constructors. |
+ void _defineConstructors( |
+ ClassElement classElem, |
+ JS.Expression className, |
+ List<FieldDeclaration> fields, |
+ List<JS.Statement> body, |
+ List<ConstructorDeclaration> ctors) { |
+ // See if we have a "call" with a statically known function type: |
+ // |
+ // - if it's a method, then it does because all methods do, |
+ // - if it's a getter, check the return type. |
+ // |
+ // Other cases like a getter returning dynamic/Object/Function will be |
+ // handled at runtime by the dynamic call mechanism. So we only |
+ // concern ourselves with statically known function types. |
+ // |
+ // For the same reason, we can ignore "noSuchMethod". |
+ // call-implemented-by-nSM will be dispatched by dcall at runtime. |
+ bool isCallable = classElem.lookUpMethod('call', null) != null || |
+ classElem.lookUpGetter('call', null)?.returnType is FunctionType; |
+ |
+ void addConstructor(ConstructorElement element, JS.Expression jsCtor) { |
+ var ctorName = _constructorName(element); |
+ if (JS.invalidStaticFieldName(element.name)) { |
+ jsCtor = |
+ _callHelper('defineValue(#, #, #)', [className, ctorName, jsCtor]); |
+ } else { |
+ jsCtor = js.call('#.# = #', [className, ctorName, jsCtor]); |
+ } |
+ body.add(js.statement('#.prototype = #.prototype;', [jsCtor, className])); |
+ } |
+ |
+ if (classElem.isMixinApplication) { |
+ var supertype = classElem.supertype; |
+ for (var ctor in classElem.constructors) { |
+ List<JS.Identifier> jsParams = _emitParametersForElement(ctor); |
+ var superCtor = supertype.lookUpConstructor(ctor.name, ctor.library); |
+ var superCall = |
+ _superConstructorCall(classElem, className, superCtor, jsParams); |
+ addConstructor( |
+ ctor, |
+ _finishConstructorFunction( |
+ jsParams, |
+ new JS.Block(superCall != null ? [superCall] : []), |
+ isCallable)); |
+ } |
+ return; |
+ } |
+ |
+ // Iff no constructor is specified for a class C, it implicitly has a |
+ // default constructor `C() : super() {}`, unless C is class Object. |
+ if (ctors.isEmpty) { |
+ var superCall = _superConstructorCall(classElem, className); |
+ List<JS.Statement> body = [_initializeFields(fields)]; |
+ if (superCall != null) body.add(superCall); |
- for (ConstructorDeclaration member in ctors) { |
- if (member.name != null && member.factoryKeyword == null) { |
- var args = [className, _constructorName(member.element)]; |
- if (isCallable) { |
- args.add(_emitCallableClassConstructor(member.element)); |
- } |
+ addConstructor(classElem.unnamedConstructor, |
+ _finishConstructorFunction([], new JS.Block(body), isCallable)); |
+ return; |
+ } |
- body.add(_callHelperStatement(code, args)); |
- } |
+ for (var ctor in ctors) { |
+ var element = ctor.element; |
+ if (element.isFactory || _externalOrNative(ctor)) continue; |
+ |
+ addConstructor( |
+ element, _emitConstructor(ctor, fields, isCallable, className)); |
} |
} |
@@ -1975,9 +1959,8 @@ class CodeGenerator extends Object |
var tCtors = <JS.Property>[]; |
if (options.emitMetadata) { |
for (ConstructorDeclaration node in ctors) { |
- var memberName = _constructorName(node.element); |
- var element = |
- resolutionMap.elementDeclaredByConstructorDeclaration(node); |
+ var element = node.element; |
+ var memberName = _constructorName(element); |
var type = _emitAnnotatedFunctionType(element.type, node.metadata, |
parameters: node.parameters.parameters, |
nameType: options.hoistSignatureTypes, |
@@ -2052,92 +2035,41 @@ class CodeGenerator extends Object |
} |
} |
- /// Generates the implicit default constructor for class C of the form |
- /// `C() : super() {}`. |
- JS.Method _emitImplicitConstructor( |
- ClassDeclaration node, |
- List<FieldDeclaration> fields, |
- Map<FieldElement, JS.TemporaryId> virtualFields) { |
- // If we don't have a method body, skip this. |
- var superCall = _superConstructorCall(node.element); |
- if (fields.isEmpty && superCall == null) return null; |
- |
- var initFields = _initializeFields(node, fields, virtualFields); |
- List<JS.Statement> body = [initFields]; |
- if (superCall != null) { |
- body.add(superCall); |
- } |
- var name = _constructorName(resolutionMap |
- .elementDeclaredByClassDeclaration(node) |
- .unnamedConstructor); |
- return annotate( |
- new JS.Method(name, js.call('function() { #; }', [body]) as JS.Fun), |
- node, |
- node.element); |
- } |
- |
- JS.Method _emitConstructor( |
- ConstructorDeclaration node, |
- InterfaceType type, |
- List<FieldDeclaration> fields, |
- Map<FieldElement, JS.TemporaryId> virtualFields, |
- bool isObject) { |
- if (_externalOrNative(node)) return null; |
- |
- var name = _constructorName(node.element); |
- var returnType = emitTypeRef(resolutionMap |
- .elementDeclaredByConstructorDeclaration(node) |
- .enclosingElement |
- .type); |
- |
- // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
- var redirect = node.redirectedConstructor; |
- if (redirect != null) { |
- var newKeyword = |
- resolutionMap.staticElementForConstructorReference(redirect).isFactory |
- ? '' |
- : 'new'; |
- // Pass along all arguments verbatim, and let the callee handle them. |
- // TODO(jmesserly): we'll need something different once we have |
- // rest/spread support, but this should work for now. |
- var params = |
- _emitFormalParameterList(node.parameters, destructure: false); |
- |
- var fun = new JS.Fun( |
- params, |
- js.statement( |
- '{ return $newKeyword #(#); }', [_visit(redirect), params]), |
- returnType: returnType); |
- return annotate( |
- new JS.Method(name, fun, isStatic: true), node, node.element); |
- } |
- |
+ JS.Expression _emitConstructor(ConstructorDeclaration node, |
+ List<FieldDeclaration> fields, bool isCallable, JS.Expression className) { |
var params = _emitFormalParameterList(node.parameters); |
- // Factory constructors are essentially static methods. |
- if (node.factoryKeyword != null) { |
- var body = <JS.Statement>[]; |
- var init = _emitArgumentInitializers(node, constructor: true); |
- if (init != null) body.add(init); |
- body.add(_visit(node.body)); |
- var fun = new JS.Fun(params, new JS.Block(body), returnType: returnType); |
- return annotate( |
- new JS.Method(name, fun, isStatic: true), node, node.element); |
- } |
- |
- // Code generation for Object's constructor. |
var savedFunction = _currentFunction; |
_currentFunction = node.body; |
- var body = _emitConstructorBody(node, fields, virtualFields); |
+ |
+ var savedSuperAllowed = _superAllowed; |
+ _superAllowed = false; |
+ var body = _emitConstructorBody(node, fields, className); |
+ _superAllowed = savedSuperAllowed; |
_currentFunction = savedFunction; |
- // We generate constructors as initializer methods in the class; |
- // this allows use of `super` for instance methods/properties. |
- // It also avoids V8 restrictions on `super` in default constructors. |
- return annotate( |
- new JS.Method(name, new JS.Fun(params, body, returnType: returnType)), |
- node, |
- node.element); |
+ return _finishConstructorFunction(params, body, isCallable); |
+ } |
+ |
+ JS.Expression _finishConstructorFunction( |
+ List<JS.Parameter> params, JS.Block body, isCallable) { |
+ // We consider a class callable if it inherits from anything with a `call` |
+ // method. As a result, we can know the callable JS function was created |
+ // at the first constructor that was hit. |
+ if (!isCallable) return new JS.Fun(params, body); |
+ return js.call( |
+ r'''function callableClass(#) { |
+ if (typeof this !== "function") { |
+ function self(...args) { |
+ return self.call.apply(self, args); |
+ } |
+ self.__proto__ = this.__proto__; |
+ callableClass.call(self, #); |
+ return self; |
+ } |
+ # |
+ }''', |
+ [params, params, body]); |
} |
JS.Expression _constructorName(ConstructorElement ctor) { |
@@ -2149,10 +2081,8 @@ class CodeGenerator extends Object |
return _emitMemberName(name, isStatic: true); |
} |
- JS.Block _emitConstructorBody( |
- ConstructorDeclaration node, |
- List<FieldDeclaration> fields, |
- Map<FieldElement, JS.TemporaryId> virtualFields) { |
+ JS.Block _emitConstructorBody(ConstructorDeclaration node, |
+ List<FieldDeclaration> fields, JS.Expression className) { |
var body = <JS.Statement>[]; |
ClassDeclaration cls = node.parent; |
@@ -2171,7 +2101,7 @@ class CodeGenerator extends Object |
orElse: () => null); |
if (redirectCall != null) { |
- body.add(_visit(redirectCall)); |
+ body.add(_emitRedirectingConstructor(redirectCall, className)); |
return new JS.Block(body); |
} |
@@ -2179,7 +2109,7 @@ class CodeGenerator extends Object |
// These are expanded into each non-redirecting constructor. |
// In the future we may want to create an initializer function if we have |
// multiple constructors, but it needs to be balanced against readability. |
- body.add(_initializeFields(cls, fields, virtualFields, node)); |
+ body.add(_initializeFields(fields, node)); |
var superCall = node.initializers.firstWhere( |
(i) => i is SuperConstructorInvocation, |
@@ -2188,57 +2118,47 @@ class CodeGenerator extends Object |
// If no superinitializer is provided, an implicit superinitializer of the |
// form `super()` is added at the end of the initializer list, unless the |
// enclosing class is class Object. |
- var jsSuper = _superConstructorCall(cls.element, superCall); |
- if (jsSuper != null) body.add(jsSuper); |
+ var superCallArgs = |
+ superCall != null ? _emitArgumentList(superCall.argumentList) : null; |
+ var jsSuper = _superConstructorCall( |
+ cls.element, className, superCall?.staticElement, superCallArgs); |
+ if (jsSuper != null) body.add(annotate(jsSuper, superCall)); |
body.add(_visit(node.body)); |
return new JS.Block(body)..sourceInformation = node; |
} |
- @override |
- JS.Statement visitRedirectingConstructorInvocation( |
- RedirectingConstructorInvocation node) { |
- var ctor = resolutionMap.staticElementForConstructorReference(node); |
- var cls = ctor.enclosingElement; |
+ JS.Statement _emitRedirectingConstructor( |
+ RedirectingConstructorInvocation node, JS.Expression className) { |
+ var ctor = node.staticElement; |
// We can't dispatch to the constructor with `this.new` as that might hit a |
// derived class constructor with the same name. |
- return js.statement('#.prototype.#.call(this, #);', [ |
- new JS.Identifier(cls.name), |
+ return js.statement('#.#.call(this, #);', [ |
+ className, |
_constructorName(ctor), |
_emitArgumentList(node.argumentList) |
]); |
} |
- JS.Statement _superConstructorCall(ClassElement element, |
- [SuperConstructorInvocation node]) { |
- if (element.supertype == null) { |
- assert(element.type.isObject || options.unsafeForceCompile); |
- return null; |
- } |
- |
- ConstructorElement superCtor; |
- if (node != null) { |
- superCtor = node.staticElement; |
- } else { |
- // Get the supertype's unnamed constructor. |
- superCtor = element.supertype.element.unnamedConstructor; |
- } |
- |
+ JS.Statement _superConstructorCall( |
+ ClassElement element, JS.Expression className, |
+ [ConstructorElement superCtor, List<JS.Expression> args]) { |
+ // Get the supertype's unnamed constructor. |
+ superCtor ??= element.supertype?.element?.unnamedConstructor; |
if (superCtor == null) { |
- // This will only happen if the code has errors: |
- // we're trying to generate an implicit constructor for a type where |
- // we don't have a default constructor in the supertype. |
- assert(options.unsafeForceCompile); |
+ assert(element.type.isObject || options.unsafeForceCompile); |
return null; |
} |
+ // We can skip the super call if it's empty. Typically this happens for |
+ // things that extend Object. |
if (superCtor.name == '' && !_hasUnnamedSuperConstructor(element)) { |
return null; |
} |
var name = _constructorName(superCtor); |
- var args = node != null ? _emitArgumentList(node.argumentList) : []; |
- return annotate(js.statement('super.#(#);', [name, args]), node); |
+ return js.statement( |
+ '#.__proto__.#.call(this, #);', [className, name, args ?? []]); |
} |
bool _hasUnnamedSuperConstructor(ClassElement e) { |
@@ -2264,10 +2184,7 @@ class CodeGenerator extends Object |
/// 2. field initializing parameters, |
/// 3. constructor field initializers, |
/// 4. initialize fields not covered in 1-3 |
- JS.Statement _initializeFields( |
- ClassDeclaration cls, |
- List<FieldDeclaration> fieldDecls, |
- Map<FieldElement, JS.TemporaryId> virtualFields, |
+ JS.Statement _initializeFields(List<FieldDeclaration> fieldDecls, |
[ConstructorDeclaration ctor]) { |
// Run field initializers if they can have side-effects. |
var fields = new Map<FieldElement, JS.Expression>(); |
@@ -2320,7 +2237,8 @@ class CodeGenerator extends Object |
var body = <JS.Statement>[]; |
fields.forEach((FieldElement e, JS.Expression initialValue) { |
- JS.Expression access = virtualFields[e] ?? _declareMemberName(e.getter); |
+ JS.Expression access = |
+ _classProperties.virtualFields[e] ?? _declareMemberName(e.getter); |
body.add(js.statement('this.# = #;', [access, initialValue])); |
}); |
@@ -2802,7 +2720,8 @@ class CodeGenerator extends Object |
JS.Expression _emitSimpleIdentifier(SimpleIdentifier node) { |
var accessor = resolutionMap.staticElementForIdentifier(node); |
if (accessor == null) { |
- return _callHelper('throw("compile error: unresolved identifier: " + #)', |
+ return _callHelper( |
+ 'throw(Error("compile error: unresolved identifier: " + #))', |
js.escapedString(node.name ?? '<null>')); |
} |
@@ -3807,6 +3726,19 @@ class CodeGenerator extends Object |
_propertyName(node.name.label.name), _visit(node.expression)); |
} |
+ List<JS.Parameter> _emitParametersForElement(ExecutableElement member) { |
+ var jsParams = <JS.Identifier>[]; |
+ for (var p in member.parameters) { |
+ if (p.parameterKind != ParameterKind.NAMED) { |
+ jsParams.add(new JS.Identifier(p.name)); |
+ } else { |
+ jsParams.add(new JS.TemporaryId('namedArgs')); |
+ break; |
+ } |
+ } |
+ return jsParams; |
+ } |
+ |
List<JS.Parameter> _emitFormalParameterList(FormalParameterList node, |
{bool destructure: true}) { |
if (node == null) return []; |
@@ -4065,15 +3997,9 @@ class CodeGenerator extends Object |
JS.Expression _emitConstructorName( |
ConstructorElement element, DartType type, SimpleIdentifier name) { |
- var classElem = element.enclosingElement; |
- var interop = _emitJSInterop(classElem); |
- if (interop != null) return interop; |
- var typeName = _emitConstructorAccess(type); |
- if (name != null || element.isFactory) { |
- var namedCtor = _constructorName(element); |
- return new JS.PropertyAccess(typeName, namedCtor); |
- } |
- return typeName; |
+ return _emitJSInterop(type.element) ?? |
+ new JS.PropertyAccess( |
+ _emitConstructorAccess(type), _constructorName(element)); |
} |
@override |
@@ -4093,10 +4019,12 @@ class CodeGenerator extends Object |
bool isNative = false; |
if (element == null) { |
ctor = _callHelper( |
- 'throw("compile error: unresolved constructor: " + # + "." + #)', [ |
- js.escapedString(type?.name ?? '<null>'), |
- js.escapedString(name?.name ?? '<unnamed>') |
- ]); |
+ 'throw(Error("compile error: unresolved constructor: " ' |
+ '+ # + "." + #))', |
+ [ |
+ js.escapedString(type?.name ?? '<null>'), |
+ js.escapedString(name?.name ?? '<unnamed>') |
+ ]); |
} else { |
ctor = _emitConstructorName(element, type, name); |
isFactory = element.isFactory; |
@@ -5297,7 +5225,7 @@ class CodeGenerator extends Object |
var name = js.string(node.components.join('.'), "'"); |
if (last.startsWith('_')) { |
var nativeSymbol = _emitPrivateNameSymbol(currentLibrary, last); |
- return js.call('new #(#, #)', [ |
+ return js.call('new #.new(#, #)', [ |
_emitConstructorAccess(privateSymbolClass.type), |
name, |
nativeSymbol |
@@ -5802,6 +5730,10 @@ class CodeGenerator extends Object |
@override |
visitConstructorFieldInitializer(node) => _unreachable(node); |
+ /// Unusued, see [_emitRedirectingConstructor]. |
+ @override |
+ visitRedirectingConstructorInvocation(node) => _unreachable(node); |
+ |
/// Unusued. Handled in [visitForEachStatement]. |
@override |
visitDeclaredIdentifier(node) => _unreachable(node); |