blob: b9f376aac32e4c1d221f5bfb421f8315fb040262 [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: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/src/dart/element/generic_inferrer.dart'
show GenericInferrer;
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
/// A contributor that produces suggestions based on the members of an
/// extension.
class ExtensionMemberContributor extends DartCompletionContributor {
late MemberSuggestionBuilder memberBuilder;
@override
Future<void> computeSuggestions(
DartCompletionRequest request, SuggestionBuilder builder) async {
var containingLibrary = request.libraryElement;
// Gracefully degrade if the library could not be determined, such as with a
// detached part file or source change.
if (containingLibrary == null) {
return;
}
memberBuilder = MemberSuggestionBuilder(request, builder);
// Recompute the target because resolution might have changed it.
var expression = request.dotTarget;
if (expression == null) {
var classOrMixin = request.target.containingNode
.thisOrAncestorOfType<ClassOrMixinDeclaration>();
if (classOrMixin != null) {
var type = classOrMixin.declaredElement?.thisType;
if (type != null) {
_addExtensionMembers(containingLibrary, type);
}
} else {
var extension = request.target.containingNode
.thisOrAncestorOfType<ExtensionDeclaration>();
if (extension != null) {
var extendedType = extension.extendedType.type;
if (extendedType is InterfaceType) {
var types = [extendedType, ...extendedType.allSupertypes];
for (var type in types) {
var inheritanceDistance = memberBuilder.request.featureComputer
.inheritanceDistanceFeature(
extendedType.element, type.element);
_addTypeMembers(type, inheritanceDistance);
}
}
}
}
return;
}
if (expression.isSynthetic) {
return;
}
if (expression is Identifier) {
var elem = expression.staticElement;
if (elem is ClassElement) {
// Suggestions provided by StaticMemberContributor.
return;
} else if (elem is ExtensionElement) {
// Suggestions provided by StaticMemberContributor.
return;
} else if (elem is PrefixElement) {
// Suggestions provided by LibraryMemberContributor.
return;
}
}
if (expression is ExtensionOverride) {
var staticElement = expression.staticElement;
if (staticElement != null) {
_addInstanceMembers(staticElement, 0.0);
}
} else {
var type = expression.staticType;
if (type == null) {
// Without a type we can't find the extensions that apply. We shouldn't
// get to this point, but there's an NPE if we invoke
// `_resolveExtendedType` when `type` is `null`, so we guard against it
// to ensure that we can return the suggestions from other providers.
return;
}
_addExtensionMembers(containingLibrary, type);
expression.staticType;
}
}
void _addExtensionMembers(LibraryElement containingLibrary, DartType type) {
var typeSystem = containingLibrary.typeSystem;
var nameScope = containingLibrary.scope;
for (var extension in nameScope.extensions) {
var extendedType =
_resolveExtendedType(containingLibrary, extension, type);
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
var inheritanceDistance = 0.0;
if (type is InterfaceType && extendedType is InterfaceType) {
inheritanceDistance = memberBuilder.request.featureComputer
.inheritanceDistanceFeature(type.element, extendedType.element);
}
// TODO(brianwilkerson) We might want to apply the substitution to the
// members of the extension for display purposes.
_addInstanceMembers(extension, inheritanceDistance);
}
}
}
void _addInstanceMembers(
ExtensionElement extension, double inheritanceDistance) {
for (var method in extension.methods) {
if (!method.isStatic) {
memberBuilder.addSuggestionForMethod(
method: method, inheritanceDistance: inheritanceDistance);
}
}
for (var accessor in extension.accessors) {
if (!accessor.isStatic) {
memberBuilder.addSuggestionForAccessor(
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
}
void _addTypeMembers(InterfaceType type, double inheritanceDistance) {
for (var method in type.methods) {
memberBuilder.addSuggestionForMethod(
method: method, inheritanceDistance: inheritanceDistance);
}
for (var accessor in type.accessors) {
memberBuilder.addSuggestionForAccessor(
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
/// Use the [type] of the object being extended in the [library] to compute
/// the actual type extended by the [extension]. Return the computed type,
/// or `null` if the type cannot be computed.
DartType? _resolveExtendedType(
LibraryElement library,
ExtensionElement extension,
DartType type,
) {
var typeParameters = extension.typeParameters;
var inferrer =
GenericInferrer(library.typeSystem as TypeSystemImpl, typeParameters);
inferrer.constrainArgument(
type,
extension.extendedType,
'extendedType',
);
var typeArguments = inferrer.infer(typeParameters,
failAtError: true, genericMetadataIsEnabled: true);
if (typeArguments == null) {
return null;
}
var substitution = Substitution.fromPairs(
typeParameters,
typeArguments,
);
return substitution.substituteType(
extension.extendedType,
);
}
}