Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(326)

Unified Diff: pkg/dev_compiler/lib/src/compiler/code_generator.dart

Issue 3003613002: improve DDC's type checks (Closed)
Patch Set: format Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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)) {

Powered by Google App Engine
This is Rietveld 408576698