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 '../common.dart'; | 5 import '../common.dart'; |
6 import '../common/names.dart' show Identifiers, Names, Selectors; | 6 import '../common/names.dart' show Identifiers, Names, Selectors; |
7 import '../compiler.dart' show Compiler; | 7 import '../common_elements.dart'; |
8 import '../elements/elements.dart'; | 8 import '../elements/elements.dart'; |
| 9 import '../types/types.dart'; |
9 import '../tree/tree.dart'; | 10 import '../tree/tree.dart'; |
10 import 'backend.dart'; | 11 import 'backend_helpers.dart'; |
11 | 12 |
12 /** | 13 /** |
13 * Categorizes `noSuchMethod` implementations. | 14 * Categorizes `noSuchMethod` implementations. |
14 * | 15 * |
15 * If user code includes `noSuchMethod` implementations, type inference is | 16 * If user code includes `noSuchMethod` implementations, type inference is |
16 * hindered because (for instance) any selector where the type of the | 17 * hindered because (for instance) any selector where the type of the |
17 * receiver is not known all implementations of `noSuchMethod` must be taken | 18 * receiver is not known all implementations of `noSuchMethod` must be taken |
18 * into account when inferring the return type. | 19 * into account when inferring the return type. |
19 * | 20 * |
20 * The situation can be ameliorated with some heuristics for disregarding some | 21 * The situation can be ameliorated with some heuristics for disregarding some |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
61 | 62 |
62 /// The implementations that fall into category D1 | 63 /// The implementations that fall into category D1 |
63 final Set<MethodElement> complexNoReturnImpls = new Set<MethodElement>(); | 64 final Set<MethodElement> complexNoReturnImpls = new Set<MethodElement>(); |
64 | 65 |
65 /// The implementations that fall into category D2 | 66 /// The implementations that fall into category D2 |
66 final Set<MethodElement> complexReturningImpls = new Set<MethodElement>(); | 67 final Set<MethodElement> complexReturningImpls = new Set<MethodElement>(); |
67 | 68 |
68 /// The implementations that have not yet been categorized. | 69 /// The implementations that have not yet been categorized. |
69 final Set<MethodElement> _uncategorizedImpls = new Set<MethodElement>(); | 70 final Set<MethodElement> _uncategorizedImpls = new Set<MethodElement>(); |
70 | 71 |
71 final JavaScriptBackend _backend; | 72 final BackendHelpers _helpers; |
72 final Compiler _compiler; | 73 final NoSuchMethodResolver _resolver; |
73 | 74 |
74 NoSuchMethodRegistry(JavaScriptBackend backend) | 75 NoSuchMethodRegistry(this._helpers, this._resolver); |
75 : this._backend = backend, | |
76 this._compiler = backend.compiler; | |
77 | |
78 DiagnosticReporter get reporter => _compiler.reporter; | |
79 | 76 |
80 bool get hasThrowingNoSuchMethod => throwingImpls.isNotEmpty; | 77 bool get hasThrowingNoSuchMethod => throwingImpls.isNotEmpty; |
81 bool get hasComplexNoSuchMethod => otherImpls.isNotEmpty; | 78 bool get hasComplexNoSuchMethod => otherImpls.isNotEmpty; |
82 | 79 |
83 void registerNoSuchMethod(MethodElement noSuchMethodElement) { | 80 void registerNoSuchMethod(MethodElement noSuchMethodElement) { |
84 _uncategorizedImpls.add(noSuchMethodElement); | 81 _uncategorizedImpls.add(noSuchMethodElement); |
85 } | 82 } |
86 | 83 |
87 void onQueueEmpty() { | 84 void onQueueEmpty() { |
88 _uncategorizedImpls.forEach(_categorizeImpl); | 85 _uncategorizedImpls.forEach(_categorizeImpl); |
89 _uncategorizedImpls.clear(); | 86 _uncategorizedImpls.clear(); |
90 } | 87 } |
91 | 88 |
92 /// Now that type inference is complete, split category D into two | 89 /// Now that type inference is complete, split category D into two |
93 /// subcategories: D1, those that have no return type, and D2, those | 90 /// subcategories: D1, those that have no return type, and D2, those |
94 /// that have a return type. | 91 /// that have a return type. |
95 void onTypeInferenceComplete() { | 92 void onTypeInferenceComplete(GlobalTypeInferenceResults results) { |
96 otherImpls.forEach(_subcategorizeOther); | 93 otherImpls.forEach((MethodElement element) { |
| 94 if (results.resultOf(element).throwsAlways) { |
| 95 complexNoReturnImpls.add(element); |
| 96 } else { |
| 97 complexReturningImpls.add(element); |
| 98 } |
| 99 }); |
97 } | 100 } |
98 | 101 |
99 /// Emits a diagnostic | 102 /// Emits a diagnostic |
100 void emitDiagnostic() { | 103 void emitDiagnostic(DiagnosticReporter reporter) { |
101 throwingImpls.forEach((e) { | 104 throwingImpls.forEach((e) { |
102 if (!_hasForwardingSyntax(e)) { | 105 if (!_resolver.hasForwardingSyntax(e)) { |
103 reporter.reportHintMessage(e, MessageKind.DIRECTLY_THROWING_NSM); | 106 reporter.reportHintMessage(e, MessageKind.DIRECTLY_THROWING_NSM); |
104 } | 107 } |
105 }); | 108 }); |
106 complexNoReturnImpls.forEach((e) { | 109 complexNoReturnImpls.forEach((e) { |
107 if (!_hasForwardingSyntax(e)) { | 110 if (!_resolver.hasForwardingSyntax(e)) { |
108 reporter.reportHintMessage(e, MessageKind.COMPLEX_THROWING_NSM); | 111 reporter.reportHintMessage(e, MessageKind.COMPLEX_THROWING_NSM); |
109 } | 112 } |
110 }); | 113 }); |
111 complexReturningImpls.forEach((e) { | 114 complexReturningImpls.forEach((e) { |
112 if (!_hasForwardingSyntax(e)) { | 115 if (!_resolver.hasForwardingSyntax(e)) { |
113 reporter.reportHintMessage(e, MessageKind.COMPLEX_RETURNING_NSM); | 116 reporter.reportHintMessage(e, MessageKind.COMPLEX_RETURNING_NSM); |
114 } | 117 } |
115 }); | 118 }); |
116 } | 119 } |
117 | 120 |
118 /// Returns [true] if the given element is a complex [noSuchMethod] | 121 /// Returns [true] if the given element is a complex [noSuchMethod] |
119 /// implementation. An implementation is complex if it falls into | 122 /// implementation. An implementation is complex if it falls into |
120 /// category D, as described above. | 123 /// category D, as described above. |
121 bool isComplex(MethodElement element) { | 124 bool isComplex(MethodElement element) { |
122 assert(element.name == Identifiers.noSuchMethod_); | 125 assert(element.name == Identifiers.noSuchMethod_); |
123 return otherImpls.contains(element); | 126 return otherImpls.contains(element); |
124 } | 127 } |
125 | 128 |
126 _subcategorizeOther(MethodElement element) { | |
127 if (_compiler.globalInference.results.resultOf(element).throwsAlways) { | |
128 complexNoReturnImpls.add(element); | |
129 } else { | |
130 complexReturningImpls.add(element); | |
131 } | |
132 } | |
133 | |
134 NsmCategory _categorizeImpl(MethodElement element) { | 129 NsmCategory _categorizeImpl(MethodElement element) { |
135 assert(element.name == Identifiers.noSuchMethod_); | 130 assert(element.name == Identifiers.noSuchMethod_); |
136 if (defaultImpls.contains(element)) { | 131 if (defaultImpls.contains(element)) { |
137 return NsmCategory.DEFAULT; | 132 return NsmCategory.DEFAULT; |
138 } | 133 } |
139 if (throwingImpls.contains(element)) { | 134 if (throwingImpls.contains(element)) { |
140 return NsmCategory.THROWING; | 135 return NsmCategory.THROWING; |
141 } | 136 } |
142 if (otherImpls.contains(element)) { | 137 if (otherImpls.contains(element)) { |
143 return NsmCategory.OTHER; | 138 return NsmCategory.OTHER; |
144 } | 139 } |
145 if (notApplicableImpls.contains(element)) { | 140 if (notApplicableImpls.contains(element)) { |
146 return NsmCategory.NOT_APPLICABLE; | 141 return NsmCategory.NOT_APPLICABLE; |
147 } | 142 } |
148 if (!Selectors.noSuchMethod_.signatureApplies(element)) { | 143 if (!Selectors.noSuchMethod_.signatureApplies(element)) { |
149 notApplicableImpls.add(element); | 144 notApplicableImpls.add(element); |
150 return NsmCategory.NOT_APPLICABLE; | 145 return NsmCategory.NOT_APPLICABLE; |
151 } | 146 } |
152 if (isDefaultNoSuchMethodImplementation(element)) { | 147 if (_helpers.isDefaultNoSuchMethodImplementation(element)) { |
153 defaultImpls.add(element); | 148 defaultImpls.add(element); |
154 return NsmCategory.DEFAULT; | 149 return NsmCategory.DEFAULT; |
155 } else if (_hasForwardingSyntax(element)) { | 150 } else if (_resolver.hasForwardingSyntax(element)) { |
156 // If the implementation is 'noSuchMethod(x) => super.noSuchMethod(x);' | 151 // If the implementation is 'noSuchMethod(x) => super.noSuchMethod(x);' |
157 // then it is in the same category as the super call. | 152 // then it is in the same category as the super call. |
158 Element superCall = | 153 Element superCall = |
159 element.enclosingClass.lookupSuperByName(Names.noSuchMethod_); | 154 element.enclosingClass.lookupSuperByName(Names.noSuchMethod_); |
160 NsmCategory category = _categorizeImpl(superCall); | 155 NsmCategory category = _categorizeImpl(superCall); |
161 switch (category) { | 156 switch (category) { |
162 case NsmCategory.DEFAULT: | 157 case NsmCategory.DEFAULT: |
163 defaultImpls.add(element); | 158 defaultImpls.add(element); |
164 break; | 159 break; |
165 case NsmCategory.THROWING: | 160 case NsmCategory.THROWING: |
166 throwingImpls.add(element); | 161 throwingImpls.add(element); |
167 break; | 162 break; |
168 case NsmCategory.OTHER: | 163 case NsmCategory.OTHER: |
169 otherImpls.add(element); | 164 otherImpls.add(element); |
170 break; | 165 break; |
171 case NsmCategory.NOT_APPLICABLE: | 166 case NsmCategory.NOT_APPLICABLE: |
172 // If the super method is not applicable, the call is redirected to | 167 // If the super method is not applicable, the call is redirected to |
173 // `Object.noSuchMethod`. | 168 // `Object.noSuchMethod`. |
174 defaultImpls.add(element); | 169 defaultImpls.add(element); |
175 category = NsmCategory.DEFAULT; | 170 category = NsmCategory.DEFAULT; |
176 break; | 171 break; |
177 } | 172 } |
178 return category; | 173 return category; |
179 } else if (_hasThrowingSyntax(element)) { | 174 } else if (_resolver.hasThrowingSyntax(element)) { |
180 throwingImpls.add(element); | 175 throwingImpls.add(element); |
181 return NsmCategory.THROWING; | 176 return NsmCategory.THROWING; |
182 } else { | 177 } else { |
183 otherImpls.add(element); | 178 otherImpls.add(element); |
184 return NsmCategory.OTHER; | 179 return NsmCategory.OTHER; |
185 } | 180 } |
186 } | 181 } |
| 182 } |
187 | 183 |
188 bool isDefaultNoSuchMethodImplementation(MethodElement element) { | 184 enum NsmCategory { |
189 ClassElement classElement = element.enclosingClass; | 185 DEFAULT, |
190 return classElement == _compiler.commonElements.objectClass || | 186 THROWING, |
191 classElement == _backend.helpers.jsInterceptorClass || | 187 NOT_APPLICABLE, |
192 classElement == _backend.helpers.jsNullClass; | 188 OTHER, |
193 } | 189 } |
194 | 190 |
195 bool _hasForwardingSyntax(MethodElement element) { | 191 /// Interface for determining the form of a `noSuchMethod` implementation. |
| 192 abstract class NoSuchMethodResolver { |
| 193 /// Computes whether [method] is of the form |
| 194 /// |
| 195 /// noSuchMethod(i) => super.noSuchMethod(i); |
| 196 /// |
| 197 bool hasForwardingSyntax(MethodElement method); |
| 198 |
| 199 /// Computes whether [method] is of the form |
| 200 /// |
| 201 /// noSuchMethod(i) => throw new Error(); |
| 202 /// |
| 203 bool hasThrowingSyntax(MethodElement method); |
| 204 } |
| 205 |
| 206 /// AST-based implementation of [NoSuchMethodResolver]. |
| 207 class NoSuchMethodResolverImpl implements NoSuchMethodResolver { |
| 208 bool hasForwardingSyntax(MethodElement element) { |
196 // At this point we know that this is signature-compatible with | 209 // At this point we know that this is signature-compatible with |
197 // Object.noSuchMethod, but it may have more than one argument as long as | 210 // Object.noSuchMethod, but it may have more than one argument as long as |
198 // it only has one required argument. | 211 // it only has one required argument. |
199 if (!element.hasResolvedAst) { | 212 if (!element.hasResolvedAst) { |
200 // TODO(johnniwinther): Why do we see unresolved elements here? | 213 // TODO(johnniwinther): Why do we see unresolved elements here? |
201 return false; | 214 return false; |
202 } | 215 } |
203 ResolvedAst resolvedAst = element.resolvedAst; | 216 ResolvedAst resolvedAst = element.resolvedAst; |
204 if (resolvedAst.kind != ResolvedAstKind.PARSED) { | 217 if (resolvedAst.kind != ResolvedAstKind.PARSED) { |
205 return false; | 218 return false; |
(...skipping 29 matching lines...) Expand all Loading... |
235 arg.argumentsNode == null && | 248 arg.argumentsNode == null && |
236 arg.receiver == null && | 249 arg.receiver == null && |
237 arg.selector is Identifier && | 250 arg.selector is Identifier && |
238 arg.selector.source == param) { | 251 arg.selector.source == param) { |
239 return true; | 252 return true; |
240 } | 253 } |
241 } | 254 } |
242 return false; | 255 return false; |
243 } | 256 } |
244 | 257 |
245 bool _hasThrowingSyntax(MethodElement element) { | 258 bool hasThrowingSyntax(MethodElement element) { |
246 if (!element.hasResolvedAst) { | 259 if (!element.hasResolvedAst) { |
247 // TODO(johnniwinther): Why do we see unresolved elements here? | 260 // TODO(johnniwinther): Why do we see unresolved elements here? |
248 return false; | 261 return false; |
249 } | 262 } |
250 ResolvedAst resolvedAst = element.resolvedAst; | 263 ResolvedAst resolvedAst = element.resolvedAst; |
251 if (resolvedAst.kind != ResolvedAstKind.PARSED) { | 264 if (resolvedAst.kind != ResolvedAstKind.PARSED) { |
252 return false; | 265 return false; |
253 } | 266 } |
254 Statement body = resolvedAst.body; | 267 Statement body = resolvedAst.body; |
255 if (body is Return && body.isArrowBody) { | 268 if (body is Return && body.isArrowBody) { |
256 if (body.expression is Throw) { | 269 if (body.expression is Throw) { |
257 return true; | 270 return true; |
258 } | 271 } |
259 } else if (body is Block && | 272 } else if (body is Block && |
260 !body.statements.isEmpty && | 273 !body.statements.isEmpty && |
261 body.statements.nodes.tail.isEmpty) { | 274 body.statements.nodes.tail.isEmpty) { |
262 if (body.statements.nodes.head is ExpressionStatement) { | 275 if (body.statements.nodes.head is ExpressionStatement) { |
263 ExpressionStatement stmt = body.statements.nodes.head; | 276 ExpressionStatement stmt = body.statements.nodes.head; |
264 return stmt.expression is Throw; | 277 return stmt.expression is Throw; |
265 } | 278 } |
266 } | 279 } |
267 return false; | 280 return false; |
268 } | 281 } |
269 } | 282 } |
270 | |
271 enum NsmCategory { | |
272 DEFAULT, | |
273 THROWING, | |
274 NOT_APPLICABLE, | |
275 OTHER, | |
276 } | |
OLD | NEW |