Extension type. Updates and tests for search.

Change-Id: I17c5c9eb5237ba6f090cab974d4419e46b584ac8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323504
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-08-31 16:02:18 +00:00 committed by Commit Queue
parent d4261d12ff
commit 702cca5a18
11 changed files with 271 additions and 13 deletions

View file

@ -1370,6 +1370,21 @@ void f() {
assertHasResult(SearchResultKind.REFERENCE, 'int b');
}
Future<void> test_typeReference_extensionType() async {
addTestFile('''
extension type A(int it) {}
extension type B(int it) implements A {}
void f(A a) {}
''');
await findElementReferences(search: 'A(int it)', false);
expect(searchElement!.kind, ElementKind.EXTENSION_TYPE);
expect(results, hasLength(2));
assertHasResult(SearchResultKind.REFERENCE, 'A {}');
assertHasResult(SearchResultKind.REFERENCE, 'A a) {}');
}
Future<void> test_typeReference_typeAlias_functionType() async {
addTestFile('''
typedef F = Function();

View file

@ -224,6 +224,72 @@ enum B {
assertHasDeclaration(ElementKind.SETTER, 'B');
}
Future<void> test_extensionType_methodGetter() async {
addTestFile('''
extension type A(int it) {
void foo() {}
void bar() {}
}
extension type B(int it) {
int get foo => 0;
}
''');
await findMemberDeclarations('foo');
expect(results, hasLength(2));
assertHasDeclaration(ElementKind.METHOD, 'A');
assertHasDeclaration(ElementKind.GETTER, 'B');
}
Future<void> test_extensionType_methodGetterSetter() async {
addTestFile('''
extension type A(int it) {
void foo() {}
void bar() {}
}
extension type B(int it) {
int get foo => 0;
set foo(int _) {}
}
''');
await findMemberDeclarations('foo');
expect(results, hasLength(3));
assertHasDeclaration(ElementKind.METHOD, 'A');
assertHasDeclaration(ElementKind.GETTER, 'B');
assertHasDeclaration(ElementKind.SETTER, 'B');
}
Future<void> test_extensionType_methodMethod() async {
addTestFile('''
extension type A(int it) {
void foo() {}
void bar() {}
}
extension type B(int it) {
void foo() {}
}
''');
await findMemberDeclarations('foo');
expect(results, hasLength(2));
assertHasDeclaration(ElementKind.METHOD, 'A');
assertHasDeclaration(ElementKind.METHOD, 'B');
}
Future<void> test_extensionType_methodSetter() async {
addTestFile('''
extension type A(int it) {
void foo() {}
void bar() {}
}
extension type B(int it) {
set foo(int _) {}
}
''');
await findMemberDeclarations('foo');
expect(results, hasLength(2));
assertHasDeclaration(ElementKind.METHOD, 'A');
assertHasDeclaration(ElementKind.SETTER, 'B');
}
Future<void> test_localVariable() async {
addTestFile('''
class A {

View file

@ -201,6 +201,60 @@ void whenResolved(A a, B b) {
b.foo(2);
}
void whenUnresolved(a, b) {
a.foo(10);
b.foo(20);
}
''');
await findMemberReferences('foo');
assertNoResult(SearchResultKind.INVOCATION, 'foo(1)');
assertNoResult(SearchResultKind.INVOCATION, 'foo(2)');
assertHasRef(SearchResultKind.INVOCATION, 'foo(10)', true);
assertHasRef(SearchResultKind.INVOCATION, 'foo(20)', true);
}
Future<void> test_extensionType_fields_implicit() async {
addTestFile('''
extension type A(int it) {
int get foo => 0;
}
extension type B(int it) {
int get foo => 0;
}
void whenResolved(A a, B b) {
a.foo; // resolved A
b.foo; // resolved B
}
void whenUnresolved(a, b) {
a.foo; // unresolved A
b.foo; // unresolved B
}
''');
await findMemberReferences('foo');
assertNoResult(SearchResultKind.READ, 'foo; // resolved A');
assertNoResult(SearchResultKind.READ, 'foo; // resolved B');
assertHasRef(SearchResultKind.READ, 'foo; // unresolved A', true);
assertHasRef(SearchResultKind.READ, 'foo; // unresolved B', true);
}
Future<void> test_extensionType_methods() async {
addTestFile('''
extension type A(int it) {
void foo() {}
}
extension type B(int it) {
void foo() {}
}
void whenResolved(A a, B b) {
a.foo(1);
b.foo(2);
}
void whenUnresolved(a, b) {
a.foo(10);
b.foo(20);

View file

@ -87,6 +87,14 @@ extension MyExtension on int {}
assertHasDeclaration(ElementKind.EXTENSION, 'MyExtension');
}
Future<void> test_extensionTypeDeclaration() async {
addTestFile('''
extension type MyExtensionType(int it) {}
''');
await findTopLevelDeclarations('My*');
assertHasDeclaration(ElementKind.EXTENSION_TYPE, 'MyExtensionType');
}
Future<void> test_invalidRegex() async {
var result = await findTopLevelDeclarations('[A');
expect(result, const TypeMatcher<RequestError>());

View file

@ -242,6 +242,23 @@ class C extends B {}
_assertContainsClass(subtypes, 'C');
}
Future<void> test_searchAllSubtypes_extensionType() async {
await resolveTestCode('''
class A {}
extension type B(int it) implements A {}
extension type C(int it) implements A {}
''');
var element = findElement.class_('A');
var subtypes = <InterfaceElement>{};
await searchEngine.appendAllSubtypes(
element, subtypes, OperationPerformanceImpl("<root>"));
expect(subtypes, hasLength(2));
_assertContainsClass(subtypes, 'B');
_assertContainsClass(subtypes, 'C');
}
Future<void> test_searchAllSubtypes_mixin() async {
await resolveTestCode('''
class T {}
@ -444,6 +461,29 @@ enum E {
);
}
Future<void> test_searchReferences_extensionType() async {
final code = '''
extension type A(int it) {}
void f(A a) {}
''';
await resolveTestCode(code);
final element = findElement.extensionType('A');
final matches = await searchEngine.searchReferences(element);
expect(
matches,
unorderedEquals([
predicate((SearchMatch m) {
return m.kind == MatchKind.REFERENCE &&
identical(m.element, findElement.parameter('a')) &&
m.sourceRange.offset == code.indexOf('A a) {}') &&
m.sourceRange.length == 'A'.length;
}),
]),
);
}
Future<void>
test_searchReferences_parameter_ofConstructor_super_named() async {
var code = '''

View file

@ -38,6 +38,9 @@ DefinedNames computeDefinedNames(CompilationUnit unit) {
}
member.members.forEach(appendClassMemberName);
}
if (member is ExtensionTypeDeclaration) {
member.members.forEach(appendClassMemberName);
}
if (member is MixinDeclaration) {
member.members.forEach(appendClassMemberName);
}

View file

@ -88,7 +88,7 @@ import 'package:analyzer/src/utilities/uri_cache.dart';
/// TODO(scheglov) Clean up the list of implicitly analyzed files.
class AnalysisDriver implements AnalysisDriverGeneric {
/// The version of data format, should be incremented on every format change.
static const int DATA_VERSION = 299;
static const int DATA_VERSION = 300;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.

View file

@ -96,6 +96,20 @@ class _LocalNameScope {
return scope;
}
factory _LocalNameScope.forExtensionType(
_LocalNameScope enclosing, ExtensionTypeDeclaration node) {
final scope = _LocalNameScope(enclosing);
scope.addTypeParameters(node.typeParameters);
for (final member in node.members) {
if (member is FieldDeclaration) {
scope.addVariableNames(member.fields);
} else if (member is MethodDeclaration) {
scope.add(member.name);
}
}
return scope;
}
factory _LocalNameScope.forFunction(
_LocalNameScope enclosing, FunctionDeclaration node) {
_LocalNameScope scope = _LocalNameScope(enclosing);
@ -231,6 +245,17 @@ class _ReferencedNamesComputer extends GeneralizingAstVisitor<void> {
}
}
@override
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
final outerScope = localScope;
try {
localScope = _LocalNameScope.forExtensionType(localScope, node);
super.visitExtensionTypeDeclaration(node);
} finally {
localScope = outerScope;
}
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_LocalNameScope outerScope = localScope;

View file

@ -306,6 +306,7 @@ class Search {
if (unitResult is UnitElementResult) {
unitResult.element.classes.forEach(addElements);
unitResult.element.enums.forEach(addElements);
unitResult.element.extensionTypes.forEach(addElements);
unitResult.element.mixins.forEach(addElements);
}
}
@ -462,6 +463,7 @@ class Search {
unitElement.classes.forEach(addElement);
unitElement.enums.forEach(addElement);
unitElement.extensions.forEach(addElement);
unitElement.extensionTypes.forEach(addElement);
unitElement.functions.forEach(addElement);
unitElement.mixins.forEach(addElement);
unitElement.topLevelVariables.forEach(addElement);

View file

@ -2,12 +2,13 @@
// 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/src/dart/analysis/defined_names.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/parser_test_base.dart';
import '../../../util/feature_sets.dart';
main() {
defineReflectiveSuite(() {
@ -16,7 +17,7 @@ main() {
}
@reflectiveTest
class DefinedNamesTest extends ParserTestCase {
class DefinedNamesTest {
test_classMemberNames_class() {
DefinedNames names = _computeDefinedNames('''
class A {
@ -36,12 +37,29 @@ class B {
unorderedEquals(['a', 'b', 'd', 'e', 'f', 'g']));
}
test_classMemberNames_extensionType() {
DefinedNames names = _computeDefinedNames('''
extension type A.named(int it) {
int a, b;
A();
A.other();
void d() {}
int get e => 0;
set f(int _) {}
}
extension type B(int it) {
void g() {}
}
''');
expect(names.topLevelNames, unorderedEquals(['A', 'B']));
expect(names.classMemberNames,
unorderedEquals(['a', 'b', 'd', 'e', 'f', 'g']));
}
test_classMemberNames_mixin() {
DefinedNames names = _computeDefinedNames('''
mixin A {
int a, b;
A();
A.c();
d() {}
get e => null;
set f(_) {}
@ -59,7 +77,7 @@ mixin B {
DefinedNames names = _computeDefinedNames('''
class A {}
class B = Object with A;
typedef C {}
typedef C();
D() {}
get E => null;
set F(_) {}
@ -71,8 +89,14 @@ mixin M {}
expect(names.classMemberNames, isEmpty);
}
DefinedNames _computeDefinedNames(String code) {
CompilationUnit unit = parseCompilationUnit2(code);
return computeDefinedNames(unit);
DefinedNames _computeDefinedNames(
String code, {
FeatureSet? featureSet,
}) {
final parseResult = parseString(
content: code,
featureSet: featureSet ?? FeatureSets.latestWithExperiments,
);
return computeDefinedNames(parseResult.unit);
}
}

View file

@ -2,12 +2,15 @@
// 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:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dart/analysis/referenced_names.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/parser_test_base.dart';
import '../../../util/feature_sets.dart';
main() {
defineReflectiveSuite(() {
@ -17,7 +20,7 @@ main() {
}
@reflectiveTest
class ComputeReferencedNamesTest extends ParserTestCase {
class ComputeReferencedNamesTest {
test_class_constructor() {
Set<String> names = _computeReferencedNames('''
class U {
@ -174,6 +177,17 @@ class U<T> {
expect(names, unorderedEquals(['A']));
}
test_extensionType_typeParameters() {
Set<String> names = _computeReferencedNames('''
extension type Z<T>(int it) {
A m(B b, T t, Z z) {
C c = 0;
}
}
''');
expect(names, unorderedEquals(['int', 'A', 'B', 'C']));
}
test_instantiatedNames_importPrefix() {
Set<String> names = _computeReferencedNames('''
import 'a.dart' as p1;
@ -405,8 +419,15 @@ main() {
expect(names, unorderedEquals(['A', 'B', 'C']));
}
Set<String> _computeReferencedNames(String code) {
CompilationUnit unit = parseCompilationUnit2(code);
Set<String> _computeReferencedNames(
String code, {
FeatureSet? featureSet,
}) {
final parseResult = parseString(
content: code,
featureSet: featureSet ?? FeatureSets.latestWithExperiments,
);
final unit = parseResult.unit;
return computeReferencedNames(unit);
}
}