f8d6ad7e4b
We shouldn't treat parameter names inside function types as if they are references to other parameters/variables of that name. Fixes https://github.com/dart-lang/sdk/issues/61099 Change-Id: Iadc97723a87af1ddbbde618d1c9c8e5b51499a6f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/440160 Commit-Queue: Keerti Parthasarathy <keertip@google.com> Reviewed-by: Keerti Parthasarathy <keertip@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
560 lines
12 KiB
Dart
560 lines
12 KiB
Dart
// Copyright (c) 2025, 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 'package:analysis_server/lsp_protocol/protocol.dart';
|
|
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
|
import 'package:collection/collection.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../utils/test_code_extensions.dart';
|
|
import 'server_abstract.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(InlineValueTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class InlineValueTest extends AbstractLspAnalysisServerTest {
|
|
late TestCode code;
|
|
|
|
/// Whether to enable the inlineValuesProperties experiment flag in the
|
|
/// client configuration passed during initialization.
|
|
bool experimentalInlineValuesProperties = false;
|
|
|
|
Future<void> test_block_ifStatement_inside() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int a, int b, int c) {
|
|
if (a == 1) {
|
|
/*[0*/a/*0]*/;
|
|
/*[1*/b/*1]*/;
|
|
/*[2*/c/*2]*/;
|
|
^
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_block_ifStatement_notInside() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int a, int /*[0*/b/*0]*/, int c) {
|
|
if (/*[1*/a/*1]*/ == 1) {
|
|
// Code inside blocks is excluded
|
|
a;
|
|
b;
|
|
c;
|
|
}
|
|
^/*[2*/c/*2]*/;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_block_switchStatement() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int a, int /*[0*/b/*0]*/, int c) {
|
|
switch (/*[1*/a/*1]*/) {
|
|
case 0:
|
|
// Ignored because not in this case
|
|
a;
|
|
b;
|
|
c;
|
|
case 1:
|
|
^/*[2*/c/*2]*/;
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_iterables() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
// There are no marked ranges, because none of these should produce values.
|
|
code = TestCode.parse(r'''
|
|
import 'dart:async';
|
|
|
|
void f(
|
|
Iterable<int> p1,
|
|
Future<int> p2,
|
|
FutureOr<int> p3,
|
|
Stream<int> p4,
|
|
) {
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code);
|
|
}
|
|
|
|
Future<void> test_parameter_declaration() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int /*[0*/aaa/*0]*/, int /*[1*/bbb/*1]*/) {
|
|
^
|
|
aaa + bbb;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
/// Lists are included, iterables are not.
|
|
Future<void> test_parameter_iterables() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
void f(List list1, List<int> /*[0*/list2/*0]*/, Iterable iterable1, Iterable iterable2) {
|
|
print(/*[1*/list1/*1]*/);
|
|
print(iterable1);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_parameter_nestedInFunctionType() async {
|
|
// A parameter nested inside a function type should not produce a value.
|
|
code = TestCode.parse(r'''
|
|
void f(
|
|
int /*[0*/value/*0]*/, {
|
|
required void Function(int value) /*[1*/func/*1]*/,
|
|
}) {
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_parameter_read() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int aaa, int bbb) {
|
|
^/*[0*/aaa/*0]*/ + /*[1*/bbb/*1]*/;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_parameter_write() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int aaa, int bbb, int ccc) {
|
|
/*[0*/aaa/*0]*/++;
|
|
/*[1*/bbb/*1]*/ = 1;
|
|
/*[2*/ccc/*2]*/ += 1;
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_parameters_scope() async {
|
|
// We should only get the parameters declared in the current function.
|
|
code = TestCode.parse(r'''
|
|
void f(int aaa, int bbb, int ccc) {
|
|
void b(int /*[0*/aaa/*0]*/, int /*[1*/bbb/*1]*/) {
|
|
|
|
var _ = (aaa, bbb, ccc) => null;
|
|
var _ = () {
|
|
var aaa = 1, bbb = 1, ccc = 1;
|
|
};
|
|
|
|
^
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_property_experimentDisabled() async {
|
|
code = TestCode.parse(r'''
|
|
void f(String /*[0*/s/*0]*/) {
|
|
print(s.length); // No inline value, experiment not enabled
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(
|
|
code,
|
|
// The parameter is a variable even though this test is about properties.
|
|
ofType: InlineValueVariableLookup,
|
|
);
|
|
}
|
|
|
|
Future<void> test_property_getter() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
void f(String /*[0*/s/*0]*/) {
|
|
print(/*[1*/s.length/*1]*/);
|
|
print(/*[2*/s.length.isEven/*2]*/);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(
|
|
code,
|
|
ofTypes: {
|
|
0: InlineValueVariableLookup,
|
|
1: InlineValueEvaluatableExpression,
|
|
2: InlineValueEvaluatableExpression,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_property_getter_enum_value_excluded() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
enum MyEnum {
|
|
one,
|
|
}
|
|
|
|
void f(MyEnum x) {
|
|
print(/*[0*/x/*0]*/ == MyEnum.one); // MyEnum.one excluded
|
|
print(/*[1*/MyEnum.one.index/*1]*/);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(
|
|
code,
|
|
ofTypes: {
|
|
0: InlineValueVariableLookup,
|
|
1: InlineValueEvaluatableExpression,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_property_getter_enum_values_excluded() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
enum MyEnum {
|
|
one,
|
|
}
|
|
|
|
void f() {
|
|
print(MyEnum.values); // MyEnum.values excluded
|
|
print(/*[0*/MyEnum.values.length/*0]*/);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueEvaluatableExpression);
|
|
}
|
|
|
|
/// Lists are included, iterables are not.
|
|
Future<void> test_property_iterables() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
void f(List<int> /*[0*/list/*0]*/, Iterable<int> iterable) {
|
|
print(/*[1*/list.length/*1]*/);
|
|
print(iterable.length);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(
|
|
code,
|
|
ofTypes: {
|
|
0: InlineValueVariableLookup,
|
|
1: InlineValueEvaluatableExpression,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_property_method() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
class A {
|
|
void x() {}
|
|
}
|
|
void f(A /*[0*/a/*0]*/) {
|
|
a.x(); // No value for methods.
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_property_method_targets() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
class A {
|
|
String x(int a) => a.toString();
|
|
}
|
|
void f(A /*[0*/a/*0]*/, int b) {
|
|
// No value for length because the expression contains a method call.
|
|
a.x(b).length;
|
|
|
|
// No value for either length of isEven because the expression contains a
|
|
// method call.
|
|
a.x(/*[1*/b/*1]*/).length.isEven;
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
/// Unlike variables, which we include for the line of the execution pointer
|
|
/// (to aid with reviewing conditional statements), getters are only evaluated
|
|
/// if they are before the execution pointer to reduce the chance of
|
|
/// triggering side-effects before the code would have.
|
|
Future<void> test_property_range_onlyBeforePointer() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
void f(String /*[0*/s/*0]*/) {
|
|
^if (s.isNotEmpty) {
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_property_setter() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
class A {
|
|
int? x;
|
|
}
|
|
void f(A /*[0*/a/*0]*/) {
|
|
a.x = 1; // No value for setters.
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_scope_method_inNestedFunction() async {
|
|
code = TestCode.parse(r'''
|
|
class A {
|
|
void method() {
|
|
void inner() {
|
|
var [!valueVar!] = 1;
|
|
^
|
|
}
|
|
var noValueVar = 1;
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_scope_method_notInNestedFunction() async {
|
|
code = TestCode.parse(r'''
|
|
class A {
|
|
void method() {
|
|
void inner() {
|
|
var noValueVar = 1;
|
|
}
|
|
var [!valueVar!] = 1;
|
|
^
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_scope_topLevelFunction_inNestedFunction() async {
|
|
code = TestCode.parse(r'''
|
|
void top() {
|
|
void inner() {
|
|
var [!valueVar!] = 1;
|
|
^
|
|
}
|
|
var noValueVar = 1;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_scope_topLevelFunction_notInNestedFunction() async {
|
|
code = TestCode.parse(r'''
|
|
void top() {
|
|
void inner() {
|
|
var noValueVar = 1;
|
|
}
|
|
var [!valueVar!] = 1;
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_declaration() async {
|
|
code = TestCode.parse(r'''
|
|
void f() {
|
|
int /*[0*/aaa/*0]*/ = 1;
|
|
int /*[1*/bbb/*1]*/ = 1, /*[2*/ccc/*2]*/ = 1;
|
|
^
|
|
aaa + bbb;
|
|
ccc;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_forIn() async {
|
|
code = TestCode.parse(r'''
|
|
void f(List<int> ints) {
|
|
for (var /*[0*/i/*0]*/ in /*[1*/ints/*1]*/) {
|
|
^
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_forIn_destructure() async {
|
|
code = TestCode.parse(r'''
|
|
void f(List<(int, int)> records) {
|
|
for (var (/*[0*/x/*0]*/, /*[1*/y/*1]*/) in /*[2*/records/*2]*/) {
|
|
^
|
|
}
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
/// Lists are included, iterables are not.
|
|
Future<void> test_variable_iterables() async {
|
|
experimentalInlineValuesProperties = true;
|
|
|
|
code = TestCode.parse(r'''
|
|
void f() {
|
|
var list = [1,];
|
|
var iterable = list as Iterable<int>;
|
|
|
|
print(/*[0*/list/*0]*/);
|
|
print(iterable);
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_propertyAccess() async {
|
|
code = TestCode.parse(r'''
|
|
void f(int /*[0*/aaa/*0]*/) {
|
|
aaa.isEven; // No inline value for var because it's in a property access.
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_read() async {
|
|
code = TestCode.parse(r'''
|
|
void f() {
|
|
int aaa = 1;
|
|
int bbb = 1, ccc = 1;
|
|
^/*[0*/aaa/*0]*/ + /*[1*/bbb/*1]*/ + /*[2*/ccc/*2]*/;
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variable_write() async {
|
|
code = TestCode.parse(r'''
|
|
void f() {
|
|
int aaa = 0, bbb = 0, ccc = 0;
|
|
/*[0*/aaa/*0]*/ = 1;
|
|
/*[1*/bbb/*1]*/++;
|
|
/*[2*/ccc/*2]*/ += 1;
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
Future<void> test_variables_scope() async {
|
|
// We should not get the top-levels or the nested functions.
|
|
code = TestCode.parse(r'''
|
|
var aaa = 1;
|
|
var bbb = 1;
|
|
|
|
void f() {
|
|
int /*[0*/aaa/*0]*/ = 0, /*[1*/ccc/*1]*/ = 0;
|
|
|
|
var _ = (aaa, bbb, ccc) => null;
|
|
var _ = () {
|
|
var aaa = 1, bbb = 1, ccc = 1;
|
|
};
|
|
|
|
^
|
|
}
|
|
''');
|
|
|
|
await verify_values(code, ofType: InlineValueVariableLookup);
|
|
}
|
|
|
|
/// Verifies [code] produces values at the marked ranges.
|
|
///
|
|
/// The [ofTypes] contains the kind of value to be expected for the range with
|
|
/// the same index. If a range is not included in [ofTypes] then [ofType] is
|
|
/// used instead.
|
|
Future<void> verify_values(
|
|
TestCode code, {
|
|
Type? ofType,
|
|
Map<int, Type>? ofTypes,
|
|
}) async {
|
|
await provideConfig(initialize, {
|
|
if (experimentalInlineValuesProperties)
|
|
'experimentalInlineValuesProperties': true,
|
|
});
|
|
await openFile(mainFileUri, code.code);
|
|
await initialAnalysis;
|
|
|
|
var actualValues = await getInlineValues(
|
|
mainFileUri,
|
|
visibleRange: rangeOfWholeContent(code.code),
|
|
stoppedAt: code.position.position,
|
|
);
|
|
|
|
var expectedValues = code.ranges.ranges.mapIndexed((index, range) {
|
|
return switch (ofTypes?[index] ?? ofType) {
|
|
const (InlineValueVariableLookup) => InlineValue.t3(
|
|
InlineValueVariableLookup(caseSensitiveLookup: true, range: range),
|
|
),
|
|
const (InlineValueEvaluatableExpression) => InlineValue.t1(
|
|
InlineValueEvaluatableExpression(range: range),
|
|
),
|
|
_ => throw 'No type provided for range $index',
|
|
};
|
|
});
|
|
|
|
expect(actualValues, unorderedEquals(expectedValues));
|
|
}
|
|
}
|