Narrowing improvements in dart2js global inference.
* no longer narrow by selector use, only narrow by type and non-null
* check for narrow nesting
* add non-null in a couple known places
One large apps, I compared the type-masks in dump-info and all differences were improvements with non-null. We weren't doing any narrowing based on the possible targets of a selector.
Change-Id: I270f360f70fbe3171d09ccd71d10517be9140194
Reviewed-on: https://dart-review.googlesource.com/c/90340
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 518bf66..52981a0 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -899,15 +899,10 @@
if (variable != null) {
Local local = _localsMap.getLocalVariable(variable);
if (!_capturedVariables.contains(local)) {
- TypeInformation refinedType = _types
- .refineReceiver(selector, mask, receiverType, isConditional: false);
DartType type = _localsMap.getLocalType(_elementMap, local);
_state.updateLocal(
- _inferrer, _capturedAndBoxed, local, refinedType, node, type);
- List<Refinement> refinements = _localRefinementMap[variable];
- if (refinements != null) {
- refinements.add(new Refinement(selector, mask));
- }
+ _inferrer, _capturedAndBoxed, local, receiverType, node, type,
+ isNullable: _appliesToNullWithoutThrow(selector));
}
}
@@ -916,6 +911,27 @@
inLoop: inLoop, isConditional: false);
}
+ /// Whether [selector] could be a valid selector on `Null` without throwing.
+ bool _appliesToNullWithoutThrow(Selector selector) {
+ var name = selector.name;
+ if (selector.isOperator && name == "==") return true;
+ // Known getters and valid tear-offs.
+ if (selector.isGetter &&
+ (name == "hashCode" ||
+ name == "runtimeType" ||
+ name == "toString" ||
+ name == "noSuchMethod")) return true;
+ // Calling toString always succeeds, calls to `noSuchMethod` (even well
+ // formed calls) always throw.
+ if (selector.isCall &&
+ name == "toString" &&
+ selector.positionalArgumentCount == 0 &&
+ selector.namedArgumentCount == 0) {
+ return true;
+ }
+ return false;
+ }
+
TypeInformation handleDynamicGet(ir.Node node, Selector selector,
AbstractValue mask, TypeInformation receiverType) {
return _handleDynamic(
@@ -944,60 +960,10 @@
callType, node, selector, mask, receiverType, arguments);
}
- /// Map from synthesized variables created for non-null operations to observed
- /// refinements. This is used to refine locals in cases like:
- ///
- /// local?.method()
- ///
- /// which in kernel is encoded as
- ///
- /// let #t1 = local in #t1 == null ? null : #1.method()
- ///
- Map<ir.VariableDeclaration, List<Refinement>> _localRefinementMap =
- <ir.VariableDeclaration, List<Refinement>>{};
-
@override
TypeInformation visitLet(ir.Let node) {
- ir.VariableDeclaration alias;
- ir.Expression body = node.body;
- if (node.variable.name == null &&
- node.variable.isFinal &&
- node.variable.initializer is ir.VariableGet &&
- body is ir.ConditionalExpression &&
- body.condition is ir.MethodInvocation &&
- body.then is ir.NullLiteral) {
- ir.VariableGet get = node.variable.initializer;
- ir.MethodInvocation invocation = body.condition;
- ir.Expression receiver = invocation.receiver;
- if (invocation.name.name == '==' &&
- receiver is ir.VariableGet &&
- receiver.variable == node.variable &&
- invocation.arguments.positional.single is ir.NullLiteral) {
- // We have
- // let #t1 = local in #t1 == null ? null : e
- alias = get.variable;
- _localRefinementMap[node.variable] = <Refinement>[];
- }
- }
visit(node.variable);
- TypeInformation type = visit(body);
- if (alias != null) {
- List<Refinement> refinements = _localRefinementMap.remove(node.variable);
- if (refinements.isNotEmpty) {
- Local local = _localsMap.getLocalVariable(alias);
- DartType type = _localsMap.getLocalType(_elementMap, local);
- TypeInformation localType =
- _state.readLocal(_inferrer, _capturedAndBoxed, local);
- for (Refinement refinement in refinements) {
- localType = _types.refineReceiver(
- refinement.selector, refinement.mask, localType,
- isConditional: true);
- _state.updateLocal(
- _inferrer, _capturedAndBoxed, local, localType, node, type);
- }
- }
- }
- return type;
+ return visit(node.body);
}
@override
@@ -1553,7 +1519,8 @@
Local local = _localsMap.getLocalVariable(variable);
DartType type = _localsMap.getLocalType(_elementMap, local);
_state.updateLocal(
- _inferrer, _capturedAndBoxed, local, localFunctionType, node, type);
+ _inferrer, _capturedAndBoxed, local, localFunctionType, node, type,
+ isNullable: false);
}
// We don't put the closure in the work queue of the
@@ -1669,14 +1636,16 @@
}
Local local = _localsMap.getLocalVariable(exception);
_state.updateLocal(
- _inferrer, _capturedAndBoxed, local, mask, node, const DynamicType());
+ _inferrer, _capturedAndBoxed, local, mask, node, const DynamicType(),
+ isNullable: false /* `throw null` produces a NullThrownError */);
}
ir.VariableDeclaration stackTrace = node.stackTrace;
if (stackTrace != null) {
Local local = _localsMap.getLocalVariable(stackTrace);
// TODO(johnniwinther): Use a mask based on [StackTrace].
_state.updateLocal(_inferrer, _capturedAndBoxed, local,
- _types.dynamicType, node, const DynamicType());
+ _types.dynamicType, node, const DynamicType(),
+ isNullable: false /* if requested, the stack is never null */);
}
visit(node.body);
return null;
@@ -1905,9 +1874,10 @@
Local local,
TypeInformation type,
ir.Node node,
- DartType staticType) {
+ DartType staticType,
+ {isNullable: true}) {
assert(type != null);
- type = inferrer.types.narrowType(type, staticType);
+ type = inferrer.types.narrowType(type, staticType, isNullable: isNullable);
FieldEntity field = capturedAndBoxed[local];
if (field != null) {
@@ -1923,10 +1893,9 @@
Local local,
DartType type,
ir.Node node) {
- TypeInformation existing = readLocal(inferrer, capturedAndBoxed, local);
- TypeInformation newType =
- inferrer.types.narrowType(existing, type, isNullable: false);
- updateLocal(inferrer, capturedAndBoxed, local, newType, node, type);
+ TypeInformation currentType = readLocal(inferrer, capturedAndBoxed, local);
+ updateLocal(inferrer, capturedAndBoxed, local, currentType, node, type,
+ isNullable: false);
}
LocalState mergeFlow(InferrerEngine inferrer, LocalState other) {
diff --git a/pkg/compiler/lib/src/inferrer/type_system.dart b/pkg/compiler/lib/src/inferrer/type_system.dart
index 22d41df..aa827d3 100644
--- a/pkg/compiler/lib/src/inferrer/type_system.dart
+++ b/pkg/compiler/lib/src/inferrer/type_system.dart
@@ -6,7 +6,6 @@
import '../common.dart';
import '../elements/entities.dart';
import '../elements/types.dart';
-import '../universe/selector.dart';
import '../world.dart';
import 'abstract_value_domain.dart';
import 'type_graph_nodes.dart';
@@ -309,52 +308,28 @@
return info.type != mask;
}
- /// Returns a new receiver type for this [selector] applied to
- /// [receiverType].
- ///
- /// The option [isConditional] is true when [selector] was seen in a
- /// conditional send (e.g. `a?.selector`), in which case the returned type
- /// may be null.
- TypeInformation refineReceiver(
- Selector selector, AbstractValue mask, TypeInformation receiver,
- {bool isConditional}) {
- if (_abstractValueDomain.isExact(receiver.type).isDefinitelyTrue) {
- return receiver;
- }
- AbstractValue otherType = _closedWorld.computeReceiverType(selector, mask);
- // Conditional sends (a?.b) can still narrow the possible types of `a`,
- // however, we still need to consider that `a` may be null.
- if (isConditional) {
- // Note: we don't check that receiver.type.isNullable here because this is
- // called during the graph construction.
- otherType = _abstractValueDomain.includeNull(otherType);
- }
- // If this is refining to nullable subtype of `Object` just return
- // the receiver. We know the narrowing is useless.
- if (_abstractValueDomain.isNull(otherType).isPotentiallyTrue &&
- _abstractValueDomain.containsAll(otherType).isPotentiallyTrue) {
- return receiver;
- }
- TypeInformation newType =
- new NarrowTypeInformation(_abstractValueDomain, receiver, otherType);
- allocatedTypes.add(newType);
- return newType;
- }
+ bool _isNonNullNarrow(TypeInformation type) =>
+ type is NarrowTypeInformation &&
+ _abstractValueDomain.isNull(type.typeAnnotation).isDefinitelyFalse;
/// Returns the intersection between [type] and [annotation].
/// [isNullable] indicates whether the annotation implies a null
/// type.
TypeInformation narrowType(TypeInformation type, DartType annotation,
{bool isNullable: true}) {
- if (annotation.treatAsDynamic) return type;
- if (annotation.isVoid) return type;
AbstractValue otherType;
- if (annotation.isInterfaceType) {
+ if (annotation.isVoid) return type;
+ if (annotation.treatAsDynamic) {
+ if (isNullable) return type;
+ // If the input is already narrowed to be not-null, there is no value
+ // in adding another narrowing node.
+ if (_isNonNullNarrow(type)) return type;
+ otherType = _abstractValueDomain.excludeNull(dynamicType.type);
+ } else if (annotation.isInterfaceType) {
InterfaceType interface = annotation;
if (interface.element == _closedWorld.commonElements.objectClass) {
- if (isNullable) {
- return type;
- }
+ if (isNullable) return type;
+ if (_isNonNullNarrow(type)) return type;
otherType = _abstractValueDomain.excludeNull(dynamicType.type);
} else {
otherType =
@@ -373,23 +348,9 @@
if (isNullable) {
otherType = _abstractValueDomain.includeNull(otherType);
}
- if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) {
- return type;
- } else {
- TypeInformation newType =
- new NarrowTypeInformation(_abstractValueDomain, type, otherType);
- allocatedTypes.add(newType);
- return newType;
- }
- }
-
- /// Returns the non-nullable type of [type].
- TypeInformation narrowNotNull(TypeInformation type) {
- if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) {
- return type;
- }
- TypeInformation newType = new NarrowTypeInformation(_abstractValueDomain,
- type, _abstractValueDomain.excludeNull(dynamicType.type));
+ if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) return type;
+ TypeInformation newType =
+ new NarrowTypeInformation(_abstractValueDomain, type, otherType);
allocatedTypes.add(newType);
return newType;
}
@@ -674,14 +635,20 @@
// mapped iterable, we save the intermediate results to avoid computing them
// again.
var list = [];
+ bool isDynamicIngoringNull = false;
+ bool mayBeNull = false;
for (AbstractValue mask in masks) {
// Don't do any work on computing unions if we know that after all that
// work the result will be `dynamic`.
// TODO(sigmund): change to `mask == dynamicType` so we can continue to
// track the non-nullable bit.
if (_abstractValueDomain.containsAll(mask).isPotentiallyTrue) {
- return dynamicType;
+ isDynamicIngoringNull = true;
}
+ if (_abstractValueDomain.isNull(mask).isPotentiallyTrue) {
+ mayBeNull = true;
+ }
+ if (isDynamicIngoringNull && mayBeNull) return dynamicType;
list.add(mask);
}
@@ -691,8 +658,12 @@
newType == null ? mask : _abstractValueDomain.union(newType, mask);
// Likewise - stop early if we already reach dynamic.
if (_abstractValueDomain.containsAll(newType).isPotentiallyTrue) {
- return dynamicType;
+ isDynamicIngoringNull = true;
}
+ if (_abstractValueDomain.isNull(newType).isPotentiallyTrue) {
+ mayBeNull = true;
+ }
+ if (isDynamicIngoringNull && mayBeNull) return dynamicType;
}
return newType ?? _abstractValueDomain.emptyType;
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 51d104c..110a9ca 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -14869,7 +14869,7 @@
_convertNativeToDart_EventTarget(this._get_currentTarget);
@JSName('currentTarget')
@Creates('Null')
- @Returns('EventTarget|=Object')
+ @Returns('EventTarget|=Object|Null')
final dynamic _get_currentTarget;
final bool defaultPrevented;
@@ -21153,7 +21153,7 @@
_convertNativeToDart_EventTarget(this._get_relatedTarget);
@JSName('relatedTarget')
@Creates('Node')
- @Returns('EventTarget|=Object')
+ @Returns('EventTarget|=Object|Null')
final dynamic _get_relatedTarget;
@JSName('screenX')
diff --git a/tests/compiler/dart2js/inference/data/catch.dart b/tests/compiler/dart2js/inference/data/catch.dart
index d468579..4d84e87 100644
--- a/tests/compiler/dart2js/inference/data/catch.dart
+++ b/tests/compiler/dart2js/inference/data/catch.dart
@@ -13,9 +13,9 @@
/// Untyped catch clause.
////////////////////////////////////////////////////////////////////////////////
-/*element: catchUntyped:[null|subclass=Object]*/
+/*element: catchUntyped:[subclass=Object]*/
catchUntyped() {
- var local;
+ dynamic local = 0;
try {} catch (e) {
local = e;
}
@@ -39,7 +39,7 @@
/// Catch clause with stack trace.
////////////////////////////////////////////////////////////////////////////////
-/*element: catchStackTrace:[null|subclass=Object]*/
+/*element: catchStackTrace:[subclass=Object]*/
catchStackTrace() {
dynamic local = 0;
try {} catch (_, s) {
diff --git a/tests/compiler/dart2js/inference/data/for_in.dart b/tests/compiler/dart2js/inference/data/for_in.dart
index 231c016..c622275 100644
--- a/tests/compiler/dart2js/inference/data/for_in.dart
+++ b/tests/compiler/dart2js/inference/data/for_in.dart
@@ -7,11 +7,7 @@
forInDirect();
forInReturn();
forInReturnMulti();
- forInReturnRefined();
- forInReturnRefinedDynamic();
- testInForIn();
- operatorInForIn();
- updateInForIn();
+ forInReturnNonNull();
}
////////////////////////////////////////////////////////////////////////////////
@@ -66,12 +62,12 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Sequentially refine element and return it from a for-in loop on known list
-// type.
+// Sequentially refine that an element is not null and return it from a for-in
+// loop on known list type.
////////////////////////////////////////////////////////////////////////////////
-/*element: forInReturnRefined:[null|subclass=JSInt]*/
-forInReturnRefined() {
+/*element: forInReturnNonNull:[subclass=JSInt]*/
+forInReturnNonNull() {
/*iterator: Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: 3)*/
/*current: [exact=ArrayIterator]*/
/*moveNext: [exact=ArrayIterator]*/
@@ -82,113 +78,5 @@
a. /*[subclass=JSInt]*/ isEven;
return a;
}
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Sequentially refine element and return it from a for-in loop on known list
-// type with a dynamic variable.
-////////////////////////////////////////////////////////////////////////////////
-
-/*element: forInReturnRefinedDynamic:[null|subclass=JSInt]*/
-forInReturnRefinedDynamic() {
- /*iterator: Container([exact=JSExtendableArray], element: [exact=JSUInt31], length: 3)*/
- /*current: [exact=ArrayIterator]*/
- /*moveNext: [exact=ArrayIterator]*/
- for (dynamic a in [1, 2, 3]) {
- // TODO(johnniwinther): We should know the type of [a] here.
- a.isEven;
- a. /*[subclass=JSInt]*/ isEven;
- return a;
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Refine element through test and return it from a for-in loop on known list
-// type.
-////////////////////////////////////////////////////////////////////////////////
-
-/*element: Class1.:[exact=Class1]*/
-class Class1 {
- /*element: Class1.field1:[exact=JSUInt31]*/
- var field1 = 42;
-}
-
-/*element: _testInForIn:[null|exact=Class1]*/
-_testInForIn(
- /*Container([exact=JSExtendableArray], element: [exact=Class1], length: 2)*/ list) {
- /*iterator: Container([exact=JSExtendableArray], element: [exact=Class1], length: 2)*/
- /*current: [exact=ArrayIterator]*/
- /*moveNext: [exact=ArrayIterator]*/
- for (var t in list) {
- if (t.field1) {
- return t;
- }
- }
-}
-
-/*element: testInForIn:[null]*/
-testInForIn() {
- _testInForIn([new Class1(), new Class1()]);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Refine element through operator and return it from a for-in loop on known
-// list type.
-////////////////////////////////////////////////////////////////////////////////
-
-/*element: Class2.:[exact=Class2]*/
-class Class2 {
- /*element: Class2.field2a:[exact=JSUInt31]*/
- var field2a = 42;
- /*element: Class2.field2b:[exact=JSUInt31]*/
- var field2b = 42;
-}
-
-/*element: _operatorInForIn:[null|exact=Class2]*/
-_operatorInForIn(
- /*Container([exact=JSExtendableArray], element: [exact=Class2], length: 2)*/ list) {
- /*iterator: Container([exact=JSExtendableArray], element: [exact=Class2], length: 2)*/
- /*current: [exact=ArrayIterator]*/
- /*moveNext: [exact=ArrayIterator]*/
- for (var t in list) {
- if (t.field2a /*invoke: [exact=JSUInt31]*/ <
- t. /*[exact=Class2]*/ field2b) {
- return t;
- }
- }
-}
-
-/*element: operatorInForIn:[null]*/
-operatorInForIn() {
- _operatorInForIn([new Class2(), new Class2()]);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Refine element through operator and return it from a for-in loop on known
-// list type.
-////////////////////////////////////////////////////////////////////////////////
-
-/*element: Class3.:[exact=Class3]*/
-class Class3 {
- /*element: Class3.field3a:[exact=JSUInt31]*/
- var field3a = 42;
- /*element: Class3.field3b:[exact=JSUInt31]*/
- var field3b = 42;
-}
-
-/*element: _updateInForIn:[null]*/
-_updateInForIn(
- /*Container([exact=JSExtendableArray], element: [exact=Class3], length: 2)*/ list) {
- /*iterator: Container([exact=JSExtendableArray], element: [exact=Class3], length: 2)*/
- /*current: [exact=ArrayIterator]*/
- /*moveNext: [exact=ArrayIterator]*/
- for (var t in list) {
- t.field3b = t.field3a;
- t. /*update: [exact=Class3]*/ field3a = 87;
- }
-}
-
-/*element: updateInForIn:[null]*/
-updateInForIn() {
- _updateInForIn([new Class3(), new Class3()]);
+ return 0;
}
diff --git a/tests/compiler/dart2js/inference/data/general.dart b/tests/compiler/dart2js/inference/data/general.dart
index f4e11b1..a00c12b 100644
--- a/tests/compiler/dart2js/inference/data/general.dart
+++ b/tests/compiler/dart2js/inference/data/general.dart
@@ -384,7 +384,7 @@
var c;
L1:
if (a /*invoke: Value([exact=JSBool], value: true)*/ > 1) {
- if (a /*invoke: [empty]*/ == 2) {
+ if (a /*invoke: Value([exact=JSBool], value: true)*/ == 2) {
break L1;
}
c = 42;
@@ -581,7 +581,7 @@
return a;
}
-/*element: testSpecialization1:[subclass=JSNumber]*/
+/*element: testSpecialization1:[subclass=Object]*/
testSpecialization1() {
var a = topLevelGetter();
a - 42;
@@ -621,7 +621,7 @@
return a;
}
-/*element: testReturnNull3:[null|subclass=Object]*/
+/*element: testReturnNull3:[subclass=Object]*/
testReturnNull3(/*[null|subclass=Object]*/ a) {
if (a == null) return 42;
return a;
@@ -641,7 +641,7 @@
return a;
}
-/*element: testReturnNull6:[null|subclass=Object]*/
+/*element: testReturnNull6:[subclass=Object]*/
testReturnNull6() {
var a = topLevelGetter();
if (a == null) return 42;
diff --git a/tests/compiler/dart2js/inference/data/postfix_prefix.dart b/tests/compiler/dart2js/inference/data/postfix_prefix.dart
index 9ede9a2..92a47c2 100644
--- a/tests/compiler/dart2js/inference/data/postfix_prefix.dart
+++ b/tests/compiler/dart2js/inference/data/postfix_prefix.dart
@@ -18,7 +18,7 @@
/*element: A.[]=:[null]*/
operator []=(/*[empty]*/ index, /*[subclass=JSNumber]*/ value) {}
- /*element: A.returnDynamic1:[exact=JSUInt31]*/
+ /*element: A.returnDynamic1:Union([exact=JSString], [exact=JSUInt31])*/
returnDynamic1() => /*[subclass=A]*/ /*update: [subclass=A]*/ foo
/*invoke: Union([exact=JSString], [exact=JSUInt31])*/ --;
@@ -30,7 +30,7 @@
returnNum2() => /*[subclass=A]*/ /*update: [subclass=A]*/ foo
/*invoke: Union([exact=JSString], [exact=JSUInt31])*/ -= 42;
- /*element: A.returnDynamic2:[exact=JSUInt31]*/
+ /*element: A.returnDynamic2:Union([exact=JSString], [exact=JSUInt31])*/
returnDynamic2() => this
/*[subclass=A]*/ /*update: [subclass=A]*/ [index]
/*invoke: Union([exact=JSString], [exact=JSUInt31])*/ --;
@@ -44,12 +44,10 @@
/*[subclass=A]*/ /*update: [subclass=A]*/ [index]
/*invoke: Union([exact=JSString], [exact=JSUInt31])*/ -= 42;
- // TODO(johnniwinther): Investigate why implementations differ on update.
/*element: A.returnEmpty3:[empty]*/
returnEmpty3() {
dynamic a = this;
- return a. /*[subclass=A]*/
- /*update: [empty]*/
+ return a. /*[subclass=A]*/ /*update: [subclass=A]*/
bar
/*invoke: [empty]*/ --;
}
@@ -76,7 +74,7 @@
/*element: B.[]:[exact=JSUInt31]*/
operator [](/*[empty]*/ index) => 42;
- /*element: B.returnString1:[empty]*/
+ /*element: B.returnString1:Value([exact=JSString], value: "string")*/
returnString1() =>
super.foo /*invoke: Value([exact=JSString], value: "string")*/ --;
@@ -89,7 +87,7 @@
returnDynamic2() =>
super.foo /*invoke: Value([exact=JSString], value: "string")*/ -= 42;
- /*element: B.returnString2:[empty]*/
+ /*element: B.returnString2:Value([exact=JSString], value: "string")*/
returnString2() => super[index]
/*invoke: Value([exact=JSString], value: "string")*/ --;
diff --git a/tests/compiler/dart2js/inference/data/refine_captured_locals.dart b/tests/compiler/dart2js/inference/data/refine_captured_locals.dart
index f1695af..f11fc71 100644
--- a/tests/compiler/dart2js/inference/data/refine_captured_locals.dart
+++ b/tests/compiler/dart2js/inference/data/refine_captured_locals.dart
@@ -20,12 +20,9 @@
method1() {}
}
-/*element: Class2.:[exact=Class2]*/
-class Class2 {}
-
/*element: _refineBeforeCapture:[exact=Class1]*/
-_refineBeforeCapture(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*invoke: Union([exact=Class1], [exact=Class2])*/ method1();
+_refineBeforeCapture(/*[null|exact=Class1]*/ o) {
+ o. /*invoke: [null|exact=Class1]*/ method1();
o. /*invoke: [exact=Class1]*/ method1();
/*[exact=Class1]*/ localFunction() => o;
@@ -35,7 +32,7 @@
/*element: refineBeforeCapture:[null]*/
refineBeforeCapture() {
_refineBeforeCapture(new Class1());
- _refineBeforeCapture(new Class2());
+ _refineBeforeCapture(null);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/compiler/dart2js/inference/data/refine_locals.dart b/tests/compiler/dart2js/inference/data/refine_locals.dart
index 8711587..557770d 100644
--- a/tests/compiler/dart2js/inference/data/refine_locals.dart
+++ b/tests/compiler/dart2js/inference/data/refine_locals.dart
@@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:expect/expect.dart';
+
/*element: main:[null]*/
main() {
refineToClass();
@@ -9,7 +11,7 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Refine the type of a non-captured local variable through a sequence of
+// Refine nullability of a non-captured local variable through a sequence of
// accesses and updates.
////////////////////////////////////////////////////////////////////////////////
@@ -31,141 +33,87 @@
method0() {}
/*element: Class2.method2:[null]*/
method2() {}
- /*element: Class2.field0:[null|exact=JSUInt31]*/
+ /*element: Class2.field0:[null]*/
var field0;
- /*element: Class2.field2:[null|exact=JSUInt31]*/
+ /*element: Class2.field2:[null]*/
var field2;
}
-/*element: _refineToClass1Invoke:[empty]*/
-_refineToClass1Invoke(/*Union([exact=Class1], [exact=Class2])*/ o) {
+/*element: _refineUnion:Union([exact=Class1], [exact=Class2])*/
+_refineUnion(/*Union([null|exact=Class1], [null|exact=Class2])*/ o) {
+ o. /*invoke: Union([null|exact=Class1], [null|exact=Class2])*/ method0();
o. /*invoke: Union([exact=Class1], [exact=Class2])*/ method1();
- o. /*invoke: [exact=Class1]*/ method0();
- o. /*invoke: [exact=Class1]*/ method2();
- return o;
-}
-
-/*element: _refineToClass2Invoke:[empty]*/
-_refineToClass2Invoke(/*Union([exact=Class1], [exact=Class2])*/ o) {
o. /*invoke: Union([exact=Class1], [exact=Class2])*/ method2();
- o. /*invoke: [exact=Class2]*/ method0();
- o. /*invoke: [exact=Class2]*/ method1();
return o;
}
-/*element: _refineToEmptyInvoke:[empty]*/
-_refineToEmptyInvoke(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*invoke: Union([exact=Class1], [exact=Class2])*/ method1();
- o. /*invoke: [exact=Class1]*/ method2();
- o. /*invoke: [empty]*/ method0();
+/*element: _refineFromMethod:[exact=Class1]*/
+_refineFromMethod(/*[null|exact=Class1]*/ o) {
+ o. /*invoke: [null|exact=Class1]*/ method0();
+ o. /*invoke: [exact=Class1]*/ method1();
return o;
}
-/*element: _refineToClass1Get:[empty]*/
-_refineToClass1Get(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*Union([exact=Class1], [exact=Class2])*/ field0;
- o. /*Union([exact=Class1], [exact=Class2])*/ field1;
- o. /*[exact=Class1]*/ field2;
+/*element: _refineFromGetter:[exact=Class2]*/
+_refineFromGetter(/*[null|exact=Class2]*/ o) {
+ o. /*[null|exact=Class2]*/ field0;
+ o. /*[exact=Class2]*/ field2;
return o;
}
-/*element: _refineToClass2Get:[empty]*/
-_refineToClass2Get(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*Union([exact=Class1], [exact=Class2])*/ field0;
- o. /*Union([exact=Class1], [exact=Class2])*/ field2;
- o. /*[exact=Class2]*/ field1;
+/*element: _refineFromSetter:[exact=Class1]*/
+_refineFromSetter(/*[null|exact=Class1]*/ o) {
+ o. /*update: [null|exact=Class1]*/ field0 = 0;
+ o. /*update: [exact=Class1]*/ field1 = 0;
return o;
}
-/*element: _refineToEmptyGet:[empty]*/
-_refineToEmptyGet(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*Union([exact=Class1], [exact=Class2])*/ field1;
- o. /*[exact=Class1]*/ field2;
- o. /*[empty]*/ field0;
- return o;
-}
-
-/*element: _refineToClass1Set:[empty]*/
-_refineToClass1Set(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*update: Union([exact=Class1], [exact=Class2])*/ field0 = 0;
- o. /*update: Union([exact=Class1], [exact=Class2])*/ field1 = 0;
- o. /*update: [exact=Class1]*/ field2 = 0;
- return o;
-}
-
-/*element: _refineToClass2Set:[empty]*/
-_refineToClass2Set(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*update: Union([exact=Class1], [exact=Class2])*/ field0 = 0;
- o. /*update: Union([exact=Class1], [exact=Class2])*/ field2 = 0;
- o. /*update: [exact=Class2]*/ field1 = 0;
- return o;
-}
-
-/*element: _refineToEmptySet:[empty]*/
-_refineToEmptySet(/*Union([exact=Class1], [exact=Class2])*/ o) {
- o. /*update: Union([exact=Class1], [exact=Class2])*/ field1 = 0;
- o. /*update: [exact=Class1]*/ field2 = 0;
- o. /*update: [empty]*/ field0 = 0;
- return o;
-}
-
-/*element: _refineToClass1InvokeIfNotNull:[null]*/
-_refineToClass1InvokeIfNotNull(
- /*Union([exact=Class2], [null|exact=Class1])*/ o) {
+/*element: _noRefinementNullAware:[null|exact=Class1]*/
+_noRefinementNullAware(/*[null|exact=Class1]*/ o) {
o
?.
- /*invoke: Union([exact=Class1], [exact=Class2])*/
+ /*invoke: [exact=Class1]*/
method1();
- o
- ?.
- /*invoke: [exact=Class1]*/
- method0();
- o
- ?.
- /*invoke: [exact=Class1]*/
- method2();
return o;
}
-/*element: _noRefinementToClass1InvokeSet:Union([exact=Class2], [null|exact=Class1])*/
-_noRefinementToClass1InvokeSet(
- /*Union([exact=Class2], [null|exact=Class1])*/ o) {
- (o = o). /*invoke: Union([exact=Class2], [null|exact=Class1])*/ method1();
- (o = o). /*invoke: Union([exact=Class2], [null|exact=Class1])*/ method0();
- (o = o). /*invoke: Union([exact=Class2], [null|exact=Class1])*/ method2();
+/*element: _noRefinementNullSelectors:[exact=Class2]*/
+_noRefinementNullSelectors(/*[null|exact=Class2]*/ o) {
+ o /*invoke: [null|exact=Class2]*/ == 2;
+ o. /*[null|exact=Class2]*/ hashCode;
+ o. /*[null|exact=Class2]*/ runtimeType;
+ o. /*[null|exact=Class2]*/ toString;
+ o. /*[null|exact=Class2]*/ noSuchMethod;
+ o. /*invoke: [null|exact=Class2]*/ toString();
+ o. /*invoke: [null|exact=Class2]*/ noSuchMethod(null); // assumed to throw.
+ o. /*[exact=Class2]*/ toString;
return o;
}
+/*element: _noRefinementUpdatedVariable:[null|exact=Class1]*/
+_noRefinementUpdatedVariable(/*[null|exact=Class1]*/ o) {
+ (o = o). /*invoke: [null|exact=Class1]*/ method1();
+ (o = o). /*invoke: [null|exact=Class1]*/ method0();
+ return o;
+}
+
+/*element: _condition:Value([exact=JSBool], value: false)*/
+@AssumeDynamic()
+get _condition => false;
+
/*element: refineToClass:[null]*/
refineToClass() {
- _refineToClass1Invoke(new Class1());
- _refineToClass1Invoke(new Class2());
- _refineToClass2Invoke(new Class1());
- _refineToClass2Invoke(new Class2());
- _refineToEmptyInvoke(new Class1());
- _refineToEmptyInvoke(new Class2());
+ var nullOrClass1 = _condition ? null : new Class1();
+ var nullOrClass2 = _condition ? null : new Class2();
+ _refineUnion(nullOrClass1);
+ _refineUnion(nullOrClass2);
- _refineToClass1Get(new Class1());
- _refineToClass1Get(new Class2());
- _refineToClass2Get(new Class1());
- _refineToClass2Get(new Class2());
- _refineToEmptyGet(new Class1());
- _refineToEmptyGet(new Class2());
-
- _refineToClass1Set(new Class1());
- _refineToClass1Set(new Class2());
- _refineToClass2Set(new Class1());
- _refineToClass2Set(new Class2());
- _refineToEmptySet(new Class1());
- _refineToEmptySet(new Class2());
-
- _refineToClass1InvokeIfNotNull(null);
- _refineToClass1InvokeIfNotNull(new Class1());
- _refineToClass1InvokeIfNotNull(new Class2());
-
- _noRefinementToClass1InvokeSet(null);
- _noRefinementToClass1InvokeSet(new Class1());
- _noRefinementToClass1InvokeSet(new Class2());
+ _refineFromMethod(nullOrClass1);
+ _refineFromGetter(nullOrClass2);
+ _refineFromSetter(nullOrClass1);
+ _noRefinementNullAware(nullOrClass1);
+ _noRefinementNullSelectors(nullOrClass2);
+ _noRefinementUpdatedVariable(nullOrClass1);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/compiler/dart2js/inference/data/refine_order.dart b/tests/compiler/dart2js/inference/data/refine_order.dart
index c511c73..b2c6d9e 100644
--- a/tests/compiler/dart2js/inference/data/refine_order.dart
+++ b/tests/compiler/dart2js/inference/data/refine_order.dart
@@ -37,7 +37,7 @@
@AssumeDynamic()
statementOrderFieldAccess(/*[null|subclass=Object]*/ o) {
o.field;
- o. /*[exact=Class]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -48,7 +48,7 @@
@AssumeDynamic()
statementOrderFieldUpdate(/*[null|subclass=Object]*/ o) {
o.field = 42;
- o. /*update: [exact=Class]*/ field = 42;
+ o. /*update: [subclass=Object]*/ field = 42;
}
////////////////////////////////////////////////////////////////////////////////
@@ -59,7 +59,7 @@
@AssumeDynamic()
statementOrderInvocation(/*[null|subclass=Object]*/ o) {
o.method(null);
- o. /*invoke: [exact=Class]*/ method(null);
+ o. /*invoke: [subclass=Object]*/ method(null);
}
////////////////////////////////////////////////////////////////////////////////
@@ -71,7 +71,7 @@
receiverVsArgument(/*[null|subclass=Object]*/ o) {
// TODO(johnniwinther): The arguments should refine the receiver.
o.method(o.field);
- o. /*[exact=Class]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -82,8 +82,8 @@
@AssumeDynamic()
argumentsOrder(/*[null|subclass=Object]*/ o) {
// TODO(johnniwinther): The arguments should refine the receiver.
- o.method(o.field, o. /*[exact=Class]*/ field);
- o. /*[exact=Class]*/ field;
+ o.method(o.field, o. /*[subclass=Object]*/ field);
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -93,8 +93,8 @@
/*element: operatorOrder:[null]*/
@AssumeDynamic()
operatorOrder(/*[null|subclass=Object]*/ o) {
- o.field /*invoke: [exact=JSUInt31]*/ < o. /*[exact=Class]*/ field;
- o. /*[exact=Class]*/ field;
+ o.field /*invoke: [exact=JSUInt31]*/ < o. /*[subclass=Object]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -107,7 +107,7 @@
// TODO(johnniwinther): The right-hand side should refine the left-hand side
// receiver.
o.field = o.field;
- o. /*[exact=Class]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -117,8 +117,8 @@
/*element: logicalOr:[null]*/
@AssumeDynamic()
logicalOr(/*[null|subclass=Object]*/ o) {
- o.field || o. /*[exact=Class]*/ field;
- o. /*[exact=Class]*/ field;
+ o.field || o. /*[subclass=Object]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -128,8 +128,8 @@
/*element: conditionalCondition:[null]*/
@AssumeDynamic()
conditionalCondition(/*[null|subclass=Object]*/ o) {
- o.field ? o. /*[exact=Class]*/ field : o. /*[exact=Class]*/ field;
- o. /*[exact=Class]*/ field;
+ o.field ? o. /*[subclass=Object]*/ field : o. /*[subclass=Object]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -141,7 +141,7 @@
conditionalBothBranches(/*[null|subclass=Object]*/ o) {
// ignore: DEAD_CODE
true ? o.field : o.field;
- o. /*[exact=Class]*/ field;
+ o. /*[subclass=Object]*/ field;
}
////////////////////////////////////////////////////////////////////////////////
@@ -154,5 +154,5 @@
// ignore: DEAD_CODE
true ? o.field : null;
o.field;
- o. /*[exact=Class]*/ field;
+ o. /*[subclass=Object]*/ field;
}
diff --git a/tools/dom/scripts/dartmetadata.py b/tools/dom/scripts/dartmetadata.py
index cb81c49..a5a92f1 100644
--- a/tools/dom/scripts/dartmetadata.py
+++ b/tools/dom/scripts/dartmetadata.py
@@ -157,7 +157,7 @@
# addEventListener on the target, so we avoid
'Event.currentTarget': [
"@Creates('Null')",
- "@Returns('EventTarget|=Object')",
+ "@Returns('EventTarget|=Object|Null')",
],
# Only nodes in the DOM bubble and have target !== currentTarget.
@@ -324,7 +324,7 @@
'MouseEvent.relatedTarget': [
"@Creates('Node')",
- "@Returns('EventTarget|=Object')",
+ "@Returns('EventTarget|=Object|Null')",
],
'Notification.data': [