| // 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 '../common.dart'; |
| import '../common/names.dart' show Identifiers, Names, Selectors; |
| import '../compiler.dart' show Compiler; |
| import '../elements/elements.dart'; |
| import '../tree/tree.dart'; |
| import 'backend.dart'; |
| |
| /** |
| * Categorizes `noSuchMethod` implementations. |
| * |
| * If user code includes `noSuchMethod` implementations, type inference is |
| * hindered because (for instance) any selector where the type of the |
| * receiver is not known all implementations of `noSuchMethod` must be taken |
| * into account when inferring the return type. |
| * |
| * The situation can be ameliorated with some heuristics for disregarding some |
| * `noSuchMethod` implementations during type inference. We can partition |
| * `noSuchMethod` implementations into 4 categories. |
| * |
| * Implementations in category A are the default implementations |
| * `Object.noSuchMethod` and `Interceptor.noSuchMethod`. |
| * |
| * Implementations in category B syntactically immediately throw, for example: |
| * |
| * noSuchMethod(x) => throw 'not implemented' |
| * |
| * Implementations in category C are not applicable, for example: |
| * |
| * noSuchMethod() { /* missing parameter */ } |
| * noSuchMethod(a, b) { /* too many parameters */ } |
| * |
| * Implementations that do not fall into category A, B or C are in category D. |
| * They are the only category of implementation that are considered during type |
| * inference. |
| * |
| * Implementations that syntactically just forward to the super implementation, |
| * for example: |
| * |
| * noSuchMethod(x) => super.noSuchMethod(x); |
| * |
| * are in the same category as the superclass implementation. This covers a |
| * common case, where users implement `noSuchMethod` with these dummy |
| * implementations to avoid warnings. |
| */ |
| class NoSuchMethodRegistry { |
| /// The implementations that fall into category A, described above. |
| final Set<FunctionElement> defaultImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that fall into category B, described above. |
| final Set<FunctionElement> throwingImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that fall into category C, described above. |
| final Set<FunctionElement> notApplicableImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that fall into category D, described above. |
| final Set<FunctionElement> otherImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that fall into category D1 |
| final Set<FunctionElement> complexNoReturnImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that fall into category D2 |
| final Set<FunctionElement> complexReturningImpls = new Set<FunctionElement>(); |
| |
| /// The implementations that have not yet been categorized. |
| final Set<FunctionElement> _uncategorizedImpls = new Set<FunctionElement>(); |
| |
| final JavaScriptBackend _backend; |
| final Compiler _compiler; |
| |
| NoSuchMethodRegistry(JavaScriptBackend backend) |
| : this._backend = backend, |
| this._compiler = backend.compiler; |
| |
| DiagnosticReporter get reporter => _compiler.reporter; |
| |
| bool get hasThrowingNoSuchMethod => throwingImpls.isNotEmpty; |
| bool get hasComplexNoSuchMethod => otherImpls.isNotEmpty; |
| |
| void registerNoSuchMethod(FunctionElement noSuchMethodElement) { |
| _uncategorizedImpls.add(noSuchMethodElement); |
| } |
| |
| void onQueueEmpty() { |
| _uncategorizedImpls.forEach(_categorizeImpl); |
| _uncategorizedImpls.clear(); |
| } |
| |
| /// Now that type inference is complete, split category D into two |
| /// subcategories: D1, those that have no return type, and D2, those |
| /// that have a return type. |
| void onTypeInferenceComplete() { |
| otherImpls.forEach(_subcategorizeOther); |
| } |
| |
| /// Emits a diagnostic |
| void emitDiagnostic() { |
| throwingImpls.forEach((e) { |
| if (!_hasForwardingSyntax(e)) { |
| reporter.reportHintMessage(e, MessageKind.DIRECTLY_THROWING_NSM); |
| } |
| }); |
| complexNoReturnImpls.forEach((e) { |
| if (!_hasForwardingSyntax(e)) { |
| reporter.reportHintMessage(e, MessageKind.COMPLEX_THROWING_NSM); |
| } |
| }); |
| complexReturningImpls.forEach((e) { |
| if (!_hasForwardingSyntax(e)) { |
| reporter.reportHintMessage(e, MessageKind.COMPLEX_RETURNING_NSM); |
| } |
| }); |
| } |
| |
| /// Returns [true] if the given element is a complex [noSuchMethod] |
| /// implementation. An implementation is complex if it falls into |
| /// category D, as described above. |
| bool isComplex(FunctionElement element) { |
| assert(element.name == Identifiers.noSuchMethod_); |
| return otherImpls.contains(element); |
| } |
| |
| _subcategorizeOther(FunctionElement element) { |
| if (_compiler.globalInference.results.resultOf(element).throwsAlways) { |
| complexNoReturnImpls.add(element); |
| } else { |
| complexReturningImpls.add(element); |
| } |
| } |
| |
| NsmCategory _categorizeImpl(FunctionElement element) { |
| assert(element.name == Identifiers.noSuchMethod_); |
| if (defaultImpls.contains(element)) { |
| return NsmCategory.DEFAULT; |
| } |
| if (throwingImpls.contains(element)) { |
| return NsmCategory.THROWING; |
| } |
| if (otherImpls.contains(element)) { |
| return NsmCategory.OTHER; |
| } |
| if (notApplicableImpls.contains(element)) { |
| return NsmCategory.NOT_APPLICABLE; |
| } |
| if (!Selectors.noSuchMethod_.signatureApplies(element)) { |
| notApplicableImpls.add(element); |
| return NsmCategory.NOT_APPLICABLE; |
| } |
| if (isDefaultNoSuchMethodImplementation(element)) { |
| defaultImpls.add(element); |
| return NsmCategory.DEFAULT; |
| } else if (_hasForwardingSyntax(element)) { |
| // If the implementation is 'noSuchMethod(x) => super.noSuchMethod(x);' |
| // then it is in the same category as the super call. |
| Element superCall = |
| element.enclosingClass.lookupSuperByName(Names.noSuchMethod_); |
| NsmCategory category = _categorizeImpl(superCall); |
| switch (category) { |
| case NsmCategory.DEFAULT: |
| defaultImpls.add(element); |
| break; |
| case NsmCategory.THROWING: |
| throwingImpls.add(element); |
| break; |
| case NsmCategory.OTHER: |
| otherImpls.add(element); |
| break; |
| case NsmCategory.NOT_APPLICABLE: |
| // If the super method is not applicable, the call is redirected to |
| // `Object.noSuchMethod`. |
| defaultImpls.add(element); |
| category = NsmCategory.DEFAULT; |
| break; |
| } |
| return category; |
| } else if (_hasThrowingSyntax(element)) { |
| throwingImpls.add(element); |
| return NsmCategory.THROWING; |
| } else { |
| otherImpls.add(element); |
| return NsmCategory.OTHER; |
| } |
| } |
| |
| bool isDefaultNoSuchMethodImplementation(FunctionElement element) { |
| ClassElement classElement = element.enclosingClass; |
| return classElement == _compiler.commonElements.objectClass || |
| classElement == _backend.helpers.jsInterceptorClass || |
| classElement == _backend.helpers.jsNullClass; |
| } |
| |
| bool _hasForwardingSyntax(FunctionElement element) { |
| // At this point we know that this is signature-compatible with |
| // Object.noSuchMethod, but it may have more than one argument as long as |
| // it only has one required argument. |
| if (!element.hasResolvedAst) { |
| // TODO(johnniwinther): Why do we see unresolved elements here? |
| return false; |
| } |
| ResolvedAst resolvedAst = element.resolvedAst; |
| if (resolvedAst.kind != ResolvedAstKind.PARSED) { |
| return false; |
| } |
| String param = element.parameters.first.name; |
| Statement body = resolvedAst.body; |
| Expression expr; |
| if (body is Return && body.isArrowBody) { |
| expr = body.expression; |
| } else if (body is Block && |
| !body.statements.isEmpty && |
| body.statements.nodes.tail.isEmpty) { |
| Statement stmt = body.statements.nodes.head; |
| if (stmt is Return && stmt.hasExpression) { |
| expr = stmt.expression; |
| } |
| } |
| if (expr is Send && expr.isTypeCast) { |
| Send sendExpr = expr; |
| var typeName = sendExpr.typeAnnotationFromIsCheckOrCast.typeName; |
| if (typeName is Identifier && typeName.source == "dynamic") { |
| expr = sendExpr.receiver; |
| } |
| } |
| if (expr is Send && |
| expr.isSuperCall && |
| expr.selector is Identifier && |
| (expr.selector as Identifier).source == Identifiers.noSuchMethod_) { |
| var arg = expr.arguments.head; |
| if (expr.arguments.tail.isEmpty && |
| arg is Send && |
| arg.argumentsNode == null && |
| arg.receiver == null && |
| arg.selector is Identifier && |
| arg.selector.source == param) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool _hasThrowingSyntax(FunctionElement element) { |
| if (!element.hasResolvedAst) { |
| // TODO(johnniwinther): Why do we see unresolved elements here? |
| return false; |
| } |
| ResolvedAst resolvedAst = element.resolvedAst; |
| if (resolvedAst.kind != ResolvedAstKind.PARSED) { |
| return false; |
| } |
| Statement body = resolvedAst.body; |
| if (body is Return && body.isArrowBody) { |
| if (body.expression is Throw) { |
| return true; |
| } |
| } else if (body is Block && |
| !body.statements.isEmpty && |
| body.statements.nodes.tail.isEmpty) { |
| if (body.statements.nodes.head is ExpressionStatement) { |
| ExpressionStatement stmt = body.statements.nodes.head; |
| return stmt.expression is Throw; |
| } |
| } |
| return false; |
| } |
| } |
| |
| enum NsmCategory { |
| DEFAULT, |
| THROWING, |
| NOT_APPLICABLE, |
| OTHER, |
| } |