OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:collection'; | 5 import 'dart:collection'; |
6 import 'package:analyzer/dart/ast/ast.dart'; | 6 import 'package:analyzer/dart/ast/ast.dart'; |
7 import 'package:analyzer/dart/ast/standard_resolution_map.dart'; | 7 import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
8 import 'package:analyzer/dart/ast/token.dart' show TokenType; | 8 import 'package:analyzer/dart/ast/token.dart' show TokenType; |
9 import 'package:analyzer/dart/ast/visitor.dart' show RecursiveAstVisitor; | 9 import 'package:analyzer/dart/ast/visitor.dart' show RecursiveAstVisitor; |
10 import 'package:analyzer/dart/element/element.dart'; | 10 import 'package:analyzer/dart/element/element.dart'; |
11 import 'package:analyzer/dart/element/type.dart'; | 11 import 'package:analyzer/dart/element/type.dart'; |
12 import 'element_helpers.dart' show getStaticType, isInlineJS; | 12 import 'element_helpers.dart' show getStaticType, isInlineJS, findAnnotation; |
13 import 'js_interop.dart' show isNotNullAnnotation, isNullCheckAnnotation; | |
13 import 'property_model.dart'; | 14 import 'property_model.dart'; |
14 | 15 |
15 /// An inference engine for nullable types. | 16 /// An inference engine for nullable types. |
16 /// | 17 /// |
17 /// This can answer questions about whether expressions are nullable | 18 /// This can answer questions about whether expressions are nullable |
18 /// (see [isNullable]). Given a set of compilation units in a library, it will | 19 /// (see [isNullable]). Given a set of compilation units in a library, it will |
19 /// determine if locals can be null using flow-insensitive analysis. | 20 /// determine if locals can be null using flow-insensitive analysis. |
20 /// | 21 /// |
21 /// The analysis for null expressions is conservative and incomplete, but it can | 22 /// The analysis for null expressions is conservative and incomplete, but it can |
22 /// optimize some patterns. | 23 /// optimize some patterns. |
23 // TODO(vsm): Revisit whether we really need this when we get | 24 // TODO(vsm): Revisit whether we really need this when we get |
24 // better non-nullability in the type system. | 25 // better non-nullability in the type system. |
25 abstract class NullableTypeInference { | 26 abstract class NullableTypeInference { |
26 LibraryElement get dartCoreLibrary; | 27 LibraryElement get dartCoreLibrary; |
27 VirtualFieldModel get virtualFields; | 28 VirtualFieldModel get virtualFields; |
28 | 29 |
30 InterfaceType getImplementationType(DartType type); | |
29 bool isPrimitiveType(DartType type); | 31 bool isPrimitiveType(DartType type); |
30 bool isObjectMember(String name); | 32 bool isObjectMember(String name); |
31 | 33 |
32 /// Known non-null local variables. | 34 /// Known non-null local variables. |
33 HashSet<LocalVariableElement> _notNullLocals; | 35 HashSet<LocalVariableElement> _notNullLocals; |
34 | 36 |
35 void inferNullableTypes(AstNode node) { | 37 void inferNullableTypes(AstNode node) { |
36 var visitor = new _NullableLocalInference(this); | 38 var visitor = new _NullableLocalInference(this); |
37 node.accept(visitor); | 39 node.accept(visitor); |
38 _notNullLocals = visitor.computeNotNullLocals(); | 40 _notNullLocals = visitor.computeNotNullLocals(); |
39 } | 41 } |
40 | 42 |
41 /// Adds a new variable, typically a compiler generated temporary, and record | 43 /// Adds a new variable, typically a compiler generated temporary, and record |
42 /// whether its type is nullable. | 44 /// whether its type is nullable. |
43 void addTemporaryVariable(LocalVariableElement local, {bool nullable: true}) { | 45 void addTemporaryVariable(LocalVariableElement local, {bool nullable: true}) { |
44 if (!nullable) _notNullLocals.add(local); | 46 if (!nullable) _notNullLocals.add(local); |
45 } | 47 } |
46 | 48 |
47 /// Returns true if [expr] can be null. | 49 /// Returns true if [expr] can be null. |
48 bool isNullable(Expression expr) => _isNullable(expr); | 50 bool isNullable(Expression expr) => _isNullable(expr); |
49 | 51 |
52 bool _isNonNullMethodInvocation(MethodInvocation expr) { | |
53 // TODO(vsm): This logic overlaps with the resolver. | |
54 // Where is the best place to put this? | |
55 var e = resolutionMap.staticElementForIdentifier(expr.methodName); | |
56 if (e == null) return false; | |
57 if (isInlineJS(e)) { | |
58 // Fix types for JS builtin calls. | |
59 // | |
60 // This code was taken from analyzer. It's not super sophisticated: | |
61 // only looks for the type name in dart:core, so we just copy it here. | |
62 // | |
63 // TODO(jmesserly): we'll likely need something that can handle a wider | |
64 // variety of types, especially when we get to JS interop. | |
65 var args = expr.argumentList.arguments; | |
66 var first = args.isNotEmpty ? args.first : null; | |
67 if (first is SimpleStringLiteral) { | |
68 var types = first.stringValue; | |
69 if (types != '' && | |
70 types != 'var' && | |
71 !types.split('|').contains('Null')) { | |
72 return true; | |
73 } | |
74 } | |
75 } | |
76 | |
77 if (e.name == 'identical' && identical(e.library, dartCoreLibrary)) { | |
78 return true; | |
79 } | |
80 // If this is a method call, check to see whether it is to a final | |
81 // type for which we have a known implementation type (i.e. int, bool, | |
82 // double, and String), and if so use the element for the implementation | |
83 // type instead. | |
84 if (e is MethodElement) { | |
Jennifer Messerly
2017/08/23 19:49:37
conceptually, are we asking if the method is final
| |
85 Element container = e.enclosingElement; | |
86 if (container is ClassElement) { | |
87 DartType targetType = container.type; | |
88 InterfaceType implType = getImplementationType(targetType); | |
89 if (implType != null) { | |
90 MethodElement method = implType.lookUpMethod(e.name, dartCoreLibrary); | |
91 if (method != null) e = method; | |
92 } | |
93 } | |
94 } | |
95 // If the method or function is annotated as returning a non-null value | |
96 // then the result of the call is non-null. | |
97 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
| |
98 } | |
99 | |
100 bool _isNonNullProperty(Element element, String name) { | |
101 if (element is! PropertyInducingElement && | |
102 element is! PropertyAccessorElement) { | |
103 return false; | |
104 } | |
105 // If this is a reference to an element of a type for which | |
106 // we have a known implementation type (i.e. int, double, | |
107 // bool, String), then use the element for the implementation | |
108 // type. | |
109 Element container = element.enclosingElement; | |
110 if (container is ClassElement) { | |
111 DartType targetType = container.type; | |
112 InterfaceType implType = getImplementationType(targetType); | |
Jennifer Messerly
2017/08/23 19:49:38
var?
| |
113 if (implType != null) { | |
114 PropertyAccessorElement getter = | |
Jennifer Messerly
2017/08/23 19:49:37
var?
| |
115 implType.lookUpGetter(name, dartCoreLibrary); | |
116 if (getter != null) element = getter; | |
117 } | |
118 } | |
119 // If the getter is a synthetic element, then any annotations will | |
120 // be on the variable, so use those instead. | |
121 if (element is PropertyAccessorElement && | |
122 element.isSynthetic && | |
123 element.variable != null) { | |
Jennifer Messerly
2017/08/23 19:49:37
I'm pretty sure this is guaranteed to be non-null,
| |
124 element = (element as PropertyAccessorElement).variable; | |
Jennifer Messerly
2017/08/23 19:49:37
this could just be `return _assertedNotNull(elemen
| |
125 } | |
126 // Return true if the element is annotated as returning a non-null value. | |
127 return _assertedNotNull(element); | |
Jennifer Messerly
2017/08/23 19:51:11
this one also seems potentially unsafe -- I'm gues
| |
128 } | |
129 | |
50 /// Returns true if [expr] can be null, optionally using [localIsNullable] | 130 /// Returns true if [expr] can be null, optionally using [localIsNullable] |
51 /// for locals. | 131 /// for locals. |
52 /// | 132 /// |
53 /// If [localIsNullable] is not supplied, this will use the known list of | 133 /// If [localIsNullable] is not supplied, this will use the known list of |
54 /// [_notNullLocals]. | 134 /// [_notNullLocals]. |
55 bool _isNullable(Expression expr, | 135 bool _isNullable(Expression expr, |
56 [bool localIsNullable(LocalVariableElement e)]) { | 136 [bool localIsNullable(LocalVariableElement e)]) { |
57 // TODO(jmesserly): we do recursive calls in a few places. This could | 137 // TODO(jmesserly): we do recursive calls in a few places. This could |
58 // leads to O(depth) cost for calling this function. We could store the | 138 // leads to O(depth) cost for calling this function. We could store the |
59 // resulting value if that becomes an issue, so we maintain the invariant | 139 // resulting value if that becomes an issue, so we maintain the invariant |
60 // that each node is visited once. | 140 // that each node is visited once. |
61 Element element = null; | 141 Element element = null; |
142 String name = null; | |
62 if (expr is PropertyAccess && | 143 if (expr is PropertyAccess && |
63 expr.operator?.type != TokenType.QUESTION_PERIOD) { | 144 expr.operator?.type != TokenType.QUESTION_PERIOD) { |
64 element = expr.propertyName.staticElement; | 145 element = expr.propertyName.staticElement; |
146 name = expr.propertyName.name; | |
147 } else if (expr is PrefixedIdentifier) { | |
148 element = expr.staticElement; | |
149 name = expr.identifier.name; | |
65 } else if (expr is Identifier) { | 150 } else if (expr is Identifier) { |
66 element = expr.staticElement; | 151 element = expr.staticElement; |
152 name = expr.name; | |
67 } | 153 } |
68 if (element != null) { | 154 if (element != null) { |
155 if (_isNonNullProperty(element, name)) return false; | |
156 | |
69 // Type literals are not null. | 157 // Type literals are not null. |
70 if (element is ClassElement || element is FunctionTypeAliasElement) { | 158 if (element is ClassElement || element is FunctionTypeAliasElement) { |
71 return false; | 159 return false; |
72 } | 160 } |
73 | 161 |
74 if (element is LocalVariableElement) { | 162 if (element is LocalVariableElement) { |
75 if (localIsNullable != null) { | 163 if (localIsNullable != null) { |
76 return localIsNullable(element); | 164 return localIsNullable(element); |
77 } | 165 } |
78 return !_notNullLocals.contains(element); | 166 return !_notNullLocals.contains(element); |
79 } | 167 } |
80 | 168 |
169 if (element is ParameterElement && _assertedNotNull(element)) { | |
170 return false; | |
171 } | |
172 | |
81 if (element is FunctionElement || element is MethodElement) { | 173 if (element is FunctionElement || element is MethodElement) { |
82 // A function or method. This can't be null. | 174 // A function or method. This can't be null. |
83 return false; | 175 return false; |
84 } | 176 } |
85 | 177 |
86 if (element is PropertyAccessorElement && element.isGetter) { | 178 if (element is PropertyAccessorElement && element.isGetter) { |
87 PropertyInducingElement variable = element.variable; | 179 PropertyInducingElement variable = element.variable; |
88 var isVirtual = | 180 var isVirtual = |
89 variable is FieldElement && virtualFields.isVirtual(variable); | 181 variable is FieldElement && virtualFields.isVirtual(variable); |
90 return isVirtual || (variable.computeConstantValue()?.isNull ?? true); | 182 return isVirtual || (variable.computeConstantValue()?.isNull ?? true); |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
162 type = getStaticType(expr.leftOperand); | 254 type = getStaticType(expr.leftOperand); |
163 } else if (expr is PrefixExpression) { | 255 } else if (expr is PrefixExpression) { |
164 if (expr.operator.type == TokenType.BANG) return false; | 256 if (expr.operator.type == TokenType.BANG) return false; |
165 type = getStaticType(expr.operand); | 257 type = getStaticType(expr.operand); |
166 } else if (expr is PostfixExpression) { | 258 } else if (expr is PostfixExpression) { |
167 type = getStaticType(expr.operand); | 259 type = getStaticType(expr.operand); |
168 } | 260 } |
169 if (type != null && isPrimitiveType(type)) { | 261 if (type != null && isPrimitiveType(type)) { |
170 return false; | 262 return false; |
171 } | 263 } |
172 if (expr is MethodInvocation) { | 264 if (expr is MethodInvocation && _isNonNullMethodInvocation(expr)) { |
173 // TODO(vsm): This logic overlaps with the resolver. | 265 return false; |
174 // Where is the best place to put this? | |
175 var e = resolutionMap.staticElementForIdentifier(expr.methodName); | |
176 if (isInlineJS(e)) { | |
177 // Fix types for JS builtin calls. | |
178 // | |
179 // This code was taken from analyzer. It's not super sophisticated: | |
180 // only looks for the type name in dart:core, so we just copy it here. | |
181 // | |
182 // TODO(jmesserly): we'll likely need something that can handle a wider | |
183 // variety of types, especially when we get to JS interop. | |
184 var args = expr.argumentList.arguments; | |
185 var first = args.isNotEmpty ? args.first : null; | |
186 if (first is SimpleStringLiteral) { | |
187 var types = first.stringValue; | |
188 if (types != '' && | |
189 types != 'var' && | |
190 !types.split('|').contains('Null')) { | |
191 return false; | |
192 } | |
193 } | |
194 } | |
195 | |
196 if (e?.name == 'identical' && identical(e.library, dartCoreLibrary)) { | |
197 return false; | |
198 } | |
199 } | 266 } |
200 | 267 |
201 // TODO(ochafik,jmesserly): handle other cases such as: refs to top-level | 268 // TODO(ochafik,jmesserly): handle other cases such as: refs to top-level |
202 // finals that have been assigned non-nullable values. | 269 // finals that have been assigned non-nullable values. |
203 | 270 |
204 // Failed to recognize a non-nullable case: assume it can be null. | 271 // Failed to recognize a non-nullable case: assume it can be null. |
205 return true; | 272 return true; |
206 } | 273 } |
207 } | 274 } |
208 | 275 |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
252 } | 319 } |
253 | 320 |
254 @override | 321 @override |
255 visitVariableDeclaration(VariableDeclaration node) { | 322 visitVariableDeclaration(VariableDeclaration node) { |
256 var element = node.element; | 323 var element = node.element; |
257 var initializer = node.initializer; | 324 var initializer = node.initializer; |
258 if (element is LocalVariableElement) { | 325 if (element is LocalVariableElement) { |
259 _locals.add(element); | 326 _locals.add(element); |
260 if (initializer != null) { | 327 if (initializer != null) { |
261 _visitAssignment(node.name, initializer); | 328 _visitAssignment(node.name, initializer); |
262 } else { | 329 } else if (!_assertedNotNull(element)) { |
263 _nullableLocals.add(element); | 330 _nullableLocals.add(element); |
264 } | 331 } |
265 } | 332 } |
266 super.visitVariableDeclaration(node); | 333 super.visitVariableDeclaration(node); |
267 } | 334 } |
268 | 335 |
269 @override | 336 @override |
337 visitForEachStatement(ForEachStatement node) { | |
338 if (node.identifier == null) { | |
339 var declaration = node.loopVariable; | |
340 var element = declaration.element; | |
341 _locals.add(element); | |
342 if (!_assertedNotNull(element)) { | |
343 _nullableLocals.add(element); | |
344 } | |
345 } else { | |
346 var element = node.identifier.staticElement; | |
347 if (element is LocalVariableElement && !_assertedNotNull(element)) { | |
348 _nullableLocals.add(element); | |
349 } | |
350 } | |
351 super.visitForEachStatement(node); | |
352 } | |
353 | |
354 @override | |
270 visitCatchClause(CatchClause node) { | 355 visitCatchClause(CatchClause node) { |
271 var e = node.exceptionParameter?.staticElement; | 356 var e = node.exceptionParameter?.staticElement; |
272 if (e != null) { | 357 if (e != null) { |
273 _locals.add(e); | 358 _locals.add(e); |
274 // TODO(jmesserly): we allow throwing of `null`, for better or worse. | 359 // TODO(jmesserly): we allow throwing of `null`, for better or worse. |
275 _nullableLocals.add(e); | 360 _nullableLocals.add(e); |
276 } | 361 } |
277 | 362 |
278 e = node.stackTraceParameter?.staticElement; | 363 e = node.stackTraceParameter?.staticElement; |
279 if (e != null) _locals.add(e); | 364 if (e != null) _locals.add(e); |
(...skipping 30 matching lines...) Expand all Loading... | |
310 var op = node.operator.type; | 395 var op = node.operator.type; |
311 if (op.isIncrementOperator) { | 396 if (op.isIncrementOperator) { |
312 _visitAssignment(node.operand, node); | 397 _visitAssignment(node.operand, node); |
313 } | 398 } |
314 super.visitPrefixExpression(node); | 399 super.visitPrefixExpression(node); |
315 } | 400 } |
316 | 401 |
317 void _visitAssignment(Expression left, Expression right) { | 402 void _visitAssignment(Expression left, Expression right) { |
318 if (left is SimpleIdentifier) { | 403 if (left is SimpleIdentifier) { |
319 var element = left.staticElement; | 404 var element = left.staticElement; |
320 if (element is LocalVariableElement) { | 405 if (element is LocalVariableElement && !_assertedNotNull(element)) { |
321 bool visitLocal(LocalVariableElement otherLocal) { | 406 bool visitLocal(LocalVariableElement otherLocal) { |
322 // Record the assignment. | 407 // Record the assignment. |
323 _assignments | 408 _assignments |
324 .putIfAbsent(otherLocal, () => new HashSet.identity()) | 409 .putIfAbsent(otherLocal, () => new HashSet.identity()) |
325 .add(element); | 410 .add(element); |
326 // Optimistically assume this local is not null. | 411 // Optimistically assume this local is not null. |
327 // We will validate this assumption later. | 412 // We will validate this assumption later. |
328 return false; | 413 return false; |
329 } | 414 } |
330 | 415 |
331 if (_nullInference._isNullable(right, visitLocal)) { | 416 if (_nullInference._isNullable(right, visitLocal)) { |
332 _nullableLocals.add(element); | 417 _nullableLocals.add(element); |
333 } | 418 } |
334 } | 419 } |
335 } | 420 } |
336 } | 421 } |
337 } | 422 } |
423 | |
424 bool _assertedNotNull(Element e) => | |
425 findAnnotation(e, isNotNullAnnotation) != null || | |
426 findAnnotation(e, isNullCheckAnnotation) != null; | |
OLD | NEW |