blob: 0a322bdb03fea62e00e3b5ed957c9faf4b3cc9be [file] [log] [blame]
// 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.
part of js_backend;
/**
* 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 3 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 that do not fall into category A or B are in category C. 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> otherImpls = new Set<FunctionElement>();
/// The implementations that fall into category C1
final Set<FunctionElement> complexNoReturnImpls = new Set<FunctionElement>();
/// The implementations that fall into category C2
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;
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 C into two
/// subcategories: C1, those that have no return type, and C2, those
/// that have a return type.
void onTypeInferenceComplete() {
otherImpls.forEach(_subcategorizeOther);
}
/// Emits a diagnostic
void emitDiagnostic() {
throwingImpls.forEach((e) {
if (!_hasForwardingSyntax(e)) {
_compiler.reportHintMessage(
e, MessageKind.DIRECTLY_THROWING_NSM);
}
});
complexNoReturnImpls.forEach((e) {
if (!_hasForwardingSyntax(e)) {
_compiler.reportHintMessage(
e, MessageKind.COMPLEX_THROWING_NSM);
}
});
complexReturningImpls.forEach((e) {
if (!_hasForwardingSyntax(e)) {
_compiler.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 C, as described above.
bool isComplex(FunctionElement element) {
assert(element.name == Identifiers.noSuchMethod_);
return otherImpls.contains(element);
}
_subcategorizeOther(FunctionElement element) {
TypeMask returnType =
_compiler.typesTask.getGuaranteedReturnTypeOfElement(element);
if (returnType == const TypeMask.nonNullEmpty()) {
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 (!Selectors.noSuchMethod_.signatureApplies(element)) {
otherImpls.add(element);
return NsmCategory.OTHER;
}
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(Selectors.noSuchMethod_.memberName);
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;
}
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.objectClass
|| classElement == _backend.jsInterceptorClass
|| classElement == _backend.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.
String param = element.parameters.first.name;
Statement body = element.node.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.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) {
Statement body = element.node.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, OTHER }