[DAS] Fixes semantic token for `FunctionType.call`
Bug: https://github.com/dart-lang/sdk/issues/61319
Change-Id: I4b42c410ff8e901da546d71658e37a5da6a410aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/451620
Auto-Submit: Felipe Morschel <git@fmorschel.dev>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/computer/computer_highlights.dart b/pkg/analysis_server/lib/src/computer/computer_highlights.dart
index 5e2f93d..bd7bdcb 100644
--- a/pkg/analysis_server/lib/src/computer/computer_highlights.dart
+++ b/pkg/analysis_server/lib/src/computer/computer_highlights.dart
@@ -19,7 +19,6 @@
show SemanticTokenInfo;
import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart'
show highlightRegionTokenModifiers, highlightRegionTokenTypes;
-import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -30,6 +29,7 @@
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
+import 'package:collection/collection.dart';
/// A computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a Dart [CompilationUnit].
class DartUnitHighlightsComputer {
@@ -674,16 +674,42 @@
/// Returns whether [nameToken] is a reference to the `call` method on
/// a function.
bool _isCallMethod(AstNode parent, Token nameToken) {
+ late bool enclosingInstanceFunction =
+ switch (parent.enclosingInstanceElement) {
+ ExtensionElement(:var extendedType) => extendedType.isFunction,
+ _ => false,
+ };
+ Expression? expression() => switch (parent) {
+ ExpressionFunctionBody(:var expression) ||
+ ExpressionStatement(:var expression) => expression,
+ ReturnStatement(:var expression) => expression,
+ ArgumentList(:var arguments) => arguments.firstWhereOrNull((argument) {
+ return argument is SimpleIdentifier && argument.token == nameToken;
+ }),
+ AssignmentExpression(:var rightHandSide) => rightHandSide,
+ VariableDeclaration(:var initializer) => initializer,
+ _ => null,
+ };
return // Invocation
- parent is MethodInvocation &&
+ (parent is MethodInvocation &&
parent.methodName.token == nameToken &&
parent.methodName.name == MethodElement.CALL_METHOD_NAME &&
- parent.realTarget?.staticType is FunctionType ||
+ ((parent.realTarget?.staticType).isFunction ||
+ enclosingInstanceFunction)) ||
// Tearoff
(parent is PrefixedIdentifier &&
parent.identifier.token == nameToken &&
parent.identifier.name == MethodElement.CALL_METHOD_NAME &&
- parent.prefix.staticType is FunctionType);
+ parent.prefix.staticType.isFunction) ||
+ // Property access
+ (parent is PropertyAccess &&
+ parent.propertyName.token == nameToken &&
+ parent.propertyName.name == MethodElement.CALL_METHOD_NAME &&
+ parent.realTarget.staticType.isFunction) ||
+ // Special cases for extension methods
+ (expression() is SimpleIdentifier &&
+ nameToken.lexeme == MethodElement.CALL_METHOD_NAME &&
+ enclosingInstanceFunction);
}
void _reset() {
@@ -2084,3 +2110,8 @@
(false, false, false) => Quote.Double,
};
}
+
+extension on DartType? {
+ bool get isFunction =>
+ this is FunctionType || (this?.isDartCoreFunction ?? false);
+}
diff --git a/pkg/analysis_server/test/lsp/semantic_tokens_test.dart b/pkg/analysis_server/test/lsp/semantic_tokens_test.dart
index 6aee1a5..943b097 100644
--- a/pkg/analysis_server/test/lsp/semantic_tokens_test.dart
+++ b/pkg/analysis_server/test/lsp/semantic_tokens_test.dart
@@ -1213,6 +1213,148 @@
await _initializeAndVerifyTokensInRange(content, expected);
}
+ Future<void> test_function_callMethod_invocation_extension() async {
+ var content = r'''
+extension on void Function() {
+ m() => [!call()!];
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_function_callMethod_propertyAccess() async {
+ var content = r'''
+extension on void Function()? {
+ m() => [!this?.call!];
+}
+''';
+
+ var expected = <_Token>[
+ _Token('this', SemanticTokenTypes.keyword),
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_function_callMethod_simpleIdentifier() async {
+ var content = r'''
+extension on void Function() {
+ m() {
+ [!call!];
+ }
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_function_callMethod_simpleIdentifier_argument() async {
+ var content = r'''
+extension on void Function() {
+ m(void Function() f) {
+ m([!call!]);
+ }
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_function_callMethod_simpleIdentifier_assignment() async {
+ var content = r'''
+extension on void Function() {
+ m() {
+ var a;
+ a = [!call!];
+ }
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void>
+ test_function_callMethod_simpleIdentifier_expressionFunctionBody() async {
+ var content = r'''
+extension on void Function() {
+ m() => [!call!];
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_function_callMethod_simpleIdentifier_return() async {
+ var content = r'''
+extension on void Function() {
+ m() {
+ return [!call!];
+ }
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void>
+ test_function_callMethod_simpleIdentifier_variableDeclaration() async {
+ var content = r'''
+extension on void Function() {
+ m() {
+ var _ = [!call!];
+ }
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
Future<void> test_function_callMethod_tearOff() async {
var content = r'''
f(void Function(int) x) {
@@ -1230,6 +1372,39 @@
await _initializeAndVerifyTokensInRange(content, expected);
}
+ Future<void> test_functionType_callMethod_invocation_extension() async {
+ var content = r'''
+extension on Function {
+ m() => [!call()!];
+}
+''';
+
+ var expected = <_Token>[
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
+ Future<void> test_functionType_callMethod_tearOff() async {
+ var content = r'''
+f(Function x) {
+ [!x.call!];
+}
+''';
+
+ var expected = [
+ _Token('x', SemanticTokenTypes.parameter),
+ _Token('call', SemanticTokenTypes.method, [
+ CustomSemanticTokenModifiers.instance,
+ ]),
+ ];
+
+ await _initializeAndVerifyTokensInRange(content, expected);
+ }
+
/// Verify that sending a semantic token request immediately after an overlay
/// update (with no delay) does not result in corrupt semantic tokens because
/// the previous file content was used.