blob: f8354146a3314f9896044187d2d3c7c4c82a8b0f [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/element/element.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/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/codes.dart';
/// Verifies that the return type of the getter matches the parameter type
/// of the corresponding setter. Where "match" means "subtype" in non-nullable,
/// and "assignable" in legacy.
class GetterSetterTypesVerifier {
final TypeSystemImpl _typeSystem;
final ErrorReporter _errorReporter;
GetterSetterTypesVerifier({
required TypeSystemImpl typeSystem,
required ErrorReporter errorReporter,
}) : _typeSystem = typeSystem,
_errorReporter = errorReporter;
ErrorCode get _errorCode {
return _isNonNullableByDefault
? CompileTimeErrorCode.GETTER_NOT_SUBTYPE_SETTER_TYPES
: CompileTimeErrorCode.GETTER_NOT_ASSIGNABLE_SETTER_TYPES;
}
bool get _isNonNullableByDefault => _typeSystem.isNonNullableByDefault;
void checkExtension(ExtensionElement element) {
for (var getter in element.accessors) {
if (getter.isGetter) {
_checkLocalGetter(getter);
}
}
}
void checkInterface(ClassElement classElement, Interface interface) {
var libraryUri = classElement.library.source.uri;
for (var name in interface.map.keys) {
if (!name.isAccessibleFor(libraryUri)) continue;
var getter = interface.map[name]!;
if (getter.kind == ElementKind.GETTER) {
var setter = interface.map[Name(libraryUri, '${name.name}=')];
if (setter != null && setter.parameters.length == 1) {
var getterType = getter.returnType;
var setterType = setter.parameters[0].type;
if (!_match(getterType, setterType)) {
Element errorElement;
if (getter.enclosingElement == classElement) {
errorElement = getter;
} else if (setter.enclosingElement == classElement) {
errorElement = setter;
} else {
errorElement = classElement;
}
var getterName = getter.displayName;
if (getter.enclosingElement != classElement) {
var getterClassName = getter.enclosingElement.displayName;
getterName = '$getterClassName.$getterName';
}
var setterName = setter.displayName;
if (setter.enclosingElement != classElement) {
var setterClassName = setter.enclosingElement.displayName;
setterName = '$setterClassName.$setterName';
}
_errorReporter.reportErrorForElement(
_errorCode,
errorElement,
[getterName, getterType, setterType, setterName],
);
}
}
}
}
}
void checkStaticAccessors(List<PropertyAccessorElement> accessors) {
for (var getter in accessors) {
if (getter.isStatic && getter.isGetter) {
_checkLocalGetter(getter);
}
}
}
void _checkLocalGetter(PropertyAccessorElement getter) {
assert(getter.isGetter);
var setter = getter.correspondingSetter;
if (setter != null) {
var getterType = _getGetterType(getter);
var setterType = _getSetterType(setter);
if (setterType != null) {
if (!_match(getterType, setterType)) {
var name = getter.name;
_errorReporter.reportErrorForElement(
_errorCode,
getter,
[name, getterType, setterType, name],
);
}
}
}
}
bool _match(DartType getterType, DartType setterType) {
return _isNonNullableByDefault
? _typeSystem.isSubtypeOf(getterType, setterType)
: _typeSystem.isAssignableTo(getterType, setterType);
}
/// Return the return type of the [getter].
static DartType _getGetterType(PropertyAccessorElement getter) {
return getter.returnType;
}
/// Return the type of the first parameter of the [setter].
static DartType? _getSetterType(PropertyAccessorElement setter) {
var parameters = setter.parameters;
if (parameters.isNotEmpty) {
return parameters[0].type;
} else {
return null;
}
}
}