blob: 6ddcf05c858cd89fc85610e63e083283f553f8dd [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/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/lexical_lookup.dart';
import 'package:analyzer/src/dart/resolver/resolution_result.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/scope_helpers.dart';
import 'package:analyzer/src/generated/super_context.dart';
class PropertyElementResolver with ScopeHelpers {
final ResolverVisitor _resolver;
PropertyElementResolver(this._resolver);
@override
ErrorReporter get errorReporter => _resolver.errorReporter;
LibraryElement get _definingLibrary => _resolver.definingLibrary;
ExtensionMemberResolver get _extensionResolver => _resolver.extensionResolver;
TypeSystemImpl get _typeSystem => _resolver.typeSystem;
PropertyElementResolverResult resolveIndexExpression({
required IndexExpression node,
required bool hasRead,
required bool hasWrite,
}) {
var target = node.realTarget;
if (target is ExtensionOverride) {
var result = _extensionResolver.getOverrideMember(target, '[]');
// TODO(scheglov) Change ExtensionResolver to set `needsGetterError`.
if (hasRead && result.getter == null && !result.isAmbiguous) {
// Extension overrides can only refer to named extensions, so it is safe
// to assume that `target.staticElement!.name` is non-`null`.
_reportUnresolvedIndex(
node,
CompileTimeErrorCode.UNDEFINED_EXTENSION_OPERATOR,
['[]', target.staticElement!.name!],
);
}
if (hasWrite && result.setter == null && !result.isAmbiguous) {
// Extension overrides can only refer to named extensions, so it is safe
// to assume that `target.staticElement!.name` is non-`null`.
_reportUnresolvedIndex(
node,
CompileTimeErrorCode.UNDEFINED_EXTENSION_OPERATOR,
['[]=', target.staticElement!.name!],
);
}
return _toIndexResult(result);
}
var targetType = target.typeOrThrow;
targetType = _typeSystem.resolveToBound(targetType);
if (targetType.isVoid) {
// TODO(scheglov) Report directly in TypePropertyResolver?
_reportUnresolvedIndex(
node,
CompileTimeErrorCode.USE_OF_VOID_RESULT,
);
return PropertyElementResolverResult();
}
if (identical(targetType, NeverTypeImpl.instance)) {
// TODO(scheglov) Report directly in TypePropertyResolver?
errorReporter.reportErrorForNode(
HintCode.RECEIVER_OF_TYPE_NEVER,
target,
);
return PropertyElementResolverResult();
}
if (node.isNullAware) {
if (target is ExtensionOverride) {
// https://github.com/dart-lang/language/pull/953
} else {
targetType = _typeSystem.promoteToNonNull(targetType);
}
}
var result = _resolver.typePropertyResolver.resolve(
receiver: target,
receiverType: targetType,
name: '[]',
propertyErrorEntity: node.leftBracket,
nameErrorEntity: target,
);
if (hasRead && result.needsGetterError) {
_reportUnresolvedIndex(
node,
target is SuperExpression
? CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR
: CompileTimeErrorCode.UNDEFINED_OPERATOR,
['[]', targetType],
);
}
if (hasWrite && result.needsSetterError) {
_reportUnresolvedIndex(
node,
target is SuperExpression
? CompileTimeErrorCode.UNDEFINED_SUPER_OPERATOR
: CompileTimeErrorCode.UNDEFINED_OPERATOR,
['[]=', targetType],
);
}
return _toIndexResult(result);
}
PropertyElementResolverResult resolvePrefixedIdentifier({
required PrefixedIdentifier node,
required bool hasRead,
required bool hasWrite,
bool forAnnotation = false,
}) {
var prefix = node.prefix;
var identifier = node.identifier;
var prefixElement = prefix.staticElement;
if (prefixElement is PrefixElement) {
return _resolveTargetPrefixElement(
target: prefixElement,
identifier: identifier,
hasRead: hasRead,
hasWrite: hasWrite,
forAnnotation: forAnnotation,
);
}
return _resolve(
node: node,
target: prefix,
isCascaded: false,
isNullAware: false,
propertyName: 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(
node: node,
target: target,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
return _resolve(
node: node,
target: target,
isCascaded: node.target == null,
isNullAware: node.isNullAware,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
PropertyElementResolverResult resolveSimpleIdentifier({
required SimpleIdentifierImpl node,
required bool hasRead,
required bool hasWrite,
}) {
var ancestorCascade = node.ancestorCascade;
if (ancestorCascade != null) {
return _resolve(
node: node,
target: ancestorCascade.target,
isCascaded: true,
isNullAware: ancestorCascade.isNullAware,
propertyName: node,
hasRead: hasRead,
hasWrite: hasWrite,
);
}
final scopeLookupResult = node.scopeLookupResult!;
reportDeprecatedExportUse(
scopeLookupResult: scopeLookupResult,
node: node,
hasRead: hasRead,
hasWrite: hasWrite,
);
Element? readElementRequested;
Element? readElementRecovery;
if (hasRead) {
var readLookup = LexicalLookup.resolveGetter(scopeLookupResult) ??
_resolver.thisLookupGetter(node);
readElementRequested = _resolver.toLegacyElement(readLookup?.requested);
if (readElementRequested is PropertyAccessorElement &&
!readElementRequested.isStatic) {
_resolver.flowAnalysis.flow?.thisOrSuperPropertyGet(node, node.name,
readElementRequested, readElementRequested.returnType);
}
_resolver.checkReadOfNotAssignedLocalVariable(node, readElementRequested);
}
Element? writeElementRequested;
Element? writeElementRecovery;
if (hasWrite) {
var writeLookup = LexicalLookup.resolveSetter(scopeLookupResult) ??
_resolver.thisLookupSetter(node);
writeElementRequested = _resolver.toLegacyElement(writeLookup?.requested);
writeElementRecovery = _resolver.toLegacyElement(writeLookup?.recovery);
AssignmentVerifier(_resolver.definingLibrary, errorReporter).verify(
node: node,
requested: writeElementRequested,
recovery: writeElementRecovery,
receiverType: null,
);
}
return PropertyElementResolverResult(
readElementRequested: readElementRequested,
readElementRecovery: readElementRecovery,
writeElementRequested: writeElementRequested,
writeElementRecovery: writeElementRecovery,
);
}
/// If the [element] is not static, report the error on the [identifier].
///
/// Returns `true` if an error was reported.
bool _checkForStaticAccessToInstanceMember(
SimpleIdentifier identifier,
ExecutableElement element,
) {
if (element.isStatic) return false;
errorReporter.reportErrorForNode(
CompileTimeErrorCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
identifier,
[identifier.name],
);
return true;
}
void _checkForStaticMember(
Expression target,
SimpleIdentifier propertyName,
ExecutableElement? element,
) {
if (element != null && element.isStatic) {
if (target is ExtensionOverride) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER,
propertyName,
);
} else {
var enclosingElement = element.enclosingElement;
if (enclosingElement is ExtensionElement &&
enclosingElement.name == null) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode
.INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION,
propertyName,
[
propertyName.name,
element.kind.displayName,
]);
} else {
// It is safe to assume that `enclosingElement.name` is non-`null`
// because it can only be `null` for extensions, and we handle that
// case above.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER,
propertyName, [
propertyName.name,
element.kind.displayName,
enclosingElement.name!,
enclosingElement is ClassElement && enclosingElement.isMixin
? 'mixin'
: enclosingElement.kind.displayName,
]);
}
}
}
}
DartType? _computeIndexContextType({
required ExecutableElement? readElement,
required ExecutableElement? writeElement,
}) {
var method = writeElement ?? readElement;
var parameters = method is MethodElement ? method.parameters : null;
if (parameters != null && parameters.isNotEmpty) {
return parameters[0].type;
}
return null;
}
bool _isAccessible(ExecutableElement element) {
return element.isAccessibleIn2(_definingLibrary);
}
void _reportUnresolvedIndex(
IndexExpression node,
ErrorCode errorCode, [
List<Object> arguments = const [],
]) {
var leftBracket = node.leftBracket;
var rightBracket = node.rightBracket;
var offset = leftBracket.offset;
var length = rightBracket.end - offset;
errorReporter.reportErrorForOffset(errorCode, offset, length, arguments);
}
PropertyElementResolverResult _resolve({
required Expression node,
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) {
return _resolveTargetClassElement(
typeReference: targetElement,
isCascaded: isCascaded,
propertyName: propertyName,
hasRead: hasRead,
hasWrite: hasWrite,
);
} else if (targetElement is TypeAliasElement) {
var aliasedType = targetElement.aliasedType;
if (aliasedType is InterfaceType) {
return _resolveTargetClassElement(
typeReference: aliasedType.element,
isCascaded: isCascaded,
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.typeOrThrow;
if (targetType is FunctionType &&
propertyName.name == FunctionElement.CALL_METHOD_NAME) {
return PropertyElementResolverResult(
functionTypeCallType: targetType,
);
}
if (targetType.isVoid) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_VOID_RESULT,
propertyName,
);
return PropertyElementResolverResult();
}
if (isNullAware) {
targetType = _typeSystem.promoteToNonNull(targetType);
}
if (target is TypeLiteral && target.type.type is FunctionType) {
// There is no possible resolution for a property access of a function
// type literal (which can only be a type instantiation of a type alias
// of a function type).
if (hasRead) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_GETTER_ON_FUNCTION_TYPE,
propertyName,
[propertyName.name, target.type.name.name],
);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_SETTER_ON_FUNCTION_TYPE,
propertyName,
[propertyName.name, target.type.name.name],
);
}
return PropertyElementResolverResult();
}
var result = _resolver.typePropertyResolver.resolve(
receiver: target,
receiverType: targetType,
name: propertyName.name,
propertyErrorEntity: propertyName,
nameErrorEntity: propertyName,
);
_resolver.flowAnalysis.flow?.propertyGet(
node,
target,
propertyName.name,
result.getter,
result.getter?.returnType ?? _typeSystem.typeProvider.dynamicType);
if (hasRead) {
_checkForStaticMember(target, propertyName, result.getter);
if (result.needsGetterError) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_GETTER,
propertyName,
[propertyName.name, targetType],
);
}
}
if (hasWrite) {
_checkForStaticMember(target, propertyName, result.setter);
if (result.needsSetterError) {
AssignmentVerifier(_definingLibrary, errorReporter).verify(
node: propertyName,
requested: null,
recovery: result.getter,
receiverType: targetType,
);
}
}
return PropertyElementResolverResult(
readElementRequested: result.getter,
readElementRecovery: result.setter,
writeElementRequested: result.setter,
writeElementRecovery: result.getter,
);
}
PropertyElementResolverResult _resolveTargetClassElement({
required ClassElement typeReference,
required bool isCascaded,
required SimpleIdentifier propertyName,
required bool hasRead,
required bool hasWrite,
}) {
if (isCascaded) {
typeReference = _resolver.typeProvider.typeType.element;
}
ExecutableElement? readElement;
ExecutableElement? readElementRecovery;
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);
if (_checkForStaticAccessToInstanceMember(propertyName, readElement)) {
readElementRecovery = readElement;
readElement = null;
}
} 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],
);
}
if (_checkForStaticAccessToInstanceMember(propertyName, writeElement)) {
writeElementRecovery = writeElement;
writeElement = null;
}
} else {
// Recovery, try to use getter.
writeElementRecovery = typeReference.getGetter(propertyName.name);
AssignmentVerifier(_definingLibrary, errorReporter).verify(
node: propertyName,
requested: null,
recovery: writeElementRecovery,
receiverType: typeReference.thisType,
);
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
readElementRecovery: readElementRecovery,
writeElementRequested: writeElement,
writeElementRecovery: writeElementRecovery,
);
}
PropertyElementResolverResult _resolveTargetExtensionElement({
required ExtensionElement extension,
required SimpleIdentifier propertyName,
required bool hasRead,
required bool hasWrite,
}) {
var memberName = propertyName.name;
ExecutableElement? readElement;
ExecutableElement? readElementRecovery;
if (hasRead) {
readElement ??= extension.getGetter(memberName);
readElement ??= extension.getMethod(memberName);
if (readElement == null) {
// This method is only called for extension overrides, and extension
// overrides can only refer to named extensions. So it is safe to
// assume that `extension.name` is non-`null`.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER,
propertyName,
[memberName, extension.name!],
);
} else {
readElement = _resolver.toLegacyElement(readElement);
if (_checkForStaticAccessToInstanceMember(propertyName, readElement)) {
readElementRecovery = readElement;
readElement = null;
}
}
}
ExecutableElement? writeElement;
ExecutableElement? writeElementRecovery;
if (hasWrite) {
writeElement = extension.getSetter(memberName);
if (writeElement == null) {
errorReporter.reportErrorForNode(
// This method is only called for extension overrides, and extension
// overrides can only refer to named extensions. So it is safe to
// assume that `extension.name` is non-`null`.
CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER,
propertyName,
[memberName, extension.name!],
);
} else {
writeElement = _resolver.toLegacyElement(writeElement);
if (_checkForStaticAccessToInstanceMember(propertyName, writeElement)) {
writeElementRecovery = writeElement;
writeElement = null;
}
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
readElementRecovery: readElementRecovery,
writeElementRequested: writeElement,
writeElementRecovery: writeElementRecovery,
);
}
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,
);
}
var element = target.extensionName.staticElement!;
var memberName = propertyName.name;
var result = _extensionResolver.getOverrideMember(target, memberName);
ExecutableElement? readElement;
if (hasRead) {
readElement = result.getter;
if (readElement == null) {
// This method is only called for extension overrides, and extension
// overrides can only refer to named extensions. So it is safe to
// assume that `element.name` is non-`null`.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER,
propertyName,
[memberName, element.name!],
);
}
_checkForStaticMember(target, propertyName, readElement);
}
ExecutableElement? writeElement;
if (hasWrite) {
writeElement = result.setter;
if (writeElement == null) {
// This method is only called for extension overrides, and extension
// overrides can only refer to named extensions. So it is safe to
// assume that `element.name` is non-`null`.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_EXTENSION_SETTER,
propertyName,
[memberName, element.name!],
);
}
_checkForStaticMember(target, propertyName, writeElement);
}
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
);
}
PropertyElementResolverResult _resolveTargetPrefixElement({
required PrefixElement target,
required SimpleIdentifier identifier,
required bool hasRead,
required bool hasWrite,
required bool forAnnotation,
}) {
var lookupResult = target.scope.lookup(identifier.name);
reportDeprecatedExportUse(
scopeLookupResult: lookupResult,
node: identifier,
hasRead: hasRead,
hasWrite: hasWrite,
);
var readElement = _resolver.toLegacyElement(lookupResult.getter);
var writeElement = _resolver.toLegacyElement(lookupResult.setter);
if (hasRead && readElement == null || hasWrite && writeElement == null) {
if (!forAnnotation &&
!_resolver.definingLibrary.shouldIgnoreUndefined(
prefix: target.name,
name: identifier.name,
)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME,
identifier,
[identifier.name, target.name],
);
}
}
return PropertyElementResolverResult(
readElementRequested: readElement,
readElementRecovery: null,
writeElementRequested: writeElement,
writeElementRecovery: null,
);
}
PropertyElementResolverResult _resolveTargetSuperExpression({
required Expression node,
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);
_checkForStaticMember(target, propertyName, 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],
);
}
}
_resolver.flowAnalysis.flow?.propertyGet(
node,
target,
propertyName.name,
readElement,
readElement?.returnType ?? _typeSystem.typeProvider.dynamicType);
}
if (hasWrite) {
writeElement = targetType.lookUpSetter2(
propertyName.name,
_definingLibrary,
concrete: true,
inherited: true,
);
if (writeElement != null) {
writeElement = _resolver.toLegacyElement(writeElement);
_checkForStaticMember(target, propertyName, 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) {
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,
);
}
PropertyElementResolverResult _toIndexResult(ResolutionResult result) {
var readElement = result.getter;
var writeElement = result.setter;
return PropertyElementResolverResult(
readElementRequested: readElement,
writeElementRequested: writeElement,
indexContextType: _computeIndexContextType(
readElement: readElement,
writeElement: writeElement,
),
);
}
}
class PropertyElementResolverResult {
final Element? readElementRequested;
final Element? readElementRecovery;
final Element? writeElementRequested;
final Element? writeElementRecovery;
final FunctionType? functionTypeCallType;
/// If [IndexExpression] is resolved, the context type of the index.
/// Might be `null` if `[]` or `[]=` are not resolved or invalid.
final DartType? indexContextType;
PropertyElementResolverResult({
this.readElementRequested,
this.readElementRecovery,
this.writeElementRequested,
this.writeElementRecovery,
this.indexContextType,
this.functionTypeCallType,
});
Element? get readElement {
return readElementRequested ?? readElementRecovery;
}
Element? get writeElement {
return writeElementRequested ?? writeElementRecovery;
}
}