| 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 |