Chromium Code Reviews| Index: pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart |
| diff --git a/pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart b/pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart |
| index d6a7f07842a086387805124d90c5937a6f3e622d..51469d8b88a213ee0c8be154dfb88159393d1357 100644 |
| --- a/pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart |
| +++ b/pkg/dev_compiler/lib/src/compiler/nullable_type_inference.dart |
| @@ -9,7 +9,8 @@ import 'package:analyzer/dart/ast/token.dart' show TokenType; |
| import 'package:analyzer/dart/ast/visitor.dart' show RecursiveAstVisitor; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| -import 'element_helpers.dart' show getStaticType, isInlineJS; |
| +import 'element_helpers.dart' show getStaticType, isInlineJS, findAnnotation; |
| +import 'js_interop.dart' show isNotNullAnnotation, isNullCheckAnnotation; |
| import 'property_model.dart'; |
| /// An inference engine for nullable types. |
| @@ -26,6 +27,7 @@ abstract class NullableTypeInference { |
| LibraryElement get dartCoreLibrary; |
| VirtualFieldModel get virtualFields; |
| + InterfaceType getImplementationType(DartType type); |
| bool isPrimitiveType(DartType type); |
| bool isObjectMember(String name); |
| @@ -47,6 +49,84 @@ abstract class NullableTypeInference { |
| /// Returns true if [expr] can be null. |
| bool isNullable(Expression expr) => _isNullable(expr); |
| + bool _isNonNullMethodInvocation(MethodInvocation expr) { |
| + // TODO(vsm): This logic overlaps with the resolver. |
| + // Where is the best place to put this? |
| + var e = resolutionMap.staticElementForIdentifier(expr.methodName); |
| + if (e == null) return false; |
| + if (isInlineJS(e)) { |
| + // Fix types for JS builtin calls. |
| + // |
| + // This code was taken from analyzer. It's not super sophisticated: |
| + // only looks for the type name in dart:core, so we just copy it here. |
| + // |
| + // TODO(jmesserly): we'll likely need something that can handle a wider |
| + // variety of types, especially when we get to JS interop. |
| + var args = expr.argumentList.arguments; |
| + var first = args.isNotEmpty ? args.first : null; |
| + if (first is SimpleStringLiteral) { |
| + var types = first.stringValue; |
| + if (types != '' && |
| + types != 'var' && |
| + !types.split('|').contains('Null')) { |
| + return true; |
| + } |
| + } |
| + } |
| + |
| + if (e.name == 'identical' && identical(e.library, dartCoreLibrary)) { |
| + return true; |
| + } |
| + // If this is a method call, check to see whether it is to a final |
| + // type for which we have a known implementation type (i.e. int, bool, |
| + // double, and String), and if so use the element for the implementation |
| + // type instead. |
| + if (e is MethodElement) { |
|
Jennifer Messerly
2017/08/23 19:49:37
conceptually, are we asking if the method is final
|
| + Element container = e.enclosingElement; |
| + if (container is ClassElement) { |
| + DartType targetType = container.type; |
| + InterfaceType implType = getImplementationType(targetType); |
| + if (implType != null) { |
| + MethodElement method = implType.lookUpMethod(e.name, dartCoreLibrary); |
| + if (method != null) e = method; |
| + } |
| + } |
| + } |
| + // If the method or function is annotated as returning a non-null value |
| + // then the result of the call is non-null. |
| + return (e is MethodElement || e is FunctionElement) && _assertedNotNull(e); |
|
Jennifer Messerly
2017/08/23 19:49:37
this seems potentially unsafe -- does it work if I
|
| + } |
| + |
| + bool _isNonNullProperty(Element element, String name) { |
| + if (element is! PropertyInducingElement && |
| + element is! PropertyAccessorElement) { |
| + return false; |
| + } |
| + // If this is a reference to an element of a type for which |
| + // we have a known implementation type (i.e. int, double, |
| + // bool, String), then use the element for the implementation |
| + // type. |
| + Element container = element.enclosingElement; |
| + if (container is ClassElement) { |
| + DartType targetType = container.type; |
| + InterfaceType implType = getImplementationType(targetType); |
|
Jennifer Messerly
2017/08/23 19:49:38
var?
|
| + if (implType != null) { |
| + PropertyAccessorElement getter = |
|
Jennifer Messerly
2017/08/23 19:49:37
var?
|
| + implType.lookUpGetter(name, dartCoreLibrary); |
| + if (getter != null) element = getter; |
| + } |
| + } |
| + // If the getter is a synthetic element, then any annotations will |
| + // be on the variable, so use those instead. |
| + if (element is PropertyAccessorElement && |
| + element.isSynthetic && |
| + element.variable != null) { |
|
Jennifer Messerly
2017/08/23 19:49:37
I'm pretty sure this is guaranteed to be non-null,
|
| + element = (element as PropertyAccessorElement).variable; |
|
Jennifer Messerly
2017/08/23 19:49:37
this could just be `return _assertedNotNull(elemen
|
| + } |
| + // Return true if the element is annotated as returning a non-null value. |
| + return _assertedNotNull(element); |
|
Jennifer Messerly
2017/08/23 19:51:11
this one also seems potentially unsafe -- I'm gues
|
| + } |
| + |
| /// Returns true if [expr] can be null, optionally using [localIsNullable] |
| /// for locals. |
| /// |
| @@ -59,13 +139,21 @@ abstract class NullableTypeInference { |
| // resulting value if that becomes an issue, so we maintain the invariant |
| // that each node is visited once. |
| Element element = null; |
| + String name = null; |
| if (expr is PropertyAccess && |
| expr.operator?.type != TokenType.QUESTION_PERIOD) { |
| element = expr.propertyName.staticElement; |
| + name = expr.propertyName.name; |
| + } else if (expr is PrefixedIdentifier) { |
| + element = expr.staticElement; |
| + name = expr.identifier.name; |
| } else if (expr is Identifier) { |
| element = expr.staticElement; |
| + name = expr.name; |
| } |
| if (element != null) { |
| + if (_isNonNullProperty(element, name)) return false; |
| + |
| // Type literals are not null. |
| if (element is ClassElement || element is FunctionTypeAliasElement) { |
| return false; |
| @@ -78,6 +166,10 @@ abstract class NullableTypeInference { |
| return !_notNullLocals.contains(element); |
| } |
| + if (element is ParameterElement && _assertedNotNull(element)) { |
| + return false; |
| + } |
| + |
| if (element is FunctionElement || element is MethodElement) { |
| // A function or method. This can't be null. |
| return false; |
| @@ -169,33 +261,8 @@ abstract class NullableTypeInference { |
| if (type != null && isPrimitiveType(type)) { |
| return false; |
| } |
| - if (expr is MethodInvocation) { |
| - // TODO(vsm): This logic overlaps with the resolver. |
| - // Where is the best place to put this? |
| - var e = resolutionMap.staticElementForIdentifier(expr.methodName); |
| - if (isInlineJS(e)) { |
| - // Fix types for JS builtin calls. |
| - // |
| - // This code was taken from analyzer. It's not super sophisticated: |
| - // only looks for the type name in dart:core, so we just copy it here. |
| - // |
| - // TODO(jmesserly): we'll likely need something that can handle a wider |
| - // variety of types, especially when we get to JS interop. |
| - var args = expr.argumentList.arguments; |
| - var first = args.isNotEmpty ? args.first : null; |
| - if (first is SimpleStringLiteral) { |
| - var types = first.stringValue; |
| - if (types != '' && |
| - types != 'var' && |
| - !types.split('|').contains('Null')) { |
| - return false; |
| - } |
| - } |
| - } |
| - |
| - if (e?.name == 'identical' && identical(e.library, dartCoreLibrary)) { |
| - return false; |
| - } |
| + if (expr is MethodInvocation && _isNonNullMethodInvocation(expr)) { |
| + return false; |
| } |
| // TODO(ochafik,jmesserly): handle other cases such as: refs to top-level |
| @@ -259,13 +326,31 @@ class _NullableLocalInference extends RecursiveAstVisitor { |
| _locals.add(element); |
| if (initializer != null) { |
| _visitAssignment(node.name, initializer); |
| - } else { |
| + } else if (!_assertedNotNull(element)) { |
| _nullableLocals.add(element); |
| } |
| } |
| super.visitVariableDeclaration(node); |
| } |
| + @override |
| + visitForEachStatement(ForEachStatement node) { |
| + if (node.identifier == null) { |
| + var declaration = node.loopVariable; |
| + var element = declaration.element; |
| + _locals.add(element); |
| + if (!_assertedNotNull(element)) { |
| + _nullableLocals.add(element); |
| + } |
| + } else { |
| + var element = node.identifier.staticElement; |
| + if (element is LocalVariableElement && !_assertedNotNull(element)) { |
| + _nullableLocals.add(element); |
| + } |
| + } |
| + super.visitForEachStatement(node); |
| + } |
| + |
| @override |
| visitCatchClause(CatchClause node) { |
| var e = node.exceptionParameter?.staticElement; |
| @@ -317,7 +402,7 @@ class _NullableLocalInference extends RecursiveAstVisitor { |
| void _visitAssignment(Expression left, Expression right) { |
| if (left is SimpleIdentifier) { |
| var element = left.staticElement; |
| - if (element is LocalVariableElement) { |
| + if (element is LocalVariableElement && !_assertedNotNull(element)) { |
| bool visitLocal(LocalVariableElement otherLocal) { |
| // Record the assignment. |
| _assignments |
| @@ -335,3 +420,7 @@ class _NullableLocalInference extends RecursiveAstVisitor { |
| } |
| } |
| } |
| + |
| +bool _assertedNotNull(Element e) => |
| + findAnnotation(e, isNotNullAnnotation) != null || |
| + findAnnotation(e, isNullCheckAnnotation) != null; |