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 |
deleted file mode 100644 |
index 562d63d7885ae2b07ca7f55faee12b768adf0691..0000000000000000000000000000000000000000 |
--- a/lib/src/codegen/nullable_type_inference.dart |
+++ /dev/null |
@@ -1,323 +0,0 @@ |
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
-// 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 'dart:collection'; |
-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/dart/element/element.dart'; |
-import 'package:analyzer/dart/element/type.dart'; |
-import '../utils.dart' show getStaticType, isInlineJS; |
- |
-/// An inference engine for nullable types. |
-/// |
-/// This can answer questions about whether expressions are nullable |
-/// (see [isNullable]). Given a set of compilation units in a library, it will |
-/// determine if locals can be null using flow-insensitive analysis. |
-/// |
-/// The analysis for null expressions is conservative and incomplete, but it can |
-/// optimize some patterns. |
-// TODO(vsm): Revisit whether we really need this when we get |
-// better non-nullability in the type system. |
-abstract class NullableTypeInference { |
- bool isPrimitiveType(DartType type); |
- bool isObjectProperty(String name); |
- |
- /// Known non-null local variables. |
- HashSet<LocalVariableElement> _notNullLocals; |
- |
- void inferNullableTypesInLibrary(Iterable<CompilationUnit> units) { |
- var visitor = new _NullableLocalInference(this); |
- for (var unit in units) unit.accept(visitor); |
- _notNullLocals = visitor.computeNotNullLocals(); |
- } |
- |
- /// 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); |
- } |
- |
- /// Returns true if [expr] can be null. |
- bool isNullable(Expression expr) => _isNullable(expr); |
- |
- /// Returns true if [expr] can be null, optionally using [localIsNullable] |
- /// for locals. |
- /// |
- /// If [localIsNullable] is not supplied, this will use the known list of |
- /// [_notNullLocals]. |
- bool _isNullable(Expression expr, |
- [bool localIsNullable(LocalVariableElement e)]) { |
- // TODO(jmesserly): we do recursive calls in a few places. This could |
- // 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. |
- 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. |
- if (element is ClassElement || element is FunctionTypeAliasElement) { |
- return false; |
- } |
- |
- if (element is LocalVariableElement) { |
- if (localIsNullable != null) { |
- return localIsNullable(element); |
- } |
- 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). |
- return true; |
- } |
- |
- if (expr is Literal) return expr is NullLiteral; |
- if (expr is IsExpression) return false; |
- if (expr is FunctionExpression) return false; |
- if (expr is ThisExpression) return false; |
- if (expr is SuperExpression) 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); |
- } |
- 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) { |
- switch (expr.operator.type) { |
- case TokenType.EQ_EQ: |
- case TokenType.BANG_EQ: |
- case TokenType.AMPERSAND_AMPERSAND: |
- case TokenType.BAR_BAR: |
- return false; |
- case TokenType.QUESTION_QUESTION: |
- return _isNullable(expr.leftOperand, localIsNullable) && |
- _isNullable(expr.rightOperand, localIsNullable); |
- } |
- type = getStaticType(expr.leftOperand); |
- } else if (expr is PrefixExpression) { |
- if (expr.operator.type == TokenType.BANG) return false; |
- type = getStaticType(expr.operand); |
- } else if (expr is PostfixExpression) { |
- type = getStaticType(expr.operand); |
- } |
- 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 = expr.methodName.staticElement; |
- 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.split('|').contains('Null')) { |
- return false; |
- } |
- } |
- } |
- |
- if (e != null && |
- e.name == 'identical' && |
- e.library.source.uri.toString() == 'dart:core') { |
- return false; |
- } |
- } |
- |
- // 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; |
- } |
-} |
- |
-/// A visitor that determines which local variables are non-nullable. |
-/// |
-/// This will consider all assignments to local variables using |
-/// flow-insensitive inference. That information is used to determine which |
-/// variables are nullable in the given scope. |
-// TODO(ochafik): Introduce flow analysis (a variable may be nullable in |
-// some places and not in others). |
-class _NullableLocalInference extends RecursiveAstVisitor { |
- final NullableTypeInference _nullInference; |
- |
- /// Known local variables. |
- final _locals = new HashSet<LocalVariableElement>.identity(); |
- |
- /// Variables that are known to be nullable. |
- final _nullableLocals = new HashSet<LocalVariableElement>.identity(); |
- |
- /// Given a variable, tracks all other variables that it is assigned to. |
- final _assignments = |
- new HashMap<LocalVariableElement, Set<LocalVariableElement>>.identity(); |
- |
- _NullableLocalInference(this._nullInference); |
- |
- /// After visiting nodes, this can be called to compute the set of not-null |
- /// locals. |
- /// |
- /// This method must only be called once. After it is called, the visitor |
- /// should be discarded. |
- HashSet<LocalVariableElement> computeNotNullLocals() { |
- // Given a set of variables that are nullable, remove them from our list of |
- // local variables. The end result of this process is a list of variables |
- // known to be not null. |
- visitNullableLocal(LocalVariableElement e) { |
- _locals.remove(e); |
- |
- // Visit all other locals that this one is assigned to, and record that |
- // they are nullable too. |
- _assignments.remove(e)?.forEach(visitNullableLocal); |
- } |
- _nullableLocals.forEach(visitNullableLocal); |
- |
- // Any remaining locals are non-null. |
- return _locals; |
- } |
- |
- @override |
- visitVariableDeclaration(VariableDeclaration node) { |
- var element = node.element; |
- var initializer = node.initializer; |
- if (element is LocalVariableElement) { |
- _locals.add(element); |
- if (initializer != null) { |
- _visitAssignment(node.name, initializer); |
- } else { |
- _nullableLocals.add(element); |
- } |
- } |
- super.visitVariableDeclaration(node); |
- } |
- |
- @override |
- visitCatchClause(CatchClause node) { |
- var e = node.exceptionParameter?.staticElement; |
- if (e != null) { |
- _locals.add(e); |
- // TODO(jmesserly): we allow throwing of `null`, for better or worse. |
- _nullableLocals.add(e); |
- } |
- |
- e = node.stackTraceParameter?.staticElement; |
- if (e != null) _locals.add(e); |
- |
- super.visitCatchClause(node); |
- } |
- |
- @override |
- visitAssignmentExpression(AssignmentExpression node) { |
- _visitAssignment(node.leftHandSide, node.rightHandSide); |
- super.visitAssignmentExpression(node); |
- } |
- |
- @override |
- visitBinaryExpression(BinaryExpression node) { |
- var op = node.operator.type; |
- if (op.isAssignmentOperator) { |
- _visitAssignment(node.leftOperand, node); |
- } |
- super.visitBinaryExpression(node); |
- } |
- |
- @override |
- visitPostfixExpression(PostfixExpression node) { |
- var op = node.operator.type; |
- if (op.isIncrementOperator) { |
- _visitAssignment(node.operand, node); |
- } |
- super.visitPostfixExpression(node); |
- } |
- |
- @override |
- visitPrefixExpression(PrefixExpression node) { |
- var op = node.operator.type; |
- if (op.isIncrementOperator) { |
- _visitAssignment(node.operand, node); |
- } |
- super.visitPrefixExpression(node); |
- } |
- |
- void _visitAssignment(Expression left, Expression right) { |
- if (left is SimpleIdentifier) { |
- var element = left.staticElement; |
- if (element is LocalVariableElement) { |
- bool visitLocal(LocalVariableElement otherLocal) { |
- // Record the assignment. |
- _assignments |
- .putIfAbsent(otherLocal, () => new HashSet.identity()) |
- .add(element); |
- // Optimistically assume this local is not null. |
- // We will validate this assumption later. |
- return false; |
- } |
- |
- if (_nullInference._isNullable(right, visitLocal)) { |
- _nullableLocals.add(element); |
- } |
- } |
- } |
- } |
-} |