Skip to content

Commit 5b10311

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Fixes completion for core function and suggestions of true/false
Fixes: #40703 Change-Id: Ib4fe7e11c95c3d5c199241aded12a041631a6d88 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464580 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Auto-Submit: Felipe Morschel <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent cd3fa6e commit 5b10311

15 files changed

+416
-91
lines changed

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

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,11 @@ class DeclarationHelper {
17801780
/// Returns `true` if the [identifier] is a wildcard (a single `_`).
17811781
bool _isWildcard(String? identifier) => identifier == '_';
17821782

1783+
bool _matchesContextType(ExecutableElement element) {
1784+
return request.contextType == element.type ||
1785+
(request.contextType?.isDartCoreFunction ?? false);
1786+
}
1787+
17831788
/// Record that the given [operation] should be performed in the second pass.
17841789
void _recordOperation(NotImportedOperation operation) {
17851790
notImportedOperations.add(operation);
@@ -1857,6 +1862,10 @@ class DeclarationHelper {
18571862
return;
18581863
}
18591864

1865+
if (mustBeConstant && !element.isConst) {
1866+
return;
1867+
}
1868+
18601869
if (!element.isVisibleIn(request.libraryElement)) {
18611870
return;
18621871
}
@@ -1884,16 +1893,26 @@ class DeclarationHelper {
18841893
// TODO(keertip): Compute the completion string.
18851894
var matcherScore = state.matcher.score(matcherName);
18861895
if (matcherScore != -1) {
1887-
var isTearOff =
1888-
preferNonInvocation || (mustBeConstant && !element.isConst);
1896+
if (_matchesContextType(element) && !preferNonInvocation) {
1897+
var suggestion = ConstructorSuggestion(
1898+
importData: importData,
1899+
element: element,
1900+
hasClassName: hasClassName,
1901+
isTearOff: true,
1902+
isRedirect: isConstructorRedirect,
1903+
suggestUnnamedAsNew: true,
1904+
matcherScore: matcherScore,
1905+
);
1906+
collector.addSuggestion(suggestion);
1907+
}
18891908

18901909
var suggestion = ConstructorSuggestion(
18911910
importData: importData,
18921911
element: element,
18931912
hasClassName: hasClassName,
1894-
isTearOff: isTearOff,
1913+
isTearOff: preferNonInvocation,
18951914
isRedirect: isConstructorRedirect,
1896-
suggestUnnamedAsNew: suggestUnnamedAsNew || isTearOff,
1915+
suggestUnnamedAsNew: suggestUnnamedAsNew || preferNonInvocation,
18971916
matcherScore: matcherScore,
18981917
);
18991918
collector.addSuggestion(suggestion);
@@ -2068,6 +2087,14 @@ class DeclarationHelper {
20682087
if (_isWildcard(element.name)) return;
20692088
var matcherScore = state.matcher.score(element.displayName);
20702089
if (matcherScore != -1) {
2090+
if (_matchesContextType(element) && !preferNonInvocation) {
2091+
var suggestion = LocalFunctionSuggestion(
2092+
kind: CompletionSuggestionKind.IDENTIFIER,
2093+
element: element,
2094+
matcherScore: matcherScore,
2095+
);
2096+
collector.addSuggestion(suggestion);
2097+
}
20712098
var suggestion = LocalFunctionSuggestion(
20722099
kind: _executableSuggestionKind,
20732100
element: element,
@@ -2099,7 +2126,7 @@ class DeclarationHelper {
20992126
if (ignoreVisibility ||
21002127
visibilityTracker.isVisible(element: method, importData: importData)) {
21012128
if (mustBeAssignable ||
2102-
mustBeConstant ||
2129+
mustBeConstant && !method.isStatic ||
21032130
(mustBeNonVoid && method.returnType is VoidType)) {
21042131
return;
21052132
}
@@ -2118,6 +2145,21 @@ class DeclarationHelper {
21182145
if (method.name == 'setState' &&
21192146
enclosingElement is ClassElement &&
21202147
enclosingElement.isExactState) {
2148+
if (_matchesContextType(method) && !preferNonInvocation) {
2149+
var suggestion = SetStateMethodSuggestion(
2150+
kind: CompletionSuggestionKind.IDENTIFIER,
2151+
element: method,
2152+
replacementRange: state.request.replacementRange,
2153+
importData: importData,
2154+
referencingInterface: referencingInterface,
2155+
matcherScore: matcherScore,
2156+
indent: state.indent,
2157+
endOfLine: state.endOfLine,
2158+
addTypeAnnotation: addTypeAnnotation,
2159+
keyword: keyword,
2160+
);
2161+
collector.addSuggestion(suggestion);
2162+
}
21212163
var suggestion = SetStateMethodSuggestion(
21222164
element: method,
21232165
replacementRange: state.request.replacementRange,
@@ -2132,6 +2174,22 @@ class DeclarationHelper {
21322174
collector.addSuggestion(suggestion);
21332175
return;
21342176
}
2177+
if (_matchesContextType(method) && !preferNonInvocation) {
2178+
var suggestion = MethodSuggestion(
2179+
kind: CompletionSuggestionKind.IDENTIFIER,
2180+
replacementRange: state.request.replacementRange,
2181+
element: method,
2182+
importData: importData,
2183+
matcherScore: matcherScore,
2184+
referencingInterface: referencingInterface,
2185+
addTypeAnnotation: addTypeAnnotation,
2186+
keyword: keyword,
2187+
);
2188+
collector.addSuggestion(suggestion);
2189+
}
2190+
if (mustBeConstant && (method.isStatic || !preferNonInvocation)) {
2191+
return;
2192+
}
21352193
var suggestion = MethodSuggestion(
21362194
kind: _executableSuggestionKind,
21372195
replacementRange: state.request.replacementRange,
@@ -2405,13 +2463,24 @@ class DeclarationHelper {
24052463
) {
24062464
if (visibilityTracker.isVisible(element: element, importData: importData)) {
24072465
if (mustBeAssignable ||
2408-
mustBeConstant ||
24092466
(mustBeNonVoid && element.returnType is VoidType) ||
24102467
mustBeType) {
24112468
return;
24122469
}
24132470
var matcherScore = state.matcher.score(element.displayName);
24142471
if (matcherScore != -1) {
2472+
if (_matchesContextType(element) && !preferNonInvocation) {
2473+
var suggestion = TopLevelFunctionSuggestion(
2474+
kind: CompletionSuggestionKind.IDENTIFIER,
2475+
importData: importData,
2476+
element: element,
2477+
matcherScore: matcherScore,
2478+
);
2479+
collector.addSuggestion(suggestion);
2480+
}
2481+
if (!preferNonInvocation && mustBeConstant) {
2482+
return;
2483+
}
24152484
var suggestion = TopLevelFunctionSuggestion(
24162485
importData: importData,
24172486
element: element,

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

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import 'package:analyzer/source/source_range.dart';
2828
import 'package:analyzer/src/dart/ast/ast.dart';
2929
import 'package:analyzer/src/dart/ast/extensions.dart';
3030
import 'package:analyzer/src/dart/ast/token.dart';
31+
import 'package:analyzer/src/dart/element/type.dart';
32+
import 'package:analyzer/src/dart/element/type_provider.dart';
3133
import 'package:analyzer/src/utilities/extensions/ast.dart';
3234
import 'package:analyzer/src/utilities/extensions/flutter.dart';
3335

@@ -185,6 +187,9 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
185187
Set<AstNode> excludedNodes = const {},
186188
}) {
187189
var contextType = state.contextType;
190+
if (contextType?.isDartCoreFunction ?? false) {
191+
contextType = _voidFunctionNoParameters();
192+
}
188193
if (contextType is FunctionType) {
189194
// TODO(brianwilkerson): Consider passing the context type to the
190195
// declaration helper so that we can limit which functions are suggested
@@ -332,22 +337,28 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
332337
// the list of parameters.
333338
var parameter = parameters[positionalArgumentCount];
334339
var parameterType = parameter.type;
340+
while (parameterType is TypeParameterType) {
341+
parameterType = parameterType.bound;
342+
}
343+
if (parameterType.isDartCoreFunction) {
344+
parameterType = _voidFunctionNoParameters();
345+
}
335346
var isFunctionType = parameterType is FunctionType;
336347
_forExpression(
337348
parent,
338349
mustBeNonVoid: true,
339-
canBeNull:
340-
parameterType.nullabilitySuffix != NullabilitySuffix.none ||
341-
parameterType is DynamicType,
342-
// TODO(FMorschel): Determine if the expected type is bool and only
343-
// suggest `true` and `false` in that case.
344-
canBeBool: !isFunctionType,
350+
canBeNull: _canBeNull(parameterType),
351+
canBeBool: _canBeBool(parameterType),
345352
// TODO(FMorschel): Determine if the parameter type has a constant
346-
// constructor.
347-
// Function tear-offs and closures cannot be constant.
353+
// constructor.
354+
// Function tear-offs and closures cannot have the `const` keyword
355+
// before it
348356
canSuggestConst: !isFunctionType,
349357
);
350-
if (isFunctionType) {
358+
var expression = node.thisOrAncestorOfType<ExpressionImpl>();
359+
var isConstant =
360+
expression?.constantContext(includeSelf: true) != null;
361+
if (isFunctionType && !isConstant) {
351362
Expression? argument;
352363
if (argumentIndex < arguments.length) {
353364
argument = arguments[argumentIndex];
@@ -2166,9 +2177,28 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
21662177
if (inArgumentList) {
21672178
collector.completionLocation = 'ArgumentList_method_named';
21682179
}
2169-
_forExpression(node, mustBeNonVoid: inArgumentList);
2170-
var parameterType = node.element?.type;
2171-
if (parameterType is FunctionType) {
2180+
var parameterType = node.element?.type ?? DynamicTypeImpl.instance;
2181+
while (parameterType is TypeParameterType) {
2182+
parameterType = parameterType.bound;
2183+
}
2184+
if (parameterType.isDartCoreFunction) {
2185+
parameterType = _voidFunctionNoParameters();
2186+
}
2187+
var isFunctionType = parameterType is FunctionType;
2188+
_forExpression(
2189+
node,
2190+
mustBeNonVoid: inArgumentList,
2191+
canBeNull: _canBeNull(parameterType),
2192+
canBeBool: _canBeBool(parameterType),
2193+
// TODO(FMorschel): Determine if the parameter type has a constant
2194+
// constructor.
2195+
// Function tear-offs and closures cannot have the `const` keyword
2196+
// before it
2197+
canSuggestConst: !isFunctionType,
2198+
);
2199+
var expression = node.ifTypeOrNull<ExpressionImpl>();
2200+
var isConstant = expression?.constantContext(includeSelf: true) != null;
2201+
if (isFunctionType && !isConstant) {
21722202
var includeTrailingComma = !node.isFollowedByComma;
21732203
_addClosureSuggestion(parameterType, includeTrailingComma);
21742204
}
@@ -3317,7 +3347,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
33173347
collector.completionLocation = 'VariableDeclaration_initializer';
33183348
_forExpression(node, mustBeNonVoid: true);
33193349
var variableType = node.declaredFragment?.element.type;
3320-
if (variableType is FunctionType) {
3350+
if (variableType is FunctionType && !node.isConst) {
33213351
_addClosureSuggestion(variableType, false);
33223352
}
33233353
}
@@ -3446,6 +3476,18 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
34463476
);
34473477
}
34483478

3479+
bool _canBeBool(DartType parameterType) {
3480+
var typeProvider = state.libraryElement.typeProvider;
3481+
var typeSystem = state.libraryElement.typeSystem;
3482+
return typeSystem.isSubtypeOf(typeProvider.boolType, parameterType);
3483+
}
3484+
3485+
bool _canBeNull(DartType parameterType) {
3486+
return parameterType.nullabilitySuffix != NullabilitySuffix.none ||
3487+
parameterType.isDartCoreNull ||
3488+
parameterType is DynamicType;
3489+
}
3490+
34493491
/// Returns the context type in which [node] is analyzed.
34503492
DartType? _computeContextType(Expression node) {
34513493
return state.request.featureComputer.computeContextType(
@@ -3519,6 +3561,8 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
35193561
) {
35203562
var mustBeStatic = literal.inStaticContext;
35213563
var mustBeConst = literal.inConstantContext;
3564+
// TODO(FMorschel): Consider the type of the collection to further
3565+
// constrain the suggestions (like `null`).
35223566
keywordHelper.addCollectionElementKeywords(
35233567
literal,
35243568
elements,
@@ -3645,6 +3689,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
36453689
bool canBeBool = true,
36463690
bool canBeNull = true,
36473691
bool canSuggestConst = true,
3692+
bool preferNonInvocation = false,
36483693
}) {
36493694
var mustBeConstant =
36503695
node is Expression &&
@@ -3663,6 +3708,7 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
36633708
mustBeConstant: mustBeConstant,
36643709
mustBeNonVoid: mustBeNonVoid,
36653710
mustBeStatic: mustBeStatic,
3711+
preferNonInvocation: preferNonInvocation,
36663712
).addLexicalDeclarations(node);
36673713
}
36683714

@@ -4304,6 +4350,17 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
43044350
node.parent?.accept(this);
43054351
}
43064352
}
4353+
4354+
FunctionTypeImpl _voidFunctionNoParameters() {
4355+
var typeProvider = state.libraryElement.typeProvider
4356+
.ifTypeOrNull<TypeProviderImpl>();
4357+
return FunctionTypeImpl(
4358+
typeParameters: const [],
4359+
parameters: const [],
4360+
returnType: typeProvider?.voidType ?? DynamicTypeImpl.instance,
4361+
nullabilitySuffix: NullabilitySuffix.none,
4362+
);
4363+
}
43074364
}
43084365

43094366
extension on AstNode {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,10 +335,12 @@ class KeywordHelper {
335335
node.inAsyncStarOrSyncStarMethodOrFunction) {
336336
addKeyword(Keyword.AWAIT);
337337
}
338-
if (switchIsValid(node) && featureSet.isEnabled(Feature.patterns)) {
338+
if (!mustBeConstant &&
339+
switchIsValid(node) &&
340+
featureSet.isEnabled(Feature.patterns)) {
339341
addKeyword(Keyword.SWITCH);
340342
}
341-
} else if (featureSet.isEnabled(Feature.patterns)) {
343+
} else if (!mustBeConstant && featureSet.isEnabled(Feature.patterns)) {
342344
addKeyword(Keyword.SWITCH);
343345
}
344346
}

pkg/analysis_server/test/lsp/completion_dart_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,25 @@ class Foo {
732732
);
733733
}
734734

735+
Future<void> test_functionTearOff() async {
736+
content = '''
737+
void f(void Function(int) x) {
738+
f(^);
739+
}
740+
741+
void myFunction(int i) {}
742+
''';
743+
744+
await expectLabel(
745+
content,
746+
label: 'myFunction',
747+
labelDetail: ' (…) → void',
748+
labelDescription: null,
749+
filterText: null,
750+
detail: '(int i) → void',
751+
);
752+
}
753+
735754
Future<void> test_functionType_callMember_identifierQualified() async {
736755
content = '''
737756
extension on void Function(int i) {

pkg/analysis_server/test/services/completion/dart/completion_test.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4979,10 +4979,6 @@ class q {m(Map q){var x;m(^)}n(){var x;n()}}
49794979
suggestions
49804980
x
49814981
kind: localVariable
4982-
true
4983-
kind: keyword
4984-
false
4985-
kind: keyword
49864982
this
49874983
kind: keyword
49884984
const
@@ -7963,10 +7959,6 @@ suggestions
79637959
kind: constructorInvocation
79647960
A.second
79657961
kind: constructorInvocation
7966-
false
7967-
kind: keyword
7968-
true
7969-
kind: keyword
79707962
const
79717963
kind: keyword
79727964
switch

0 commit comments

Comments
 (0)