blob: db73a0c1d9a256e099d6fef1d85444eb1ad464b8 [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/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/error/assignment_verifier.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/super_context.dart';
import 'package:meta/meta.dart';
class PropertyElementResolver {
final ResolverVisitor _resolver;
PropertyElementResolver(this._resolver);
LibraryElement get _definingLibrary => _resolver.definingLibrary;
ErrorReporter get _errorReporter => _resolver.errorReporter;
ExtensionMemberResolver get _extensionResolver => _resolver.extensionResolver;
PropertyElementResolverResult resolvePrefixedIdentifier({
@required PrefixedIdentifier node,
@required bool hasRead,
@required bool hasWrite,
}) {
return _resolve(
target: node.prefix,
isCascaded: false,
isNullAware: false,
propertyName: node.identifier,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
PropertyElementResolverResult resolvePropertyAccess({
@required PropertyAccess node,
@required bool hasRead,
@required bool hasWrite,
}) {
var target = node.realTarget;
var propertyName = node.propertyName;
if (target is ExtensionOverride) {
return _resolveTargetExtensionOverride(
target: target,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
if (target is SuperExpression) {
return _resolveTargetSuperExpression(
target: target,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
return _resolve(
target: target,
isCascaded: node.target == null,
isNullAware: node.isNullAware,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
void _checkExtensionOverrideStaticMember(
SimpleIdentifier propertyName,
ExecutableElement element,
) {
if (element != null && element.isStatic) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER,
propertyName,
);
}
}
/// If the [element] is not static, report the error on the [identifier].
void _checkForStaticAccessToInstanceMember(
SimpleIdentifier identifier,
ExecutableElement element,
) {
if (element.isStatic) return;
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
identifier,
[identifier.name],
);
}
bool _isAccessible(ExecutableElement element) {
return element.isAccessibleIn(_definingLibrary);
}
PropertyElementResolverResult _resolve({
@required Expression target,
@required bool isCascaded,
@required bool isNullAware,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
//
// If this property access is of the form 'C.m' where 'C' is a class,
// then we don't call resolveProperty(...) which walks up the class
// hierarchy, instead we just look for the member in the type only. This
// does not apply to conditional property accesses (i.e. 'C?.m').
//
if (target is Identifier) {
var targetElement = target.staticElement;
if (targetElement is ClassElement) {
if (isCascaded) {
targetElement = _resolver.typeProvider.typeType.element;
}
return _resolveTargetClassElement(
typeReference: targetElement,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
}
//
// If this property access is of the form 'E.m' where 'E' is an extension,
// then look for the member in the extension. This does not apply to
// conditional property accesses (i.e. 'C?.m').
//
if (target is Identifier) {
var targetElement = target.staticElement;
if (targetElement is ExtensionElement) {
return _resolveTargetExtensionElement(
extension: targetElement,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
}
var targetType = target.staticType;
targetType = _resolveTypeParameter(targetType);
if (targetType.isVoid) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_VOID_RESULT,
propertyName,
);
return PropertyElementResolverResult();
}
if (isNullAware) {
targetType = _resolver.typeSystem.promoteToNonNull(targetType);
}
var result = _resolver.typePropertyResolver.resolve(
receiver: target,
receiverType: targetType,
name: propertyName.name,
receiverErrorNode: target,
nameErrorEntity: propertyName,
);
if (hasRead && result.needsGetterError) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_GETTER,
propertyName,
[propertyName.name, targetType],
);
}
if (hasWrite && result.needsSetterError) {
AssignmentVerifier(_definingLibrary, _errorReporter).verify(
node: propertyName,
requested: null,
recovery: result.getter,
receiverTypeObject: targetType,
);
}
return PropertyElementResolverResult(
readElementRequested: result.getter,
readElementRecovery: result.setter,
writeElementRequested: result.setter,
writeElementRecovery: result.getter,
);
}
PropertyElementResolverResult _resolveTargetClassElement({
@required ClassElement typeReference,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
ExecutableElement readElement;
if (hasRead) {
readElement = typeReference.getGetter(propertyName.name);
if (readElement != null && !_isAccessible(readElement)) {
readElement = null;
}
if (readElement == null) {
readElement = typeReference.getMethod(propertyName.name);
if (readElement != null && !_isAccessible(readElement)) {
readElement = null;
}
}
if (readElement != null) {
readElement = _resolver.toLegacyElement(readElement);
_checkForStaticAccessToInstanceMember(propertyName, readElement);
} else {
var code = typeReference.isEnum
? CompileTimeErrorCode.UNDEFINED_ENUM_CONSTANT
: CompileTimeErrorCode.UNDEFINED_GETTER;
_errorReporter.reportErrorForNode(
code,
propertyName,
[propertyName.name, typeReference.name],
);
}
}
ExecutableElement writeElement;
ExecutableElement writeElementRecovery;
if (hasWrite) {
writeElement = typeReference.getSetter(propertyName.name);
if (writeElement != null) {
writeElement = _resolver.toLegacyElement(writeElement);
if (!_isAccessible(writeElement)) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.PRIVATE_SETTER,
propertyName,
[propertyName.name, typeReference.name],
);
}
_checkForStaticAccessToInstanceMember(propertyName, writeElement);
} else {
// Recovery, try to use getter.
writeElementRecovery = typeReference.getGetter(propertyName.name);
AssignmentVerifier(_definingLibrary, _errorReporter).verify(
node: propertyName,
requested: null,
recovery: writeElementRecovery,
receiverTypeObject: typeReference.displayName,
);
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
writeElementRecovery: writeElementRecovery,
);
}
PropertyElementResolverResult _resolveTargetExtensionElement({
@required ExtensionElement extension,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
var memberName = propertyName.name;
ExecutableElement readElement;
if (hasRead) {
readElement ??= extension.getGetter(memberName);
readElement ??= extension.getMethod(memberName);
if (readElement == null) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER,
propertyName,
[memberName, extension.name],
);
} else {
readElement = _resolver.toLegacyElement(readElement);
_checkForStaticAccessToInstanceMember(propertyName, readElement);
}
}
ExecutableElement writeElement;
if (hasWrite) {
writeElement = extension.getSetter(memberName);
if (writeElement == null) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER,
propertyName,
[memberName, extension.name],
);
} else {
writeElement = _resolver.toLegacyElement(writeElement);
_checkForStaticAccessToInstanceMember(propertyName, writeElement);
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
);
}
PropertyElementResolverResult _resolveTargetExtensionOverride({
@required ExtensionOverride target,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
if (target.parent is CascadeExpression) {
// Report this error and recover by treating it like a non-cascade.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.EXTENSION_OVERRIDE_WITH_CASCADE,
target.extensionName,
);
}
ExtensionElement element = target.extensionName.staticElement;
String memberName = propertyName.name;
var result = _extensionResolver.getOverrideMember(target, memberName);
ExecutableElement readElement;
if (hasRead) {
readElement = result.getter;
if (readElement == null) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER,
propertyName,
[memberName, element.name],
);
}
_checkExtensionOverrideStaticMember(propertyName, readElement);
}
ExecutableElement writeElement;
if (hasWrite) {
writeElement = result.setter;
if (writeElement == null) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER,
propertyName,
[memberName, element.name],
);
}
_checkExtensionOverrideStaticMember(propertyName, writeElement);
}
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
);
}
PropertyElementResolverResult _resolveTargetSuperExpression({
@required SuperExpression target,
@required SimpleIdentifier propertyName,
@required bool hasRead,
@required bool hasWrite,
}) {
if (SuperContext.of(target) != SuperContext.valid) {
return PropertyElementResolverResult();
}
var targetType = target.staticType;
ExecutableElement readElement;
ExecutableElement writeElement;
if (targetType is InterfaceTypeImpl) {
if (hasRead) {
var name = Name(_definingLibrary.source.uri, propertyName.name);
readElement = _resolver.inheritance
.getMember2(targetType.element, name, forSuper: true);
if (readElement != null) {
readElement = _resolver.toLegacyElement(readElement);
} else {
// We were not able to find the concrete dispatch target.
// But we would like to give the user at least some resolution.
// So, we retry simply looking for an inherited member.
readElement =
_resolver.inheritance.getInherited2(targetType.element, name);
if (readElement != null) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.ABSTRACT_SUPER_MEMBER_REFERENCE,
propertyName,
[readElement.kind.displayName, propertyName.name],
);
} else {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_SUPER_GETTER,
propertyName,
[propertyName.name, targetType],
);
}
}
}
if (hasWrite) {
writeElement = targetType.lookUpSetter2(
propertyName.name,
_definingLibrary,
concrete: true,
inherited: true,
);
if (writeElement != null) {
writeElement = _resolver.toLegacyElement(writeElement);
} else {
// We were not able to find the concrete dispatch target.
// But we would like to give the user at least some resolution.
// So, we retry without the "concrete" requirement.
writeElement = targetType.lookUpSetter2(
propertyName.name,
_definingLibrary,
inherited: true,
);
if (writeElement != null) {
ClassElementImpl receiverSuperClass =
targetType.element.supertype.element;
if (!receiverSuperClass.hasNoSuchMethod) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.ABSTRACT_SUPER_MEMBER_REFERENCE,
propertyName,
[writeElement.kind.displayName, propertyName.name],
);
}
} else {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_SUPER_SETTER,
propertyName,
[propertyName.name, targetType],
);
}
}
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
);
}
/// If the given [type] is a type parameter, replace with its bound.
/// Otherwise, return the original type.
DartType _resolveTypeParameter(DartType type) {
if (type is TypeParameterType) {
return type.resolveToBound(_resolver.typeProvider.objectType);
}
return type;
}
}
class PropertyElementResolverResult {
final ExecutableElement readElementRequested;
final ExecutableElement readElementRecovery;
final ExecutableElement writeElementRequested;
final ExecutableElement writeElementRecovery;
PropertyElementResolverResult({
this.readElementRequested,
this.readElementRecovery,
this.writeElementRequested,
this.writeElementRecovery,
});
ExecutableElement get readElement {
return readElementRequested ?? readElementRecovery;
}
ExecutableElement get writeElement {
return writeElementRequested ?? writeElementRecovery;
}
}