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 9deaaa4fa053320d93a13df21a5d4f3fb4bdeee7..fb35f60ef47d1d8f042ac7a7ba62f28ed820b35b 100644 |
--- a/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
+++ b/pkg/dev_compiler/lib/src/compiler/code_generator.dart |
@@ -140,6 +140,7 @@ class CodeGenerator extends Object |
final ClassElement boolClass; |
final ClassElement intClass; |
+ final ClassElement doubleClass; |
final ClassElement interceptorClass; |
final ClassElement nullClass; |
final ClassElement numClass; |
@@ -210,6 +211,7 @@ class CodeGenerator extends Object |
dartCoreLibrary = _getLibrary(c, 'dart:core'), |
boolClass = _getLibrary(c, 'dart:core').getType('bool'), |
intClass = _getLibrary(c, 'dart:core').getType('int'), |
+ doubleClass = _getLibrary(c, 'dart:core').getType('double'), |
numClass = _getLibrary(c, 'dart:core').getType('num'), |
nullClass = _getLibrary(c, 'dart:core').getType('Null'), |
objectClass = _getLibrary(c, 'dart:core').getType('Object'), |
@@ -840,8 +842,11 @@ class CodeGenerator extends Object |
block.add(js.statement('# = #;', [className, classExpr])); |
} |
+ JS.Statement finishGenericTypeTest; |
+ |
if (!isMixinAlias) { |
block.addAll(_defineConstructors(classElem, className, [], [])); |
+ finishGenericTypeTest = _emitClassTypeTests(classElem, className, block); |
} |
if (classElem.interfaces.isNotEmpty) { |
@@ -853,8 +858,10 @@ class CodeGenerator extends Object |
} |
if (isGeneric) { |
- return _defineClassTypeArguments( |
- classElem, typeFormals, _statement(block)); |
+ var classDef = |
+ _defineClassTypeArguments(classElem, typeFormals, _statement(block)); |
+ if (finishGenericTypeTest == null) return classDef; |
+ block = [classDef, finishGenericTypeTest]; |
} |
return _statement(block); |
} |
@@ -932,7 +939,7 @@ class CodeGenerator extends Object |
JS.Statement deferredBaseClass = |
_setBaseClass(classElem, className, jsPeerNames, body); |
- _emitClassTypeTests(classElem, className, body); |
+ var finishGenericTypeTest = _emitClassTypeTests(classElem, className, body); |
_emitVirtualFieldSymbols(classElem, body); |
_emitClassSignature(methods, allFields, classElem, ctors, className, body); |
@@ -949,6 +956,7 @@ class CodeGenerator extends Object |
body = <JS.Statement>[classDef]; |
_emitStaticFields(staticFields, classElem, body); |
+ if (finishGenericTypeTest != null) body.add(finishGenericTypeTest); |
for (var peer in jsPeerNames) { |
_registerExtensionType(classElem, peer, body); |
} |
@@ -957,200 +965,211 @@ class CodeGenerator extends Object |
return _statement(body); |
} |
- void _emitClassTypeTests(ClassElement classElem, JS.Expression className, |
- List<JS.Statement> body) { |
- if (classElem == objectClass) { |
- // We rely on ES6 static inheritance. All types that are represented by |
- // class constructor functions will see these definitions, with [this] |
- // being bound to the class constructor. |
- |
- // The 'instanceof' checks don't work for primitive types (which have fast |
- // definitions below) and don't work for native types. In those cases we |
- // fall through to the general purpose checking code. |
- body.add(js.statement( |
- '#.is = function is_Object(o) {' |
- ' if (o instanceof this) return true;' |
- ' return #.is(o, this);' |
- '}', |
- [className, _runtimeModule])); |
- body.add(js.statement( |
- '#.as = function as_Object(o) {' |
- ' if (o == null || o instanceof this) return o;' |
- ' return #.as(o, this);' |
- '}', |
- [className, _runtimeModule])); |
- body.add(js.statement( |
- '#._check = function check_Object(o) {' |
- ' if (o == null || o instanceof this) return o;' |
- ' return #.check(o, this);' |
- '}', |
- [className, _runtimeModule])); |
- return; |
- } |
- if (classElem == stringClass) { |
- body.add(js.statement( |
- '#.is = function is_String(o) { return typeof o == "string"; }', |
- className)); |
- body.add(js.statement( |
- '#.as = function as_String(o) {' |
- ' if (typeof o == "string" || o == null) return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_String(o) {' |
- ' if (typeof o == "string" || o == null) return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- if (classElem == functionClass) { |
- body.add(js.statement( |
- '#.is = function is_Function(o) { return typeof o == "function"; }', |
- className)); |
- body.add(js.statement( |
- '#.as = function as_Function(o) {' |
- ' if (typeof o == "function" || o == null) return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_String(o) {' |
- ' if (typeof o == "function" || o == null) return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- |
- if (classElem == intClass) { |
- body.add(js.statement( |
- '#.is = function is_int(o) {' |
- ' return typeof o == "number" && Math.floor(o) == o;' |
- '}', |
- className)); |
- body.add(js.statement( |
- '#.as = function as_int(o) {' |
- ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
- ' return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_int(o) {' |
- ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
- ' return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- if (classElem == nullClass) { |
- body.add(js.statement( |
- '#.is = function is_Null(o) { return o == null; }', className)); |
- body.add(js.statement( |
- '#.as = function as_Null(o) {' |
- ' if (o == null) return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_Null(o) {' |
- ' if (o == null) return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- if (classElem == numClass) { |
- body.add(js.statement( |
- '#.is = function is_num(o) { return typeof o == "number"; }', |
- className)); |
- body.add(js.statement( |
- '#.as = function as_num(o) {' |
- ' if (typeof o == "number" || o == null) return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_num(o) {' |
- ' if (typeof o == "number" || o == null) return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- if (classElem == boolClass) { |
- body.add(js.statement( |
- '#.is = function is_bool(o) { return o === true || o === false; }', |
- className)); |
- body.add(js.statement( |
- '#.as = function as_bool(o) {' |
- ' if (o === true || o === false || o == null) return o;' |
- ' return #.as(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- body.add(js.statement( |
- '#._check = function check_bool(o) {' |
- ' if (o === true || o === false || o == null) return o;' |
- ' return #.check(o, #);' |
- '}', |
- [className, _runtimeModule, className])); |
- return; |
- } |
- // TODO(sra): Add special cases for hot tests like `x is html.Element`. |
- |
- // `instanceof` check is futile for classes that are Interceptor classes. |
- ClassElement parent = classElem; |
- while (parent != objectClass) { |
- if (parent == interceptorClass) { |
- if (classElem == interceptorClass) { |
- // Place non-instanceof version of checks on Interceptor. All |
- // interceptor classes will inherit the methods via ES6 class static |
- // inheritance. |
- body.add(_callHelperStatement('addTypeTests(#);', className)); |
- |
- // TODO(sra): We could place on the extension type a pointer to the |
- // peer constructor and use that for the `instanceof` check, e.g. |
- // |
- // if (o instanceof this[_peerConstructor]) return o; |
- // |
+ JS.Statement _emitClassTypeTests(ClassElement classElem, |
+ JS.Expression className, List<JS.Statement> body) { |
+ JS.Expression getInterfaceSymbol(ClassElement c) { |
+ var library = c.library; |
+ if (library.isDartCore || library.isDartAsync) { |
+ switch (c.name) { |
+ case 'List': |
+ case 'Map': |
+ case 'Iterable': |
+ case 'Future': |
+ case 'Stream': |
+ case 'StreamSubscription': |
+ return _callHelper('is' + c.name); |
} |
- return; |
} |
- parent = parent.type.superclass.element; |
+ return null; |
} |
- // Choose between 'simple' checks, which are often accelerated by |
- // `instanceof`, and other checks, which are slowed down by taking time to |
- // do an `instanceof` check that is futile or likely futile. |
- // |
- // The `instanceof` check is futile for (1) a class that is only used as a |
- // mixin, or (2) is only used as an interface in an `implements` clause, and |
- // is likely futile (3) if the class has type parameters, since `Foo` aka |
- // `Foo<dynamic>` is not a superclass of `Foo<int>`. The first two are |
- // whole-program properites, but we can check for the last case. |
+ void markSubtypeOf(JS.Expression testSymbol) { |
+ body.add(js.statement('#.prototype[#] = true', [className, testSymbol])); |
+ } |
- // Since ES6 classes have inheritance of static properties, we need only |
- // install checks that differ from the parent. |
+ for (var iface in classElem.interfaces) { |
+ var prop = getInterfaceSymbol(iface.element); |
+ if (prop != null) markSubtypeOf(prop); |
+ } |
- bool isSimple(ClassElement classElement) { |
- if (classElement.typeParameters.isNotEmpty) return false; |
- return true; |
+ if (classElem.library.isDartCore) { |
+ if (classElem == objectClass) { |
Leaf
2017/08/24 17:19:39
Is there a reason we attach these here instead of
Jennifer Messerly
2017/08/24 18:26:37
Yeah I'm not sure why the existing code does that.
|
+ // Everything is an Object. |
+ body.add(js.statement( |
+ '#.is = function is_Object(o) { return true; }', [className])); |
+ body.add(js.statement( |
+ '#.as = function as_Object(o) { return o; }', [className])); |
+ body.add(js.statement( |
+ '#._check = function check_Object(o) { return o; }', [className])); |
+ return null; |
+ } |
+ if (classElem == stringClass) { |
+ body.add(js.statement( |
+ '#.is = function is_String(o) { return typeof o == "string"; }', |
+ className)); |
+ body.add(js.statement( |
+ '#.as = function as_String(o) {' |
+ ' if (typeof o == "string" || o == null) return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_String(o) {' |
+ ' if (typeof o == "string" || o == null) return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ if (classElem == functionClass) { |
+ body.add(js.statement( |
+ '#.is = function is_Function(o) { return typeof o == "function"; }', |
Leaf
2017/08/24 17:19:39
Does this handle call methods?
Jennifer Messerly
2017/08/24 18:26:37
yup. Callable classes are reified as JS functions
|
+ className)); |
+ body.add(js.statement( |
+ '#.as = function as_Function(o) {' |
+ ' if (typeof o == "function" || o == null) return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_String(o) {' |
+ ' if (typeof o == "function" || o == null) return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ if (classElem == intClass) { |
+ body.add(js.statement( |
+ '#.is = function is_int(o) {' |
+ ' return typeof o == "number" && Math.floor(o) == o;' |
+ '}', |
+ className)); |
+ body.add(js.statement( |
+ '#.as = function as_int(o) {' |
+ ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
+ ' return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_int(o) {' |
+ ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
+ ' return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ if (classElem == nullClass) { |
+ body.add(js.statement( |
+ '#.is = function is_Null(o) { return o == null; }', className)); |
+ body.add(js.statement( |
+ '#.as = function as_Null(o) {' |
+ ' if (o == null) return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_Null(o) {' |
+ ' if (o == null) return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ if (classElem == numClass || classElem == doubleClass) { |
+ body.add(js.statement( |
+ '#.is = function is_num(o) { return typeof o == "number"; }', |
+ className)); |
+ body.add(js.statement( |
+ '#.as = function as_num(o) {' |
+ ' if (typeof o == "number" || o == null) return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_num(o) {' |
+ ' if (typeof o == "number" || o == null) return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ if (classElem == boolClass) { |
+ body.add(js.statement( |
+ '#.is = function is_bool(o) { return o === true || o === false; }', |
+ className)); |
+ body.add(js.statement( |
+ '#.as = function as_bool(o) {' |
+ ' if (o === true || o === false || o == null) return o;' |
+ ' return #.as(o, #, false);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ body.add(js.statement( |
+ '#._check = function check_bool(o) {' |
+ ' if (o === true || o === false || o == null) return o;' |
+ ' return #.as(o, #, true);' |
+ '}', |
+ [className, _runtimeModule, className])); |
+ return null; |
+ } |
+ } |
+ if (classElem.library.isDartAsync) { |
+ if (classElem == types.futureOrType.element) { |
+ var typeParamT = classElem.typeParameters[0].type; |
+ var tOrFutureOfT = js.call('#.is(o) || #.is(o)', [ |
+ _emitType(typeParamT), |
+ _emitType(types.futureType.instantiate([typeParamT])) |
+ ]); |
+ body.add(js.statement(''' |
+ #.is = function is_FutureOr(o) { |
+ return #; |
+ } |
+ ''', [className, tOrFutureOfT])); |
+ body.add(js.statement(''' |
+ #.as = function as_FutureOr(o) { |
+ if (o == null || #) return o; |
+ return #.castError(o, this, false); |
+ } |
+ ''', [className, tOrFutureOfT, _runtimeModule])); |
+ body.add(js.statement(''' |
+ #._check = function check_FutureOr(o) { |
+ if (o == null || #) return o; |
+ return #.castError(o, this, true); |
Leaf
2017/08/24 17:19:39
Is there a reason the other cases above go through
Jennifer Messerly
2017/08/24 18:26:37
yeah, I'm trying to simplify things and have the c
|
+ } |
+ ''', [className, tOrFutureOfT, _runtimeModule])); |
+ return null; |
+ } |
} |
- assert(classElem != objectClass); |
- bool thisIsSimple = isSimple(classElem); |
- bool superIsSimple = isSimple(classElem.type.superclass.element); |
+ body.add(_callHelperStatement('addTypeTests(#);', [className])); |
- if (thisIsSimple == superIsSimple) return; |
+ if (classElem.typeParameters.isEmpty) return null; |
- if (thisIsSimple) { |
- body.add(_callHelperStatement('addSimpleTypeTests(#);', className)); |
- } else { |
- body.add(_callHelperStatement('addTypeTests(#);', className)); |
+ // For generics, testing against the default instantiation is common, |
+ // so optimize that. |
+ var isClassSymbol = getInterfaceSymbol(classElem); |
+ if (isClassSymbol == null) { |
+ // TODO(jmesserly): we could export these symbols, if we want to mark |
+ // implemented interfaces for user-defined classes. |
+ var id = new JS.TemporaryId("_is_${classElem.name}_default"); |
+ _moduleItems.add( |
+ js.statement('const # = Symbol(#);', [id, js.string(id.name, "'")])); |
+ isClassSymbol = id; |
} |
+ // Marking every generic type instantiation as a subtype of its default |
+ // instantiation. |
+ markSubtypeOf(isClassSymbol); |
+ |
+ // Define the type tests on the default instantiation to check for that |
+ // marker. |
+ var defaultInst = _emitTopLevelName(classElem); |
+ |
+ // Return this `addTypeTests` call so we can emit it outside of the generic |
+ // type parameter scope. |
+ return _callHelperStatement( |
+ 'addTypeTests(#, #);', [defaultInst, isClassSymbol]); |
} |
void _emitSuperHelperSymbols(List<JS.Statement> body) { |
@@ -1282,6 +1301,7 @@ class CodeGenerator extends Object |
} |
var genericDef = js.statement( |
'# = #;', [_emitTopLevelName(element, suffix: r'$'), genericCall]); |
+ // TODO(jmesserly): this should be instantiate to bounds |
var dynType = fillDynamicTypeArgs(element.type); |
var genericInst = _emitType(dynType, lowerGeneric: true); |
return js.statement( |
@@ -1840,6 +1860,14 @@ class CodeGenerator extends Object |
: callMethod != null; |
var body = <JS.Statement>[]; |
+ if (isCallable) { |
+ // Our class instances will have JS `typeof this == "function"`, |
+ // so make sure to attach the runtime type information the same way |
+ // we would do it for function types. |
+ body.add(js.statement('#.prototype[#] = #;', |
+ [className, _callHelper('_runtimeType'), className])); |
+ } |
+ |
void addConstructor(ConstructorElement element, JS.Expression jsCtor) { |
var ctorName = _constructorName(element); |
if (JS.invalidStaticFieldName(element.name)) { |