blob: e67d2032938acb8c510db0ad15823485c7c1ba25 [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/src/utilities/visitors/local_declaration_visitor.dart';
/// A contributor that produces suggestions based on the instance members of a
/// given type, whether declared by that type directly or inherited from a
/// superinterface. More concretely, this class produces suggestions for
/// expressions of the form `o.^`, where `o` is an expression denoting an
/// instance of a type.
class TypeMemberContributor extends DartCompletionContributor {
@override
Future<void> computeSuggestions(
DartCompletionRequest request, SuggestionBuilder builder) async {
// Recompute the target because resolution might have changed it.
var expression = request.dotTarget;
if (expression == null ||
expression.isSynthetic ||
expression is ExtensionOverride) {
return;
}
if (expression is Identifier) {
var elem = expression.staticElement;
if (elem is ClassElement) {
// Suggestions provided by StaticMemberContributor.
return;
}
if (elem is PrefixElement) {
// Suggestions provided by LibraryMemberContributor.
return;
}
}
// Determine the target expression's type.
var type = expression.staticType?.resolveToBound(request.objectType);
if (type == null || type.isDynamic) {
// If the expression does not provide a good type, then attempt to get a
// better type from the element.
if (expression is Identifier) {
var elem = expression.staticElement;
if (elem is FunctionTypedElement) {
type = elem.returnType;
} else if (elem is ParameterElement) {
type = elem.type;
} else if (elem is LocalVariableElement) {
type = elem.type;
}
if ((type == null || type.isDynamic) &&
expression is SimpleIdentifier) {
// If the element does not provide a good type, then attempt to get a
// better type from a local declaration.
var visitor = _LocalBestTypeVisitor(expression.name, request.offset);
if (visitor.visit(expression) && visitor.typeFound != null) {
type = visitor.typeFound;
}
}
}
}
List<InterfaceType>? mixins;
List<InterfaceType>? superclassConstraints;
if (expression is SuperExpression && type is InterfaceType) {
// Suggest members from superclass if target is "super".
mixins = type.mixins;
superclassConstraints = type.superclassConstraints;
type = type.superclass;
}
if (type is FunctionType) {
builder.suggestFunctionCall();
type = request.objectType;
} else if (type == null || type.isDynamic) {
// Suggest members from object if target is "dynamic".
type = request.objectType;
}
// Build the suggestions.
if (type is InterfaceType) {
var memberBuilder = _SuggestionBuilder(request, builder);
memberBuilder.buildSuggestions(type,
mixins: mixins, superclassConstraints: superclassConstraints);
}
}
}
/// An [AstVisitor] which looks for a declaration with the given name and if
/// found, tries to determine a type for that declaration.
class _LocalBestTypeVisitor extends LocalDeclarationVisitor {
/// The name for the declaration to be found.
final String targetName;
/// The best type for the found declaration, or `null` if no declaration found
/// or failed to determine a type.
DartType? typeFound;
/// Construct a new instance to search for a declaration.
_LocalBestTypeVisitor(this.targetName, int offset) : super(offset);
@override
void declaredClass(ClassDeclaration declaration) {
if (declaration.name.name == targetName) {
// no type
finished();
}
}
@override
void declaredClassTypeAlias(ClassTypeAlias declaration) {
if (declaration.name.name == targetName) {
// no type
finished();
}
}
@override
void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
if (varDecl.name.name == targetName) {
// Type provided by the element in computeFull above
finished();
}
}
@override
void declaredFunction(FunctionDeclaration declaration) {
if (declaration.name.name == targetName) {
var returnType = declaration.returnType;
if (returnType != null) {
var type = returnType.type;
if (type != null) {
typeFound = type;
}
}
finished();
}
}
@override
void declaredFunctionTypeAlias(FunctionTypeAlias declaration) {
if (declaration.name.name == targetName) {
var returnType = declaration.returnType;
if (returnType != null) {
var type = returnType.type;
if (type != null) {
typeFound = type;
}
}
finished();
}
}
@override
void declaredGenericTypeAlias(GenericTypeAlias declaration) {
if (declaration.name.name == targetName) {
var returnType = declaration.functionType?.returnType;
if (returnType != null) {
var type = returnType.type;
if (type != null) {
typeFound = type;
}
}
finished();
}
}
@override
void declaredLabel(Label label, bool isCaseLabel) {
if (label.label.name == targetName) {
// no type
finished();
}
}
@override
void declaredLocalVar(SimpleIdentifier name, TypeAnnotation? type) {
if (name.name == targetName) {
var element = name.staticElement as VariableElement;
typeFound = element.type;
finished();
}
}
@override
void declaredMethod(MethodDeclaration declaration) {
if (declaration.name.name == targetName) {
var returnType = declaration.returnType;
if (returnType != null) {
var type = returnType.type;
if (type != null) {
typeFound = type;
}
}
finished();
}
}
@override
void declaredParam(SimpleIdentifier name, TypeAnnotation? type) {
if (name.name == targetName) {
// Type provided by the element in computeFull above.
finished();
}
}
@override
void declaredTopLevelVar(
VariableDeclarationList varList, VariableDeclaration varDecl) {
if (varDecl.name.name == targetName) {
// Type provided by the element in computeFull above.
finished();
}
}
}
/// This class provides suggestions based upon the visible instance members in
/// an interface type.
class _SuggestionBuilder extends MemberSuggestionBuilder {
/// Initialize a newly created suggestion builder.
_SuggestionBuilder(DartCompletionRequest request, SuggestionBuilder builder)
: super(request, builder);
/// Return completion suggestions for 'dot' completions on the given [type].
/// If the 'dot' completion is a super expression, then [containingMethodName]
/// is the name of the method in which the completion is requested.
void buildSuggestions(InterfaceType type,
{List<InterfaceType>? mixins,
List<InterfaceType>? superclassConstraints}) {
// Visit all of the types in the class hierarchy, collecting possible
// completions. If multiple elements are found that complete to the same
// identifier, addSuggestion will discard all but the first (with a few
// exceptions to handle getter/setter pairs).
var types = _getTypeOrdering(type);
if (mixins != null) {
types.addAll(mixins);
}
if (superclassConstraints != null) {
types.addAll(superclassConstraints);
}
for (var targetType in types) {
var inheritanceDistance = request.featureComputer
.inheritanceDistanceFeature(type.element, targetType.element);
for (var method in targetType.methods) {
// Exclude static methods when completion on an instance.
if (!method.isStatic) {
addSuggestionForMethod(
method: method, inheritanceDistance: inheritanceDistance);
}
}
for (var accessor in targetType.accessors) {
if (!accessor.isStatic) {
addSuggestionForAccessor(
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
if (targetType.isDartCoreFunction) {
builder.suggestFunctionCall();
}
}
}
/// Get a list of [InterfaceType]s that should be searched to find the
/// possible completions for an object having type [type].
List<InterfaceType> _getTypeOrdering(InterfaceType type) {
// Candidate completions can come from [type] as well as any types above it
// in the class hierarchy (including mixins, superclasses, and interfaces).
// If a given completion identifier shows up in multiple types, we should
// use the element that is nearest in the superclass chain, so we will
// visit [type] first, then its mixins, then its superclass, then its
// superclass's mixins, etc., and only afterwards visit interfaces.
//
// We short-circuit loops in the class hierarchy by keeping track of the
// classes seen (not the interfaces) so that we won't be fooled by nonsense
// like "class C<T> extends C<List<T>> {}"
var result = <InterfaceType>[];
Set<ClassElement> classesSeen = HashSet<ClassElement>();
var typesToVisit = <InterfaceType>[type];
while (typesToVisit.isNotEmpty) {
var nextType = typesToVisit.removeLast();
if (!classesSeen.add(nextType.element)) {
// Class had already been seen, so ignore this type.
continue;
}
result.add(nextType);
// typesToVisit is a stack, so push on the interfaces first, then the
// superclass, then the mixins. This will ensure that they are visited
// in the reverse order.
typesToVisit.addAll(nextType.interfaces);
var superclass = nextType.superclass;
if (superclass != null) {
typesToVisit.add(superclass);
}
typesToVisit.addAll(nextType.superclassConstraints);
typesToVisit.addAll(nextType.mixins);
}
return result;
}
}