blob: 8069eda65e58c7bf974e8d3fa35b5316632fb7db [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.
// @dart = 2.10
import '../common.dart';
import '../common/elements.dart' show CommonElements;
import '../common/names.dart' show Identifiers, Selectors;
import '../elements/entities.dart';
import '../inferrer/types.dart' show GlobalTypeInferenceResults;
import '../kernel/no_such_method_resolver.dart';
import '../serialization/serialization_interfaces.dart';
/// [NoSuchMethodRegistry] and [NoSuchMethodData] 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.
/// Registry for collecting `noSuchMethod` implementations and categorizing them
/// into categories `A`, `B`, `C`, `D`.
class NoSuchMethodRegistry {
/// The implementations that fall into category A, described above.
final Set<FunctionEntity> _defaultImpls = {};
/// The implementations that fall into category B, described above.
final Set<FunctionEntity> _throwingImpls = {};
/// The implementations that fall into category C, described above.
// TODO(johnniwinther): Remove this category when Dart 1 is no longer
// supported.
final Set<FunctionEntity> _notApplicableImpls = {};
/// The implementations that fall into category D, described above.
final Set<FunctionEntity> _otherImpls = {};
/// The implementations that have not yet been categorized.
final Set<FunctionEntity> _uncategorizedImpls = {};
/// The implementations that a forwarding syntax as defined by
/// [NoSuchMethodResolver.hasForwardSyntax].
final Set<FunctionEntity> _forwardingSyntaxImpls = {};
final CommonElements _commonElements;
final NoSuchMethodResolver _resolver;
NoSuchMethodRegistry(this._commonElements, this._resolver);
NoSuchMethodResolver get internalResolverForTesting => _resolver;
/// `true` if a category `B` method has been seen so far.
bool get hasThrowingNoSuchMethod => _throwingImpls.isNotEmpty;
/// `true` if a category `D` method has been seen so far.
bool get hasComplexNoSuchMethod => _otherImpls.isNotEmpty;
Iterable<FunctionEntity> get defaultImpls => _defaultImpls;
Iterable<FunctionEntity> get throwingImpls => _throwingImpls;
Iterable<FunctionEntity> get otherImpls => _otherImpls;
/// Register [noSuchMethodElement].
void registerNoSuchMethod(FunctionEntity noSuchMethodElement) {
_uncategorizedImpls.add(noSuchMethodElement);
}
/// Categorizes the registered methods.
void onQueueEmpty() {
_uncategorizedImpls.forEach(_categorizeImpl);
_uncategorizedImpls.clear();
}
NsmCategory _categorizeImpl(FunctionEntity element) {
assert(element.name == Identifiers.noSuchMethod_);
assert(!element.isAbstract);
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 (_commonElements.isDefaultNoSuchMethodImplementation(element)) {
_defaultImpls.add(element);
return NsmCategory.DEFAULT;
} else if (_resolver.hasForwardingSyntax(element)) {
_forwardingSyntaxImpls.add(element);
// If the implementation is 'noSuchMethod(x) => super.noSuchMethod(x);'
// then it is in the same category as the super call.
FunctionEntity superCall = _resolver.getSuperNoSuchMethod(element);
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 (_resolver.hasThrowingSyntax(element)) {
_throwingImpls.add(element);
return NsmCategory.THROWING;
} else {
_otherImpls.add(element);
return NsmCategory.OTHER;
}
}
/// Closes the registry and returns data object used during type inference.
NoSuchMethodData close() {
return NoSuchMethodData(
_throwingImpls, _otherImpls, _forwardingSyntaxImpls);
}
}
/// Data object used during type inference.
///
/// Post inference collected category `D` methods are into subcategories `D1`
/// and `D2`.
class NoSuchMethodData {
/// Tag used for identifying serialized [NoSuchMethodData] objects in a
/// debugging data stream.
static const String tag = 'no-such-method-data';
/// The implementations that fall into category B, described above.
final Set<FunctionEntity> _throwingImpls;
/// The implementations that fall into category D, described above.
final Set<FunctionEntity> _otherImpls;
/// The implementations that fall into category D1
final Set<FunctionEntity> _complexNoReturnImpls = {};
/// The implementations that fall into category D2
final Set<FunctionEntity> _complexReturningImpls = {};
final Set<FunctionEntity> _forwardingSyntaxImpls;
NoSuchMethodData(
this._throwingImpls, this._otherImpls, this._forwardingSyntaxImpls);
/// Deserializes a [NoSuchMethodData] object from [source].
factory NoSuchMethodData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Set<FunctionEntity> throwingImpls =
source.readMembers<FunctionEntity>().toSet();
Set<FunctionEntity> otherImpls =
source.readMembers<FunctionEntity>().toSet();
Set<FunctionEntity> forwardingSyntaxImpls =
source.readMembers<FunctionEntity>().toSet();
List<FunctionEntity> complexNoReturnImpls =
source.readMembers<FunctionEntity>();
List<FunctionEntity> complexReturningImpls =
source.readMembers<FunctionEntity>();
source.end(tag);
return NoSuchMethodData(throwingImpls, otherImpls, forwardingSyntaxImpls)
.._complexNoReturnImpls.addAll(complexNoReturnImpls)
.._complexReturningImpls.addAll(complexReturningImpls);
}
/// Serializes this [NoSuchMethodData] to [sink].
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeMembers(_throwingImpls);
sink.writeMembers(_otherImpls);
sink.writeMembers(_forwardingSyntaxImpls);
sink.writeMembers(_complexNoReturnImpls);
sink.writeMembers(_complexReturningImpls);
sink.end(tag);
}
Iterable<FunctionEntity> get throwingImpls => _throwingImpls;
Iterable<FunctionEntity> get otherImpls => _otherImpls;
Iterable<FunctionEntity> get forwardingSyntaxImpls => _forwardingSyntaxImpls;
Iterable<FunctionEntity> get complexNoReturnImpls => _complexNoReturnImpls;
Iterable<FunctionEntity> get complexReturningImpls => _complexReturningImpls;
/// 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 categorizeComplexImplementations(GlobalTypeInferenceResults results) {
_otherImpls.forEach((FunctionEntity element) {
if (results.resultOfMember(element).throwsAlways) {
_complexNoReturnImpls.add(element);
} else {
_complexReturningImpls.add(element);
}
});
}
/// Emits a diagnostic about methods in categories `B`, `D1` and `D2`.
void emitDiagnostic(DiagnosticReporter reporter) {
_throwingImpls.forEach((e) {
if (!_forwardingSyntaxImpls.contains(e)) {
reporter.reportHintMessage(e, MessageKind.DIRECTLY_THROWING_NSM);
}
});
_complexNoReturnImpls.forEach((e) {
if (!_forwardingSyntaxImpls.contains(e)) {
reporter.reportHintMessage(e, MessageKind.COMPLEX_THROWING_NSM);
}
});
_complexReturningImpls.forEach((e) {
if (!_forwardingSyntaxImpls.contains(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(FunctionEntity element) {
assert(element.name == Identifiers.noSuchMethod_);
return _otherImpls.contains(element);
}
}
enum NsmCategory {
DEFAULT,
THROWING,
NOT_APPLICABLE,
OTHER,
}