Skip to content

Commit ba5c2a1

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Support completion of extension members
Change-Id: I78a59216add7ea612d77bae0d3e52a0185a682e3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/113281 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 4938e45 commit ba5c2a1

File tree

6 files changed

+347
-120
lines changed

6 files changed

+347
-120
lines changed

pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:analysis_server/src/services/completion/dart/combinator_contribu
1515
import 'package:analysis_server/src/services/completion/dart/common_usage_sorter.dart';
1616
import 'package:analysis_server/src/services/completion/dart/completion_ranking.dart';
1717
import 'package:analysis_server/src/services/completion/dart/contribution_sorter.dart';
18+
import 'package:analysis_server/src/services/completion/dart/extension_member_contributor.dart';
1819
import 'package:analysis_server/src/services/completion/dart/field_formal_contributor.dart';
1920
import 'package:analysis_server/src/services/completion/dart/imported_reference_contributor.dart';
2021
import 'package:analysis_server/src/services/completion/dart/inherited_reference_contributor.dart';
@@ -110,6 +111,7 @@ class DartCompletionManager implements CompletionContributor {
110111
List<DartCompletionContributor> contributors = <DartCompletionContributor>[
111112
new ArgListContributor(),
112113
new CombinatorContributor(),
114+
new ExtensionMemberContributor(),
113115
new FieldFormalContributor(),
114116
new InheritedReferenceContributor(),
115117
new KeywordContributor(),
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
8+
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
9+
import 'package:analyzer/dart/ast/ast.dart';
10+
import 'package:analyzer/dart/element/element.dart';
11+
import 'package:analyzer/dart/element/type.dart';
12+
import 'package:analyzer/src/dart/element/type_algebra.dart';
13+
import 'package:analyzer/src/dart/resolver/scope.dart';
14+
import 'package:analyzer/src/generated/resolver.dart';
15+
import 'package:analyzer/src/generated/type_system.dart';
16+
17+
import '../../../protocol_server.dart' show CompletionSuggestion;
18+
19+
/// A contributor for calculating suggestions based on the members of
20+
/// extensions.
21+
class ExtensionMemberContributor extends DartCompletionContributor {
22+
MemberSuggestionBuilder builder;
23+
24+
@override
25+
Future<List<CompletionSuggestion>> computeSuggestions(
26+
DartCompletionRequest request) async {
27+
LibraryElement containingLibrary = request.libraryElement;
28+
// Gracefully degrade if the library element is not resolved
29+
// e.g. detached part file or source change
30+
if (containingLibrary == null) {
31+
return const <CompletionSuggestion>[];
32+
}
33+
34+
// Recompute the target since resolution may have changed it.
35+
Expression expression = request.dotTarget;
36+
if (expression == null || expression.isSynthetic) {
37+
return const <CompletionSuggestion>[];
38+
}
39+
if (expression is Identifier) {
40+
Element elem = expression.staticElement;
41+
if (elem is ClassElement) {
42+
// Suggestions provided by StaticMemberContributor
43+
return const <CompletionSuggestion>[];
44+
}
45+
if (elem is PrefixElement) {
46+
// Suggestions provided by LibraryMemberContributor
47+
return const <CompletionSuggestion>[];
48+
}
49+
}
50+
builder = MemberSuggestionBuilder(containingLibrary);
51+
if (expression is ExtensionOverride) {
52+
_addInstanceMembers(expression.staticElement);
53+
} else {
54+
var type = expression.staticType;
55+
LibraryScope nameScope = new LibraryScope(containingLibrary);
56+
for (var extension in nameScope.extensions) {
57+
var typeSystem = containingLibrary.context.typeSystem;
58+
var typeProvider = containingLibrary.context.typeProvider;
59+
var extendedType =
60+
_resolveExtendedType(typeSystem, typeProvider, extension, type);
61+
if (typeSystem.isSubtypeOf(type, extendedType)) {
62+
// TODO(brianwilkerson) We might want to apply the substitution to the
63+
// members of the extension for display purposes.
64+
_addInstanceMembers(extension);
65+
}
66+
}
67+
expression.staticType;
68+
}
69+
return builder.suggestions.toList();
70+
}
71+
72+
void _addInstanceMembers(ExtensionElement extension) {
73+
for (MethodElement method in extension.methods) {
74+
if (!method.isStatic) {
75+
builder.addSuggestion(method);
76+
}
77+
}
78+
for (PropertyAccessorElement accessor in extension.accessors) {
79+
if (!accessor.isStatic) {
80+
builder.addSuggestion(accessor);
81+
}
82+
}
83+
}
84+
85+
/// Use the [typeProvider], [typeSystem] and the [type] of the object being
86+
/// extended to compute the actual type extended by the [extension]. Return
87+
/// the computed type, or `null` if the type cannot be computed.
88+
DartType _resolveExtendedType(TypeSystem typeSystem,
89+
TypeProvider typeProvider, ExtensionElement extension, DartType type) {
90+
var typeParameters = extension.typeParameters;
91+
var inferrer = GenericInferrer(
92+
typeProvider,
93+
typeSystem,
94+
typeParameters,
95+
);
96+
inferrer.constrainArgument(
97+
type,
98+
extension.extendedType,
99+
'extendedType',
100+
);
101+
var typeArguments = inferrer.infer(typeParameters, failAtError: true);
102+
if (typeArguments == null) {
103+
return null;
104+
}
105+
var substitution = Substitution.fromPairs(
106+
typeParameters,
107+
typeArguments,
108+
);
109+
return substitution.substituteType(
110+
extension.extendedType,
111+
);
112+
}
113+
}

pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:collection';
6+
57
import 'package:analysis_server/src/protocol_server.dart' as protocol;
68
import 'package:analysis_server/src/protocol_server.dart'
79
hide Element, ElementKind;
@@ -12,6 +14,8 @@ import 'package:analyzer/dart/element/type.dart';
1214
import 'package:analyzer/dart/element/visitor.dart';
1315
import 'package:analyzer/src/util/comment.dart';
1416

17+
import '../../../protocol_server.dart' show CompletionSuggestion;
18+
1519
/**
1620
* Return a suggestion based upon the given element or `null` if a suggestion
1721
* is not appropriate for the given element.
@@ -238,3 +242,124 @@ class LibraryElementSuggestionBuilder extends SimpleElementVisitor
238242
}
239243
}
240244
}
245+
246+
/**
247+
* This class provides suggestions based upon the visible instance members in
248+
* an interface type.
249+
*/
250+
class MemberSuggestionBuilder {
251+
/**
252+
* Enumerated value indicating that we have not generated any completions for
253+
* a given identifier yet.
254+
*/
255+
static const int _COMPLETION_TYPE_NONE = 0;
256+
257+
/**
258+
* Enumerated value indicating that we have generated a completion for a
259+
* getter.
260+
*/
261+
static const int _COMPLETION_TYPE_GETTER = 1;
262+
263+
/**
264+
* Enumerated value indicating that we have generated a completion for a
265+
* setter.
266+
*/
267+
static const int _COMPLETION_TYPE_SETTER = 2;
268+
269+
/**
270+
* Enumerated value indicating that we have generated a completion for a
271+
* field, a method, or a getter/setter pair.
272+
*/
273+
static const int _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET = 3;
274+
275+
/**
276+
* The library containing the unit in which the completion is requested.
277+
*/
278+
final LibraryElement containingLibrary;
279+
280+
/**
281+
* Map indicating, for each possible completion identifier, whether we have
282+
* already generated completions for a getter, setter, or both. The "both"
283+
* case also handles the case where have generated a completion for a method
284+
* or a field.
285+
*
286+
* Note: the enumerated values stored in this map are intended to be bitwise
287+
* compared.
288+
*/
289+
final Map<String, int> _completionTypesGenerated = new HashMap<String, int>();
290+
291+
/**
292+
* Map from completion identifier to completion suggestion
293+
*/
294+
final Map<String, CompletionSuggestion> _suggestionMap =
295+
<String, CompletionSuggestion>{};
296+
297+
MemberSuggestionBuilder(this.containingLibrary);
298+
299+
Iterable<CompletionSuggestion> get suggestions => _suggestionMap.values;
300+
301+
/**
302+
* Add a suggestion based upon the given element, provided that it is not
303+
* shadowed by a previously added suggestion.
304+
*/
305+
void addSuggestion(Element element,
306+
{int relevance = DART_RELEVANCE_DEFAULT}) {
307+
if (element.isPrivate) {
308+
if (element.library != containingLibrary) {
309+
// Do not suggest private members for imported libraries
310+
return;
311+
}
312+
}
313+
String identifier = element.displayName;
314+
315+
if (relevance == DART_RELEVANCE_DEFAULT && identifier != null) {
316+
// Decrease relevance of suggestions starting with $
317+
// https://github.com/dart-lang/sdk/issues/27303
318+
if (identifier.startsWith(r'$')) {
319+
relevance = DART_RELEVANCE_LOW;
320+
}
321+
}
322+
323+
int alreadyGenerated = _completionTypesGenerated.putIfAbsent(
324+
identifier, () => _COMPLETION_TYPE_NONE);
325+
if (element is MethodElement) {
326+
// Anything shadows a method.
327+
if (alreadyGenerated != _COMPLETION_TYPE_NONE) {
328+
return;
329+
}
330+
_completionTypesGenerated[identifier] =
331+
_COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET;
332+
} else if (element is PropertyAccessorElement) {
333+
if (element.isGetter) {
334+
// Getters, fields, and methods shadow a getter.
335+
if ((alreadyGenerated & _COMPLETION_TYPE_GETTER) != 0) {
336+
return;
337+
}
338+
_completionTypesGenerated[identifier] |= _COMPLETION_TYPE_GETTER;
339+
} else {
340+
// Setters, fields, and methods shadow a setter.
341+
if ((alreadyGenerated & _COMPLETION_TYPE_SETTER) != 0) {
342+
return;
343+
}
344+
_completionTypesGenerated[identifier] |= _COMPLETION_TYPE_SETTER;
345+
}
346+
} else if (element is FieldElement) {
347+
// Fields and methods shadow a field. A getter/setter pair shadows a
348+
// field, but a getter or setter by itself doesn't.
349+
if (alreadyGenerated == _COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET) {
350+
return;
351+
}
352+
_completionTypesGenerated[identifier] =
353+
_COMPLETION_TYPE_FIELD_OR_METHOD_OR_GETSET;
354+
} else {
355+
// Unexpected element type; skip it.
356+
assert(false);
357+
return;
358+
}
359+
CompletionSuggestion suggestion =
360+
createSuggestion(element, relevance: relevance);
361+
if (suggestion != null) {
362+
_suggestionMap[suggestion.completion] = suggestion;
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)