| // 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/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/type_algebra.dart'; |
| import 'package:analyzer/src/dart/resolver/scope.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/type_system.dart'; |
| |
| import '../../../protocol_server.dart' show CompletionSuggestion; |
| |
| /// A contributor for calculating suggestions based on the members of |
| /// extensions. |
| class ExtensionMemberContributor extends DartCompletionContributor { |
| MemberSuggestionBuilder builder; |
| |
| @override |
| Future<List<CompletionSuggestion>> computeSuggestions( |
| DartCompletionRequest request) async { |
| LibraryElement containingLibrary = request.libraryElement; |
| // Gracefully degrade if the library element is not resolved |
| // e.g. detached part file or source change |
| if (containingLibrary == null) { |
| return const <CompletionSuggestion>[]; |
| } |
| |
| // Recompute the target since resolution may have changed it. |
| Expression expression = request.dotTarget; |
| if (expression == null || expression.isSynthetic) { |
| return const <CompletionSuggestion>[]; |
| } |
| if (expression is Identifier) { |
| Element 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>[]; |
| } |
| } |
| builder = MemberSuggestionBuilder(containingLibrary); |
| if (expression is ExtensionOverride) { |
| _addInstanceMembers(expression.staticElement); |
| } else { |
| var type = expression.staticType; |
| if (type == null) { |
| // Without a type we cannot 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>[]; |
| } |
| LibraryScope nameScope = new LibraryScope(containingLibrary); |
| for (var extension in nameScope.extensions) { |
| var typeSystem = containingLibrary.context.typeSystem; |
| var typeProvider = containingLibrary.context.typeProvider; |
| var extendedType = |
| _resolveExtendedType(typeSystem, typeProvider, extension, type); |
| if (extendedType != null && |
| typeSystem.isSubtypeOf(type, extendedType)) { |
| // TODO(brianwilkerson) We might want to apply the substitution to the |
| // members of the extension for display purposes. |
| _addInstanceMembers(extension); |
| } |
| } |
| expression.staticType; |
| } |
| return builder.suggestions.toList(); |
| } |
| |
| void _addInstanceMembers(ExtensionElement extension) { |
| for (MethodElement method in extension.methods) { |
| if (!method.isStatic) { |
| builder.addSuggestion(method); |
| } |
| } |
| for (PropertyAccessorElement accessor in extension.accessors) { |
| if (!accessor.isStatic) { |
| builder.addSuggestion(accessor); |
| } |
| } |
| } |
| |
| /// Use the [typeProvider], [typeSystem] and the [type] of the object being |
| /// extended to compute the actual type extended by the [extension]. Return |
| /// the computed type, or `null` if the type cannot be computed. |
| DartType _resolveExtendedType(TypeSystem typeSystem, |
| TypeProvider typeProvider, ExtensionElement extension, DartType type) { |
| var typeParameters = extension.typeParameters; |
| var inferrer = GenericInferrer( |
| typeProvider, |
| 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, |
| ); |
| } |
| } |