blob: 63e41ad80ba060e110fbea56408887d50ea506e5 [file] [log] [blame]
// Copyright (c) 2019, 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/analysis/features.dart';
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/scope.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.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/element.dart';
import 'package:analyzer/src/dart/element/type.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';
import 'package:analyzer/src/generated/scope_helpers.dart';
/// Helper for resolving types.
///
/// The client must set [nameScope] before calling [resolve].
class NamedTypeResolver with ScopeHelpers {
final LibraryElementImpl _libraryElement;
final TypeSystemImpl typeSystem;
final DartType dynamicType;
final bool isNonNullableByDefault;
@override
final ErrorReporter errorReporter;
late Scope nameScope;
/// If not `null`, the element of the [ClassDeclaration], or the
/// [ClassTypeAlias] being resolved.
ClassElement? enclosingClass;
/// If not `null`, a direct child of an [ExtendsClause], [WithClause],
/// or [ImplementsClause].
NamedType? classHierarchy_namedType;
/// If not `null`, a direct child the [WithClause] in the [enclosingClass].
NamedType? withClause_namedType;
/// If not `null`, the [NamedType] of the redirected constructor being
/// resolved, in the [enclosingClass].
NamedType? redirectedConstructor_namedType;
/// If [resolve] finds out that the given [NamedType] with a
/// [PrefixedIdentifier] name is actually the name of a class and the name of
/// the constructor, it rewrites the [ConstructorName] to correctly represent
/// the type and the constructor name, and set this field to the rewritten
/// [ConstructorName]. Otherwise this field will be set `null`.
ConstructorName? rewriteResult;
/// If [resolve] reported an error, this flag is set to `true`.
bool hasErrorReported = false;
NamedTypeResolver(this._libraryElement, TypeProvider typeProvider,
this.isNonNullableByDefault, this.errorReporter)
: typeSystem = _libraryElement.typeSystem,
dynamicType = typeProvider.dynamicType;
bool get _genericMetadataIsEnabled =>
enclosingClass!.library.featureSet.isEnabled(Feature.generic_metadata);
NullabilitySuffix get _noneOrStarSuffix {
return isNonNullableByDefault
? NullabilitySuffix.none
: NullabilitySuffix.star;
}
/// Resolve the given [NamedType] - set its element and static type. Only the
/// given [node] is resolved, all its children must be already resolved.
///
/// The client must set [nameScope] before calling [resolve].
void resolve(NamedTypeImpl node) {
rewriteResult = null;
hasErrorReported = false;
var typeIdentifier = node.name;
if (typeIdentifier is PrefixedIdentifierImpl) {
var prefix = typeIdentifier.prefix;
var prefixName = prefix.name;
var prefixElement = nameScope.lookup(prefixName).getter;
prefix.staticElement = prefixElement;
if (prefixElement == null) {
_resolveToElement(node, null);
return;
}
if (prefixElement is ClassElement || prefixElement is TypeAliasElement) {
_rewriteToConstructorName(node, typeIdentifier);
return;
}
if (prefixElement is PrefixElement) {
final nameNode = typeIdentifier.identifier;
final element = _lookupGetter(prefixElement.scope, nameNode);
nameNode.staticElement = element;
_resolveToElement(node, element);
return;
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.PREFIX_SHADOWED_BY_LOCAL_DECLARATION,
prefix,
[prefix.name],
);
node.type = dynamicType;
} else {
var nameNode = typeIdentifier as SimpleIdentifierImpl;
if (nameNode.name == 'void') {
node.type = VoidTypeImpl.instance;
return;
}
final element = _lookupGetter(nameScope, nameNode);
nameNode.staticElement = element;
_resolveToElement(node, element);
}
}
/// Return type arguments, exactly [parameterCount].
List<DartType> _buildTypeArguments(
NamedType node, TypeArgumentList argumentList, int parameterCount) {
var arguments = argumentList.arguments;
var argumentCount = arguments.length;
if (argumentCount != parameterCount) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS,
node,
[node.name.name, parameterCount, argumentCount],
);
return List.filled(parameterCount, DynamicTypeImpl.instance);
}
if (parameterCount == 0) {
return const <DartType>[];
}
return List.generate(
parameterCount,
(i) => arguments[i].typeOrThrow,
);
}
NullabilitySuffix _getNullability(NamedType node) {
if (isNonNullableByDefault) {
if (node.question != null) {
return NullabilitySuffix.question;
} else {
return NullabilitySuffix.none;
}
}
return NullabilitySuffix.star;
}
/// We are resolving the [NamedType] in a redirecting constructor of the
/// [enclosingClass].
InterfaceType _inferRedirectedConstructor(ClassElement element) {
if (element == enclosingClass) {
return element.thisType;
} else {
var typeParameters = element.typeParameters;
if (typeParameters.isEmpty) {
return element.thisType;
} else {
var inferrer = typeSystem.setupGenericTypeInference(
typeParameters: typeParameters,
declaredReturnType: element.thisType,
contextReturnType: enclosingClass!.thisType,
genericMetadataIsEnabled: _genericMetadataIsEnabled,
);
var typeArguments = inferrer.upwardsInfer();
return element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: _noneOrStarSuffix,
);
}
}
}
DartType _instantiateElement(NamedType node, Element element) {
var nullability = _getNullability(node);
var argumentList = node.typeArguments;
if (argumentList != null) {
if (element is ClassElement) {
var typeArguments = _buildTypeArguments(
node,
argumentList,
element.typeParameters.length,
);
return element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullability,
);
} else if (element is TypeAliasElement) {
var typeArguments = _buildTypeArguments(
node,
argumentList,
element.typeParameters.length,
);
var type = element.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: nullability,
);
type = typeSystem.toLegacyTypeIfOptOut(type);
return _verifyTypeAliasForContext(node, element, type);
} else if (_isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
} else if (element is DynamicElementImpl) {
_buildTypeArguments(node, argumentList, 0);
return DynamicTypeImpl.instance;
} else if (element is NeverElementImpl) {
_buildTypeArguments(node, argumentList, 0);
return _instantiateElementNever(nullability);
} else if (element is TypeParameterElement) {
_buildTypeArguments(node, argumentList, 0);
return element.instantiate(
nullabilitySuffix: nullability,
);
} else {
_ErrorHelper(errorReporter).reportNullOrNonTypeElement(node, element);
return dynamicType;
}
}
if (element is ClassElement) {
if (identical(node, withClause_namedType)) {
for (var mixin in enclosingClass!.mixins) {
if (mixin.element == element) {
return mixin;
}
}
}
if (identical(node, redirectedConstructor_namedType)) {
return _inferRedirectedConstructor(element);
}
return typeSystem.instantiateToBounds2(
classElement: element,
nullabilitySuffix: nullability,
);
} else if (element is TypeAliasElement) {
var type = typeSystem.instantiateToBounds2(
typeAliasElement: element,
nullabilitySuffix: nullability,
);
return _verifyTypeAliasForContext(node, element, type);
} else if (_isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
} else if (element is DynamicElementImpl) {
return DynamicTypeImpl.instance;
} else if (element is NeverElementImpl) {
return _instantiateElementNever(nullability);
} else if (element is TypeParameterElement) {
return element.instantiate(
nullabilitySuffix: nullability,
);
} else {
_ErrorHelper(errorReporter).reportNullOrNonTypeElement(node, element);
return dynamicType;
}
}
DartType _instantiateElementNever(NullabilitySuffix nullability) {
if (isNonNullableByDefault) {
return NeverTypeImpl.instance.withNullability(nullability);
} else {
return typeSystem.typeProvider.nullType;
}
}
Element? _lookupGetter(Scope scope, SimpleIdentifier node) {
final scopeLookupResult = scope.lookup(node.name);
reportDeprecatedExportUseGetter(
scopeLookupResult: scopeLookupResult,
node: node,
);
return scopeLookupResult.getter;
}
void _resolveToElement(NamedTypeImpl node, Element? element) {
if (element == null) {
node.type = dynamicType;
if (!_libraryElement.shouldIgnoreUndefinedIdentifier(node.name)) {
_ErrorHelper(errorReporter).reportNullOrNonTypeElement(node, null);
}
return;
}
if (element is MultiplyDefinedElement) {
node.type = dynamicType;
return;
}
var type = _instantiateElement(node, element);
type = _verifyNullability(node, type);
node.type = type;
}
/// We parse `foo.bar` as `prefix.Name` with the expectation that `prefix`
/// will be a [PrefixElement]. But when we resolved the `prefix` it turned
/// out to be a [ClassElement], so it is probably a `Class.constructor`.
void _rewriteToConstructorName(
NamedTypeImpl node,
PrefixedIdentifier typeIdentifier,
) {
var constructorName = node.parent;
if (constructorName is ConstructorNameImpl &&
constructorName.name == null) {
var classIdentifier = typeIdentifier.prefix;
var constructorIdentifier = typeIdentifier.identifier;
var typeArguments = node.typeArguments;
if (typeArguments != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR,
typeArguments,
[classIdentifier.name, constructorIdentifier.name],
);
var instanceCreation = constructorName.parent;
if (instanceCreation is InstanceCreationExpressionImpl) {
instanceCreation.typeArguments = typeArguments;
}
}
node.name = classIdentifier;
node.typeArguments = null;
constructorName.period = typeIdentifier.period;
constructorName.name = constructorIdentifier;
rewriteResult = constructorName;
return;
}
if (_isInstanceCreation(node)) {
node.type = dynamicType;
_ErrorHelper(errorReporter).reportNewWithNonType(node);
} else {
node.type = dynamicType;
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NOT_A_TYPE,
typeIdentifier,
[typeIdentifier.name],
);
}
}
/// If the [node] appears in a location where a nullable type is not allowed,
/// but the [type] is nullable (because the question mark was specified,
/// or the type alias is nullable), report an error, and return the
/// corresponding non-nullable type.
DartType _verifyNullability(NamedType node, DartType type) {
if (identical(node, classHierarchy_namedType)) {
if (type.nullabilitySuffix == NullabilitySuffix.question) {
var parent = node.parent;
if (parent is ExtendsClause || parent is ClassTypeAlias) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NULLABLE_TYPE_IN_EXTENDS_CLAUSE,
node,
);
} else if (parent is ImplementsClause) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NULLABLE_TYPE_IN_IMPLEMENTS_CLAUSE,
node,
);
} else if (parent is OnClause) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NULLABLE_TYPE_IN_ON_CLAUSE,
node,
);
} else if (parent is WithClause) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NULLABLE_TYPE_IN_WITH_CLAUSE,
node,
);
}
return (type as TypeImpl).withNullability(NullabilitySuffix.none);
}
}
return type;
}
DartType _verifyTypeAliasForContext(
NamedType node,
TypeAliasElement element,
DartType type,
) {
// If a type alias that expands to a type parameter.
if (element.aliasedType is TypeParameterType) {
var parent = node.parent;
if (parent is ConstructorName) {
var errorNode = _ErrorHelper._getErrorNode(node);
var constructorUsage = parent.parent;
if (constructorUsage is InstanceCreationExpression) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode
.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
errorNode,
);
} else if (constructorUsage is ConstructorDeclaration &&
constructorUsage.redirectedConstructor == parent) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode
.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
errorNode,
);
} else {
throw UnimplementedError('${constructorUsage.runtimeType}');
}
return dynamicType;
}
// Report if this type is used as a class in hierarchy.
ErrorCode? errorCode;
if (parent is ExtendsClause) {
errorCode =
CompileTimeErrorCode.EXTENDS_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
} else if (parent is ImplementsClause) {
errorCode = CompileTimeErrorCode
.IMPLEMENTS_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
} else if (parent is OnClause) {
errorCode =
CompileTimeErrorCode.MIXIN_ON_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
} else if (parent is WithClause) {
errorCode =
CompileTimeErrorCode.MIXIN_OF_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
}
if (errorCode != null) {
var errorNode = _ErrorHelper._getErrorNode(node);
errorReporter.reportErrorForNode(errorCode, errorNode);
hasErrorReported = true;
return dynamicType;
}
}
if (type is! InterfaceType && _isInstanceCreation(node)) {
_ErrorHelper(errorReporter).reportNewWithNonType(node);
return dynamicType;
}
return type;
}
static bool _isInstanceCreation(NamedType node) {
var parent = node.parent;
return parent is ConstructorName &&
parent.parent is InstanceCreationExpression;
}
}
/// Helper for reporting errors during type name resolution.
class _ErrorHelper {
final ErrorReporter errorReporter;
_ErrorHelper(this.errorReporter);
bool reportNewWithNonType(NamedType node) {
var constructorName = node.parent;
if (constructorName is ConstructorName) {
var instanceCreation = constructorName.parent;
if (instanceCreation is InstanceCreationExpression) {
var identifier = node.name;
var errorNode = _getErrorNode(node);
errorReporter.reportErrorForNode(
instanceCreation.isConst
? CompileTimeErrorCode.CONST_WITH_NON_TYPE
: CompileTimeErrorCode.NEW_WITH_NON_TYPE,
errorNode,
[identifier.name],
);
return true;
}
}
return false;
}
void reportNullOrNonTypeElement(NamedType node, Element? element) {
var identifier = node.name;
var errorNode = _getErrorNode(node);
if (errorNode.name == 'boolean') {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_CLASS_BOOLEAN,
errorNode,
[identifier.name],
);
return;
}
if (_isTypeInCatchClause(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_TYPE_IN_CATCH_CLAUSE,
identifier,
[identifier.name],
);
return;
}
if (_isTypeInAsExpression(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CAST_TO_NON_TYPE,
identifier,
[identifier.name],
);
return;
}
if (_isTypeInIsExpression(node)) {
if (element != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.TYPE_TEST_WITH_NON_TYPE,
identifier,
[identifier.name],
);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.TYPE_TEST_WITH_UNDEFINED_NAME,
identifier,
[identifier.name],
);
}
return;
}
if (_isRedirectingConstructor(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.REDIRECT_TO_NON_CLASS,
identifier,
[identifier.name],
);
return;
}
if (_isTypeInTypeArgumentList(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_TYPE_AS_TYPE_ARGUMENT,
identifier,
[identifier.name],
);
return;
}
if (reportNewWithNonType(node)) {
return;
}
var parent = node.parent;
if (parent is ExtendsClause ||
parent is ImplementsClause ||
parent is WithClause ||
parent is ClassTypeAlias) {
// Ignored. The error will be reported elsewhere.
return;
}
if (element is LocalVariableElement ||
(element is FunctionElement &&
element.enclosingElement is ExecutableElement)) {
errorReporter.reportError(
DiagnosticFactory().referencedBeforeDeclaration(
errorReporter.source,
identifier,
element: element,
),
);
return;
}
if (element != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NOT_A_TYPE,
identifier,
[identifier.name],
);
return;
}
if (identifier is SimpleIdentifier && identifier.name == 'await') {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_IDENTIFIER_AWAIT,
node,
);
return;
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_CLASS,
identifier,
[identifier.name],
);
}
/// Returns the simple identifier of the given (maybe prefixed) identifier.
static Identifier _getErrorNode(NamedType node) {
Identifier identifier = node.name;
if (identifier is PrefixedIdentifier) {
// The prefixed identifier can be:
// 1. new importPrefix.NamedType()
// 2. new NamedType.constructorName()
// 3. new unresolved.Unresolved()
if (identifier.prefix.staticElement is PrefixElement) {
return identifier.identifier;
} else {
return identifier;
}
} else {
return identifier;
}
}
/// Check if the [node] is the type in a redirected constructor name.
static bool _isRedirectingConstructor(NamedType node) {
var parent = node.parent;
if (parent is ConstructorName) {
var grandParent = parent.parent;
if (grandParent is ConstructorDeclaration) {
return identical(grandParent.redirectedConstructor, parent);
}
}
return false;
}
/// Checks if the [node] is the type in an `as` expression.
static bool _isTypeInAsExpression(NamedType node) {
var parent = node.parent;
if (parent is AsExpression) {
return identical(parent.type, node);
}
return false;
}
/// Checks if the [node] is the exception type in a `catch` clause.
static bool _isTypeInCatchClause(NamedType node) {
var parent = node.parent;
if (parent is CatchClause) {
return identical(parent.exceptionType, node);
}
return false;
}
/// Checks if the [node] is the type in an `is` expression.
static bool _isTypeInIsExpression(NamedType node) {
var parent = node.parent;
if (parent is IsExpression) {
return identical(parent.type, node);
}
return false;
}
/// Checks if the [node] is an element in a type argument list.
static bool _isTypeInTypeArgumentList(NamedType node) {
return node.parent is TypeArgumentList;
}
}