Index: lib/src/codegen/nullable_type_inference.dart |
diff --git a/lib/src/codegen/nullable_type_inference.dart b/lib/src/codegen/nullable_type_inference.dart |
index d8ec62c85ba7409711684a76ed1f19119af67cee..54999a4802d1788dabd647095502b5b20db92bad 100644 |
--- a/lib/src/codegen/nullable_type_inference.dart |
+++ b/lib/src/codegen/nullable_type_inference.dart |
@@ -7,7 +7,6 @@ import 'package:analyzer/dart/ast/ast.dart'; |
import 'package:analyzer/dart/ast/token.dart' show TokenType; |
import 'package:analyzer/dart/ast/visitor.dart' show RecursiveAstVisitor; |
import 'package:analyzer/src/generated/element.dart'; |
-import 'package:func/func.dart'; |
import '../utils.dart' show getStaticType, isInlineJS; |
/// An inference engine for nullable types. |
@@ -20,20 +19,22 @@ import '../utils.dart' show getStaticType, isInlineJS; |
/// optimize some patterns. |
// TODO(vsm): Revisit whether we really need this when we get |
// better non-nullability in the type system. |
-class NullableTypeInference { |
- final Func1<DartType, bool> _isPrimitiveType; |
+abstract class NullableTypeInference { |
+ bool isPrimitiveType(DartType type); |
+ bool isObjectProperty(String name); |
/// Known non-null local variables. |
HashSet<LocalVariableElement> _notNullLocals; |
- NullableTypeInference.forLibrary( |
- this._isPrimitiveType, Iterable<CompilationUnit> units) { |
+ void inferNullableTypesInLibrary(Iterable<CompilationUnit> units) { |
var visitor = new _NullableLocalInference(this); |
for (var unit in units) unit.accept(visitor); |
_notNullLocals = visitor.computeNotNullLocals(); |
} |
- void addVariable(LocalVariableElement local, {bool nullable: true}) { |
+ /// Adds a new variable, typically a compiler generated temporary, and record |
+ /// whether its type is nullable. |
+ void addTemporaryVariable(LocalVariableElement local, {bool nullable: true}) { |
if (!nullable) _notNullLocals.add(local); |
} |
@@ -51,19 +52,28 @@ class NullableTypeInference { |
// leads to O(depth) cost for calling this function. We could store the |
// resulting value if that becomes an issue, so we maintain the invariant |
// that each node is visited once. |
- |
- if (expr is SimpleIdentifier) { |
+ Element element = null; |
+ if (expr is PropertyAccess) { |
+ element = expr.propertyName.staticElement; |
+ } else if (expr is Identifier) { |
+ element = expr.staticElement; |
+ } |
+ if (element != null) { |
// Type literals are not null. |
- var e = expr.staticElement; |
- if (e is ClassElement || e is FunctionTypeAliasElement) { |
+ if (element is ClassElement || element is FunctionTypeAliasElement) { |
return false; |
} |
- if (e is LocalVariableElement) { |
+ if (element is LocalVariableElement) { |
if (localIsNullable != null) { |
- return localIsNullable(e); |
+ return localIsNullable(element); |
} |
- return !_notNullLocals.contains(e); |
+ return !_notNullLocals.contains(element); |
+ } |
+ |
+ if (element is FunctionElement || element is MethodElement) { |
+ // A function or method. This can't be null. |
+ return false; |
} |
// Other types of identifiers are nullable (parameters, fields). |
@@ -75,7 +85,27 @@ class NullableTypeInference { |
if (expr is FunctionExpression) return false; |
if (expr is ThisExpression) return false; |
if (expr is SuperExpression) return false; |
- if (expr is CascadeExpression) return false; |
+ if (expr is CascadeExpression) { |
+ // Cascades normally can't return `null`, because if the target is null, |
+ // they will throw noSuchMethod. |
+ // The only properties/methods on `null` are those on Object itself. |
+ for (var section in expr.cascadeSections) { |
+ Element e = null; |
+ if (section is PropertyAccess) { |
+ e = section.propertyName.staticElement; |
+ } else if (section is MethodInvocation) { |
+ e = section.methodName.staticElement; |
+ } else if (section is IndexExpression) { |
+ // Object does not have operator []=. |
+ return false; |
+ } |
+ // We encountered a non-Object method/property. |
+ if (e != null && !isObjectProperty(e.name)) { |
+ return false; |
+ } |
+ } |
+ return _isNullable(expr.target, localIsNullable); |
+ } |
if (expr is ConditionalExpression) { |
return _isNullable(expr.thenExpression, localIsNullable) || |
_isNullable(expr.elseExpression, localIsNullable); |
@@ -83,6 +113,25 @@ class NullableTypeInference { |
if (expr is ParenthesizedExpression) { |
return _isNullable(expr.expression, localIsNullable); |
} |
+ if (expr is InstanceCreationExpression) { |
+ var e = expr.staticElement; |
+ if (e == null) return true; |
+ |
+ // Follow redirects. |
+ while (e.redirectedConstructor != null) { |
+ e = e.redirectedConstructor; |
+ } |
+ |
+ // Generative constructors are not nullable. |
+ if (!e.isFactory) return false; |
+ |
+ // Factory constructors are nullable. However it is a bad pattern and |
+ // our own SDK will never do this. |
+ // TODO(jmesserly): we could enforce this for user-defined constructors. |
+ if (e.library.source.isInSystemLibrary) return false; |
+ |
+ return true; |
+ } |
DartType type = null; |
if (expr is BinaryExpression) { |
@@ -103,7 +152,7 @@ class NullableTypeInference { |
} else if (expr is PostfixExpression) { |
type = getStaticType(expr.operand); |
} |
- if (type != null && _isPrimitiveType(type)) { |
+ if (type != null && isPrimitiveType(type)) { |
return false; |
} |
if (expr is MethodInvocation) { |
@@ -127,12 +176,16 @@ class NullableTypeInference { |
} |
} |
} |
- // TODO(ochafik): Handle `identical` invocations. |
+ |
+ if (e != null && |
+ e.name == 'identical' && |
+ e.library.source.uri.toString() == 'dart:core') { |
+ return false; |
+ } |
} |
- // TODO(ochafik): Handle PrefixedIdentifier, refs to top-level finals |
- // that have been assigned non-nullable values, non-generative constructor |
- // calls, refs to local functions... |
+ // TODO(ochafik,jmesserly): handle other cases such as: refs to top-level |
+ // finals that have been assigned non-nullable values. |
// Failed to recognize a non-nullable case: assume it can be null. |
return true; |