blob: 4f9d087876bad027c2c911d801402261b4db80aa [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/type.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/class_hierarchy.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/summary2/default_types_builder.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/type_builder.dart';
/// Return `true` if [type] can be used as a class.
bool _isInterfaceTypeClass(InterfaceType type) {
if (type.element.isMixin) {
return false;
}
return _isInterfaceTypeInterface(type);
}
/// Return `true` if [type] can be used as an interface or a mixin.
bool _isInterfaceTypeInterface(InterfaceType type) {
if (type.element.isEnum) {
return false;
}
if (type.isDartCoreFunction || type.isDartCoreNull) {
return false;
}
if (type.nullabilitySuffix == NullabilitySuffix.question) {
return false;
}
return true;
}
List<InterfaceType> _toInterfaceTypeList(List<TypeName>? nodeList) {
if (nodeList != null) {
return nodeList
.map((e) => e.type)
.whereType<InterfaceType>()
.where(_isInterfaceTypeInterface)
.toList();
}
return const [];
}
class NodesToBuildType {
final List<AstNode> declarations = [];
final List<TypeBuilder> typeBuilders = [];
void addDeclaration(AstNode node) {
declarations.add(node);
}
void addTypeBuilder(TypeBuilder builder) {
typeBuilders.add(builder);
}
}
class TypesBuilder {
final Linker _linker;
TypesBuilder(this._linker);
DynamicTypeImpl get _dynamicType => DynamicTypeImpl.instance;
VoidTypeImpl get _voidType => VoidTypeImpl.instance;
/// Build types for all type annotations, and set types for declarations.
void build(NodesToBuildType nodes) {
DefaultTypesBuilder(_linker).build(nodes.declarations);
for (var builder in nodes.typeBuilders) {
builder.build();
}
// TODO(scheglov) generalize
_linker.elementNodes.forEach((element, node) {
if (element is GenericFunctionTypeElementImpl &&
node is GenericFunctionType) {
element.returnType = node.returnType?.type ?? _dynamicType;
}
if (element is TypeParameterElementImpl && node is TypeParameter) {
element.bound = node.bound?.type;
}
});
for (var declaration in nodes.declarations) {
_declaration(declaration);
}
_MixinsInference(_linker).perform(nodes.declarations);
}
FunctionType _buildFunctionType(
TypeParameterList? typeParameterList,
TypeAnnotation? returnTypeNode,
FormalParameterList parameterList,
NullabilitySuffix nullabilitySuffix,
) {
var returnType = returnTypeNode?.type ?? _dynamicType;
var typeParameters = _typeParameters(typeParameterList);
var formalParameters = _formalParameters(parameterList);
return FunctionTypeImpl(
typeFormals: typeParameters,
parameters: formalParameters,
returnType: returnType,
nullabilitySuffix: nullabilitySuffix,
);
}
void _classDeclaration(ClassDeclaration node) {
var element = node.declaredElement as ClassElementImpl;
var extendsClause = node.extendsClause;
if (extendsClause != null) {
var type = extendsClause.superclass.type;
if (type is InterfaceType && _isInterfaceTypeClass(type)) {
element.supertype = type;
} else {
element.supertype = _objectType(element);
}
} else if (element.library.isDartCore && element.name == 'Object') {
element.setModifier(Modifier.DART_CORE_OBJECT, true);
} else {
element.supertype = _objectType(element);
}
element.mixins = _toInterfaceTypeList(
node.withClause?.mixinTypes,
);
element.interfaces = _toInterfaceTypeList(
node.implementsClause?.interfaces,
);
}
void _classTypeAlias(ClassTypeAlias node) {
var element = node.declaredElement as ClassElementImpl;
var superType = node.superclass.type;
if (superType is InterfaceType && _isInterfaceTypeInterface(superType)) {
element.supertype = superType;
} else {
element.supertype = _objectType(element);
}
element.mixins = _toInterfaceTypeList(
node.withClause.mixinTypes,
);
element.interfaces = _toInterfaceTypeList(
node.implementsClause?.interfaces,
);
}
void _declaration(AstNode node) {
if (node is ClassDeclaration) {
_classDeclaration(node);
} else if (node is ClassTypeAlias) {
_classTypeAlias(node);
} else if (node is ExtensionDeclaration) {
_extensionDeclaration(node);
} else if (node is FieldFormalParameter) {
_fieldFormalParameter(node);
} else if (node is FunctionDeclaration) {
var returnType = node.returnType?.type;
if (returnType == null) {
if (node.isSetter) {
returnType = _voidType;
} else {
returnType = _dynamicType;
}
}
var element = node.declaredElement as ExecutableElementImpl;
element.returnType = returnType;
} else if (node is FunctionTypeAlias) {
_functionTypeAlias(node);
} else if (node is FunctionTypedFormalParameter) {
_functionTypedFormalParameter(node);
} else if (node is GenericTypeAlias) {
_genericTypeAlias(node);
} else if (node is MethodDeclaration) {
var returnType = node.returnType?.type;
if (returnType == null) {
if (node.isSetter) {
returnType = _voidType;
} else if (node.isOperator && node.name.name == '[]=') {
returnType = _voidType;
} else {
returnType = _dynamicType;
}
}
var element = node.declaredElement as ExecutableElementImpl;
element.returnType = returnType;
} else if (node is MixinDeclaration) {
_mixinDeclaration(node);
} else if (node is SimpleFormalParameter) {
var element = node.declaredElement as ParameterElementImpl;
element.type = node.type?.type ?? _dynamicType;
} else if (node is VariableDeclarationList) {
var type = node.type?.type;
if (type != null) {
for (var variable in node.variables) {
(variable.declaredElement as VariableElementImpl).type = type;
}
}
} else {
throw UnimplementedError('${node.runtimeType}');
}
}
void _extensionDeclaration(ExtensionDeclaration node) {
var element = node.declaredElement as ExtensionElementImpl;
element.extendedType = node.extendedType.typeOrThrow;
}
void _fieldFormalParameter(FieldFormalParameter node) {
var element = node.declaredElement as FieldFormalParameterElementImpl;
var parameterList = node.parameters;
if (parameterList != null) {
var type = _buildFunctionType(
node.typeParameters,
node.type,
parameterList,
_nullability(node, node.question != null),
);
element.type = type;
} else {
element.type = node.type?.type ?? _dynamicType;
}
}
List<ParameterElement> _formalParameters(FormalParameterList node) {
return node.parameters.asImpl.map((parameter) {
return parameter.declaredElement!;
}).toList();
}
void _functionTypeAlias(FunctionTypeAlias node) {
var element = node.declaredElement as TypeAliasElementImpl;
var function = element.aliasedElement as GenericFunctionTypeElementImpl;
function.returnType = node.returnType?.type ?? _dynamicType;
element.aliasedType = function.type;
}
void _functionTypedFormalParameter(FunctionTypedFormalParameter node) {
var type = _buildFunctionType(
node.typeParameters,
node.returnType,
node.parameters,
_nullability(node, node.question != null),
);
var element = node.declaredElement as ParameterElementImpl;
element.type = type;
}
void _genericTypeAlias(GenericTypeAlias node) {
var element = node.declaredElement as TypeAliasElementImpl;
var featureSet = element.library.featureSet;
var typeNode = node.type;
if (featureSet.isEnabled(Feature.nonfunction_type_aliases)) {
element.aliasedType = typeNode.typeOrThrow;
} else if (typeNode is GenericFunctionType) {
element.aliasedType = typeNode.typeOrThrow;
} else {
element.aliasedType = _errorFunctionType();
}
}
bool _isNonNullableByDefault(AstNode node) {
var unit = node.thisOrAncestorOfType<CompilationUnit>();
return unit!.featureSet.isEnabled(Feature.non_nullable);
}
void _mixinDeclaration(MixinDeclaration node) {
var element = node.declaredElement as MixinElementImpl;
var constraints = _toInterfaceTypeList(
node.onClause?.superclassConstraints,
);
if (constraints.isEmpty) {
constraints = [_objectType(element)];
}
element.superclassConstraints = constraints;
element.interfaces = _toInterfaceTypeList(
node.implementsClause?.interfaces,
);
}
NullabilitySuffix _nullability(AstNode node, bool hasQuestion) {
if (_isNonNullableByDefault(node)) {
if (hasQuestion) {
return NullabilitySuffix.question;
} else {
return NullabilitySuffix.none;
}
} else {
return NullabilitySuffix.star;
}
}
List<TypeParameterElement> _typeParameters(TypeParameterList? node) {
if (node == null) {
return const <TypeParameterElement>[];
}
return node.typeParameters
.map<TypeParameterElement>((p) => p.declaredElement!)
.toList();
}
/// The [FunctionType] to use when a function type is expected for a type
/// alias, but the actual provided type annotation is not a function type.
static FunctionTypeImpl _errorFunctionType() {
return FunctionTypeImpl(
typeFormals: const [],
parameters: const [],
returnType: DynamicTypeImpl.instance,
nullabilitySuffix: NullabilitySuffix.none,
);
}
static InterfaceType _objectType(ClassElementImpl element) {
return element.library.typeProvider.objectType;
}
}
/// Performs mixins inference in a [ClassDeclaration].
class _MixinInference {
final ClassElementImpl element;
final TypeSystemImpl typeSystem;
final FeatureSet featureSet;
final InterfaceType classType;
late final InterfacesMerger interfacesMerger;
_MixinInference(this.element, this.featureSet)
: typeSystem = element.library.typeSystem,
classType = element.thisType {
interfacesMerger = InterfacesMerger(typeSystem);
interfacesMerger.addWithSupertypes(element.supertype);
}
NullabilitySuffix get _noneOrStarSuffix {
return _nonNullableEnabled
? NullabilitySuffix.none
: NullabilitySuffix.star;
}
bool get _nonNullableEnabled => featureSet.isEnabled(Feature.non_nullable);
void perform(WithClause? withClause) {
if (withClause == null) return;
for (var mixinNode in withClause.mixinTypes) {
var mixinType = _inferSingle(mixinNode as TypeNameImpl);
interfacesMerger.addWithSupertypes(mixinType);
}
}
InterfaceType? _findInterfaceTypeForElement(
ClassElement element,
List<InterfaceType> interfaceTypes,
) {
for (var interfaceType in interfaceTypes) {
if (interfaceType.element == element) return interfaceType;
}
return null;
}
List<InterfaceType>? _findInterfaceTypesForConstraints(
List<InterfaceType> constraints,
List<InterfaceType> interfaceTypes,
) {
var result = <InterfaceType>[];
for (var constraint in constraints) {
var interfaceType = _findInterfaceTypeForElement(
constraint.element,
interfaceTypes,
);
// No matching interface type found, so inference fails.
if (interfaceType == null) {
return null;
}
result.add(interfaceType);
}
return result;
}
InterfaceType _inferSingle(TypeNameImpl mixinNode) {
var mixinType = _interfaceType(mixinNode.typeOrThrow);
if (mixinNode.typeArguments != null) {
return mixinType;
}
var mixinElement = mixinType.element;
if (mixinElement.typeParameters.isEmpty) {
return mixinType;
}
var mixinSupertypeConstraints =
typeSystem.gatherMixinSupertypeConstraintsForInference(mixinElement);
if (mixinSupertypeConstraints.isEmpty) {
return mixinType;
}
var matchingInterfaceTypes = _findInterfaceTypesForConstraints(
mixinSupertypeConstraints,
interfacesMerger.typeList,
);
// Note: if matchingInterfaceType is null, that's an error. Also,
// if there are multiple matching interface types that use
// different type parameters, that's also an error. But we can't
// report errors from the linker, so we just use the
// first matching interface type (if there is one). The error
// detection logic is implemented in the ErrorVerifier.
if (matchingInterfaceTypes == null) {
return mixinType;
}
// Try to pattern match matchingInterfaceTypes against
// mixinSupertypeConstraints to find the correct set of type
// parameters to apply to the mixin.
var inferredTypeArguments = typeSystem.matchSupertypeConstraints(
mixinElement,
mixinSupertypeConstraints,
matchingInterfaceTypes,
genericMetadataIsEnabled:
mixinElement.library.featureSet.isEnabled(Feature.generic_metadata),
);
if (inferredTypeArguments != null) {
var inferredMixin = mixinElement.instantiate(
typeArguments: inferredTypeArguments,
nullabilitySuffix: _noneOrStarSuffix,
);
mixinType = inferredMixin;
mixinNode.type = inferredMixin;
}
return mixinType;
}
InterfaceType _interfaceType(DartType type) {
if (type is InterfaceType && _isInterfaceTypeInterface(type)) {
return type;
}
return typeSystem.typeProvider.objectType;
}
}
/// Performs mixin inference for all declarations.
class _MixinsInference {
final Linker _linker;
_MixinsInference(this._linker);
void perform(List<AstNode> declarations) {
for (var node in declarations) {
if (node is ClassDeclaration || node is ClassTypeAlias) {
var element = (node as Declaration).declaredElement as ClassElementImpl;
element.mixinInferenceCallback = _callbackWhenRecursion;
}
}
for (var declaration in declarations) {
_inferDeclaration(declaration);
}
_resetHierarchies(declarations);
}
/// This method is invoked when mixins are asked from the [element], and
/// we are inferring the [element] now, i.e. there is a loop.
///
/// This is an error. So, we return the empty list, and break the loop.
List<InterfaceType> _callbackWhenLoop(ClassElementImpl element) {
element.mixinInferenceCallback = null;
return <InterfaceType>[];
}
/// This method is invoked when mixins are asked from the [element], and
/// we are not inferring the [element] now, i.e. there is no loop.
List<InterfaceType>? _callbackWhenRecursion(ClassElementImpl element) {
var node = _linker.getLinkingNode(element);
if (node != null) {
_inferDeclaration(node);
}
// The inference was successful, let the element return actual mixins.
return null;
}
void _infer(ClassElementImpl element, WithClause? withClause) {
if (withClause != null) {
element.mixinInferenceCallback = _callbackWhenLoop;
try {
var featureSet = element.library.featureSet;
_MixinInference(element, featureSet).perform(withClause);
} finally {
element.mixinInferenceCallback = null;
element.mixins = _toInterfaceTypeList(
withClause.mixinTypes,
);
}
}
}
void _inferDeclaration(AstNode node) {
if (node is ClassDeclaration) {
var element = node.declaredElement as ClassElementImpl;
_infer(element, node.withClause);
} else if (node is ClassTypeAlias) {
var element = node.declaredElement as ClassElementImpl;
_infer(element, node.withClause);
}
}
/// When a loop is detected during mixin inference, we pretend that the list
/// of mixins of the class is empty. But if this happens during building a
/// class hierarchy, we cache such incomplete hierarchy. So, here we reset
/// hierarchies for all classes being linked, indiscriminately.
void _resetHierarchies(List<AstNode> declarations) {
for (var declaration in declarations) {
if (declaration is ClassOrMixinDeclaration) {
var element = declaration.declaredElement as ClassElementImpl;
var sessionImpl = element.library.session as AnalysisSessionImpl;
sessionImpl.classHierarchy.remove(element);
}
}
}
}