blob: f2815f2cb0c8b0243a60e0c35c44cc8d3c7150fb [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 '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,
);
}
}