blob: 2169256ef70f01077520754b6a3bd4943aa248e0 [file] [log] [blame]
// Copyright (c) 2020, 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 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.dart';
class CorrectOverrideHelper {
final LibraryElementImpl _library;
final TypeSystemImpl _typeSystem;
final ExecutableElement _thisMember;
FunctionType? _thisTypeForSubtype;
final DiagnosticFactory _diagnosticFactory = DiagnosticFactory();
CorrectOverrideHelper({
required LibraryElementImpl library,
required ExecutableElement thisMember,
}) : _library = library,
_typeSystem = library.typeSystem,
_thisMember = thisMember {
_computeThisTypeForSubtype();
}
/// Return `true` if [_thisMember] is a correct override of [superMember].
bool isCorrectOverrideOf({
required ExecutableElement superMember,
}) {
superMember = _library.toLegacyElementIfOptOut(superMember);
var superType = superMember.type;
return _typeSystem.isSubtypeOf(_thisTypeForSubtype!, superType);
}
/// If [_thisMember] is not a correct override of [superMember], report the
/// error.
void verify({
required ExecutableElement superMember,
required ErrorReporter errorReporter,
required AstNode errorNode,
ErrorCode? errorCode,
}) {
var isCorrect = isCorrectOverrideOf(superMember: superMember);
if (!isCorrect) {
errorReporter.reportError(_diagnosticFactory.invalidOverride(
errorReporter.source,
errorCode,
errorNode,
_thisMember,
superMember));
}
}
/// Fill [_thisTypeForSubtype]. If [_thisMember] has covariant formal
/// parameters, replace their types with `Object?` or `Object`.
void _computeThisTypeForSubtype() {
var type = _thisMember.type;
var parameters = type.parameters;
List<ParameterElement>? newParameters;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters[i];
if (parameter.isCovariant) {
newParameters ??= parameters.toList(growable: false);
newParameters[i] = parameter.copyWith(
type: _typeSystem.isNonNullableByDefault
? _typeSystem.objectQuestion
: _typeSystem.objectStar,
);
}
}
if (newParameters != null) {
_thisTypeForSubtype = FunctionTypeImpl(
typeFormals: type.typeFormals,
parameters: newParameters,
returnType: type.returnType,
nullabilitySuffix: type.nullabilitySuffix,
);
} else {
_thisTypeForSubtype = type;
}
}
}
class CovariantParametersVerifier {
final AnalysisSessionImpl _session;
final TypeSystemImpl _typeSystem;
final ExecutableElement _thisMember;
CovariantParametersVerifier({
required ExecutableElement thisMember,
}) : _session = thisMember.library.session as AnalysisSessionImpl,
_typeSystem = thisMember.library.typeSystem as TypeSystemImpl,
_thisMember = thisMember;
void verify({
required ErrorReporter errorReporter,
required AstNode errorNode,
}) {
var superParameters = _superParameters();
for (var entry in superParameters.entries) {
var parameter = entry.key;
for (var superParameter in entry.value) {
var thisType = parameter.type;
var superType = superParameter.type;
if (!_typeSystem.isSubtypeOf(superType, thisType) &&
!_typeSystem.isSubtypeOf(thisType, superType)) {
var superMember = superParameter.member;
// Elements enclosing members that can participate in overrides are
// always named, so we can safely assume
// `_thisMember.enclosingElement.name` and
// `superMember.enclosingElement.name` are non-`null`.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_OVERRIDE,
errorNode,
[
_thisMember.name,
_thisMember.enclosingElement.name!,
_thisMember.type,
superMember.enclosingElement.name!,
superMember.type,
],
);
}
}
}
}
List<_SuperMember> _superMembers() {
var classHierarchy = _session.classHierarchy;
var classElement = _thisMember.enclosingElement as ClassElement;
var interfaces = classHierarchy.implementedInterfaces(classElement);
var superMembers = <_SuperMember>[];
for (var interface in interfaces) {
var superMember = _correspondingMember(interface.element, _thisMember);
if (superMember != null) {
superMembers.add(
_SuperMember(interface, superMember),
);
}
}
return superMembers;
}
Map<ParameterElement, List<_SuperParameter>> _superParameters() {
var result = <ParameterElement, List<_SuperParameter>>{};
List<_SuperMember>? superMembers;
var parameters = _thisMember.parameters;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters[i];
if (parameter.isCovariant) {
superMembers ??= _superMembers();
for (var superMember in superMembers) {
var superParameter = _correspondingParameter(
superMember.rawElement.parameters,
parameter,
i,
);
if (superParameter != null) {
var parameterSuperList = result[parameter] ??= [];
var superType = _superSubstitution(superMember)
.substituteType(superParameter.type);
parameterSuperList.add(
_SuperParameter(superParameter, superType),
);
}
}
}
}
return result;
}
/// Return the [Substitution] to convert types of [superMember] to types of
/// [_thisMember].
Substitution _superSubstitution(_SuperMember superMember) {
Substitution result = Substitution.fromInterfaceType(superMember.interface);
// If the executable has type parameters, ensure that super uses the same.
var thisTypeParameters = _thisMember.typeParameters;
if (thisTypeParameters.isNotEmpty) {
var superTypeParameters = superMember.rawElement.typeParameters;
if (thisTypeParameters.length == superTypeParameters.length) {
var typeParametersSubstitution = Substitution.fromPairs(
superTypeParameters,
thisTypeParameters.map((e) {
return e.instantiate(
nullabilitySuffix: NullabilitySuffix.none,
);
}).toList(),
);
result = Substitution.combine(result, typeParametersSubstitution);
}
}
return result;
}
/// Return a member from [classElement] that corresponds to the [proto],
/// or `null` if no such member exist.
static ExecutableElement? _correspondingMember(
ClassElement classElement,
ExecutableElement proto,
) {
if (proto is MethodElement) {
return classElement.getMethod(proto.displayName);
}
if (proto is PropertyAccessorElement) {
if (proto.isGetter) {
return classElement.getGetter(proto.displayName);
}
return classElement.getSetter(proto.displayName);
}
return null;
}
/// Return an element of [parameters] that corresponds for the [proto],
/// or `null` if no such parameter exist.
static ParameterElement? _correspondingParameter(
List<ParameterElement> parameters,
ParameterElement proto,
int protoIndex,
) {
if (proto.isPositional) {
if (parameters.length > protoIndex) {
var parameter = parameters[protoIndex];
if (parameter.isPositional) {
return parameter;
}
}
} else {
assert(proto.isNamed);
for (var parameter in parameters) {
if (parameter.isNamed && parameter.name == proto.name) {
return parameter;
}
}
}
return null;
}
}
class _SuperMember {
final InterfaceType interface;
final ExecutableElement rawElement;
_SuperMember(this.interface, this.rawElement);
}
class _SuperParameter {
final ParameterElement element;
final DartType type;
_SuperParameter(this.element, this.type);
ExecutableElement get member => element.enclosingElement as ExecutableElement;
}