| // 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 'dart:async'; |
| |
| import 'package:analysis_server/src/protocol_server.dart' |
| show CompletionSuggestion; |
| 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/element.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/resolver/scope.dart'; |
| |
| /// A contributor that produces suggestions based on the members of an |
| /// extension. |
| class ExtensionMemberContributor extends DartCompletionContributor { |
| MemberSuggestionBuilder memberBuilder; |
| |
| @override |
| Future<List<CompletionSuggestion>> 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 const <CompletionSuggestion>[]; |
| } |
| |
| 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; |
| _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 = <InterfaceType>[]; |
| ClassElementImpl.collectAllSupertypes(types, extendedType, null); |
| for (var type in types) { |
| double inheritanceDistance; |
| if (request.useNewRelevance) { |
| inheritanceDistance = memberBuilder.request.featureComputer |
| .inheritanceDistanceFeature( |
| extendedType.element, type.element); |
| } |
| _addTypeMembers(type, inheritanceDistance); |
| } |
| } |
| } |
| } |
| |
| return const <CompletionSuggestion>[]; |
| } |
| |
| if (expression.isSynthetic) { |
| return const <CompletionSuggestion>[]; |
| } |
| if (expression is Identifier) { |
| var elem = expression.staticElement; |
| if (elem is ClassElement) { |
| // Suggestions provided by StaticMemberContributor. |
| return const <CompletionSuggestion>[]; |
| } else if (elem is ExtensionElement) { |
| // Suggestions provided by StaticMemberContributor. |
| return const <CompletionSuggestion>[]; |
| } else if (elem is PrefixElement) { |
| // Suggestions provided by LibraryMemberContributor. |
| return const <CompletionSuggestion>[]; |
| } |
| } |
| if (expression is ExtensionOverride) { |
| _addInstanceMembers(expression.staticElement, -1.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 const <CompletionSuggestion>[]; |
| } |
| _addExtensionMembers(containingLibrary, type); |
| expression.staticType; |
| } |
| return const <CompletionSuggestion>[]; |
| } |
| |
| void _addExtensionMembers(LibraryElement containingLibrary, DartType type) { |
| var typeSystem = containingLibrary.typeSystem; |
| var nameScope = LibraryScope(containingLibrary); |
| for (var extension in nameScope.extensions) { |
| var extendedType = |
| _resolveExtendedType(containingLibrary, extension, type); |
| if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) { |
| double inheritanceDistance; |
| if (memberBuilder.request.useNewRelevance) { |
| 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, typeParameters); |
| inferrer.constrainArgument( |
| type, |
| extension.extendedType, |
| 'extendedType', |
| ); |
| var typeArguments = inferrer.infer(typeParameters, failAtError: true); |
| if (typeArguments == null) { |
| return null; |
| } |
| var substitution = Substitution.fromPairs( |
| typeParameters, |
| typeArguments, |
| ); |
| return substitution.substituteType( |
| extension.extendedType, |
| ); |
| } |
| } |