| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library dev_compiler.src.codegen.nullability_inferrer; | |
| 6 | |
| 7 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | |
| 8 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | |
| 9 import 'package:analyzer/src/generated/element.dart'; | |
| 10 import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; | |
| 11 | |
| 12 import 'assignments_index.dart'; | |
| 13 import 'js_codegen.dart' show TemporaryVariableElement; | |
| 14 import '../utils.dart' show isInlineJS, DirectedGraph; | |
| 15 | |
| 16 typedef bool NullableExpressionPredicate(Expression expr); | |
| 17 | |
| 18 typedef DartType _StaticTypeGetter(Expression e); | |
| 19 typedef bool _JSBuiltinTypePredicate(DartType t); | |
| 20 | |
| 21 /// Infers flow-insensitive nullability for local variables. | |
| 22 // TODO(ochafik): Use flow-sensitive inference. | |
| 23 class NullabilityInferrer { | |
| 24 /// Index of assignments of [LocalVariableElement]s defined in this inferer's | |
| 25 /// context. | |
| 26 final Map<LocalVariableElement, List<Expression>> _assignments; | |
| 27 final _StaticTypeGetter getStaticType; | |
| 28 final _JSBuiltinTypePredicate isJSBuiltinType; | |
| 29 | |
| 30 NullabilityInferrer(Iterable<AstNode> context, | |
| 31 {this.getStaticType, this.isJSBuiltinType}) | |
| 32 : _assignments = indexLocalAssignments(context); | |
| 33 | |
| 34 /// Tests whether [expr] is nullable, with assumptions on local variable | |
| 35 /// nullability provided by [isNullableLocal]. | |
| 36 /// | |
| 37 /// If [assignmentsTarget] and [assignments] are not null, this also builds an | |
| 38 /// assignments graph (records assignments from each variable to other | |
| 39 /// variables it can be assigned to). For example, given an assignment: | |
| 40 /// | |
| 41 /// y = x; | |
| 42 /// | |
| 43 /// This will lead us to conclude that `y` can be any value that `x` could | |
| 44 /// hold at that location. For example, if we are not considering control | |
| 45 /// flow, this means `y` could contain any value that can ever be assigned to | |
| 46 /// `x`. | |
| 47 bool _isNullableExpression(Expression expr, | |
| 48 [bool isNullableLocal(LocalVariableElement e), | |
| 49 LocalVariableElement assignmentsTarget, | |
| 50 DirectedGraph<LocalVariableElement> assignments]) { | |
| 51 // TODO(vsm): Revisit whether we really need this when we get | |
| 52 // better non-nullability in the type system. | |
| 53 // TODO(jmesserly): we do recursive calls in a few places. This could | |
| 54 // leads to O(depth) cost for calling this function. We could store the | |
| 55 // resulting value if that becomes an issue, so we maintain the invariant | |
| 56 // that each node is visited once. | |
| 57 | |
| 58 if (expr is SimpleIdentifier) { | |
| 59 // Type literals are not null. | |
| 60 var e = expr.staticElement; | |
| 61 if (e is ClassElement || e is FunctionTypeAliasElement) { | |
| 62 return false; | |
| 63 } | |
| 64 | |
| 65 if (e is LocalVariableElement && isNullableLocal != null) { | |
| 66 assignments?.addEdge(e, assignmentsTarget); | |
| 67 return isNullableLocal(e); | |
| 68 } | |
| 69 return true; | |
| 70 } | |
| 71 bool recurse(Expression x) => _isNullableExpression( | |
| 72 x, isNullableLocal, assignmentsTarget, assignments); | |
| 73 | |
| 74 if (expr is Literal) return expr is NullLiteral; | |
| 75 if (expr is IsExpression) return false; | |
| 76 if (expr is FunctionExpression) return false; | |
| 77 if (expr is ThisExpression) return false; | |
| 78 if (expr is SuperExpression) return false; | |
| 79 if (expr is CascadeExpression) return recurse(expr.target); | |
| 80 if (expr is ConditionalExpression) { | |
| 81 return recurse(expr.thenExpression) || recurse(expr.elseExpression); | |
| 82 } | |
| 83 if (expr is ParenthesizedExpression) { | |
| 84 return recurse(expr.expression); | |
| 85 } | |
| 86 | |
| 87 DartType type = null; | |
| 88 if (expr is BinaryExpression) { | |
| 89 switch (expr.operator.type) { | |
| 90 case TokenType.EQ_EQ: | |
| 91 case TokenType.BANG_EQ: | |
| 92 case TokenType.AMPERSAND_AMPERSAND: | |
| 93 case TokenType.BAR_BAR: | |
| 94 return false; | |
| 95 case TokenType.QUESTION_QUESTION: | |
| 96 return recurse(expr.leftOperand) && recurse(expr.rightOperand); | |
| 97 } | |
| 98 type = getStaticType(expr.leftOperand); | |
| 99 } else if (expr is PrefixExpression) { | |
| 100 if (expr.operator.type == TokenType.BANG) return false; | |
| 101 type = getStaticType(expr.operand); | |
| 102 } else if (expr is PostfixExpression) { | |
| 103 type = getStaticType(expr.operand); | |
| 104 } | |
| 105 if (type != null && isJSBuiltinType(type)) { | |
| 106 return false; | |
| 107 } | |
| 108 if (expr is MethodInvocation) { | |
| 109 // TODO(vsm): This logic overlaps with the resolver. | |
| 110 // Where is the best place to put this? | |
| 111 var e = expr.methodName.staticElement; | |
| 112 if (isInlineJS(e)) { | |
| 113 // Fix types for JS builtin calls. | |
| 114 // | |
| 115 // This code was taken from analyzer. It's not super sophisticated: | |
| 116 // only looks for the type name in dart:core, so we just copy it here. | |
| 117 // | |
| 118 // TODO(jmesserly): we'll likely need something that can handle a wider | |
| 119 // variety of types, especially when we get to JS interop. | |
| 120 var args = expr.argumentList.arguments; | |
| 121 var first = args.isNotEmpty ? args.first : null; | |
| 122 if (first is SimpleStringLiteral) { | |
| 123 var types = first.stringValue; | |
| 124 if (!types.split('|').contains('Null')) { | |
| 125 return false; | |
| 126 } | |
| 127 } | |
| 128 } | |
| 129 // TODO(ochafik): Handle `identical` invocations. | |
| 130 } | |
| 131 | |
| 132 // TODO(ochafik): Handle PrefixedIdentifier, refs to top-level finals | |
| 133 // that have been assigned non-nullable values, non-generative constructor | |
| 134 // calls, refs to local functions... | |
| 135 | |
| 136 // Failed to recognize a non-nullable case: assume it's trivially nullable. | |
| 137 return true; | |
| 138 } | |
| 139 | |
| 140 NullableExpressionPredicate buildNullabilityPredicate() { | |
| 141 // Collect the transitive closure of every variable that can be assigned | |
| 142 // trivially nullable values. | |
| 143 var assignmentsGraph = new DirectedGraph<LocalVariableElement>(); | |
| 144 | |
| 145 /// Detect vars that are "trivially nullable" (i.e. provably nullable | |
| 146 /// if we assume all known variables are non-nullable). | |
| 147 var trivialNullables = new Set<LocalVariableElement>(); | |
| 148 _assignments.forEach((local, expressions) { | |
| 149 for (var expr in expressions) { | |
| 150 // In the unlikely event of an unknown var, assume it's nullable. | |
| 151 var isTriviallyNullable = _isNullableExpression(expr, | |
| 152 (e) => e is TemporaryVariableElement, local, assignmentsGraph); | |
| 153 if (isTriviallyNullable) trivialNullables.add(local); | |
| 154 } | |
| 155 }); | |
| 156 var nullables = assignmentsGraph.getTransitiveClosure(trivialNullables); | |
| 157 | |
| 158 bool isNullableLocal(LocalVariableElement e) { | |
| 159 // TODO(jmesserly): we should be able to make this work for temps too. | |
| 160 return e is TemporaryVariableElement || nullables.contains(e); | |
| 161 } | |
| 162 return (Expression expr) => _isNullableExpression(expr, isNullableLocal); | |
| 163 } | |
| 164 } | |
| OLD | NEW |