[macros] Add first language tests for introspection.

R=jakemac@google.com

Change-Id: Ic6e081a0a97f23c35b90bc3d935d3c97f4ec5bcd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/341963
Reviewed-by: Jake Macdonald <jakemac@google.com>
Auto-Submit: Morgan :) <davidmorgan@google.com>
Commit-Queue: Morgan :) <davidmorgan@google.com>
This commit is contained in:
David Morgan 2023-12-19 09:56:46 +00:00 committed by Commit Queue
parent 3eeba4a4e2
commit 12c548d331
7 changed files with 286 additions and 0 deletions

View file

@ -0,0 +1,8 @@
# Macro "Introspect" Tests
These tests (will) cover the "read" half of the macro API, the introspection
of source during macro execution.
The macros under `impl` accept an introspection target library and type name.
They do all the introspection possible, convert the results to primitives then
compare with expectations also passed as an argument to the macro.

View file

@ -0,0 +1,65 @@
// Copyright (c) 2023, 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.
// SharedOptions=--enable-experiment=macros
import 'impl/assert_in_declarations_phase_macro.dart';
import 'impl/assert_in_definitions_phase_macro.dart';
import 'impl/assert_in_types_phase_macro.dart';
@AssertInTypesPhase(
targetLibrary: 'dart:core',
targetName: 'int',
resolveIdentifier: 'int',
)
@AssertInDefinitionsPhase(
targetName: 'A',
constructorsOf: ['()', 'b()', 'c()'],
fieldsOf: ['int d', 'String e'],
methodsOf: ['int f()', 'String g()'],
)
@AssertInDeclarationsPhase(
targetName: 'A',
constructorsOf: ['()', 'b()', 'c()'],
fieldsOf: ['int d', 'String e'],
methodsOf: ['int f()', 'String g()'],
)
abstract class A {
A();
A.b();
A.c();
final int d = 1;
final String e = 'two';
int f();
String g();
}
abstract class B {
B();
B.h();
B.i();
final int j = 1;
final String k = 'two';
int l();
String m();
}
@AssertInDefinitionsPhase(
targetName: 'B',
constructorsOf: ['()', 'h()', 'i()'],
fieldsOf: ['int j', 'String k'],
methodsOf: ['int l()', 'String m()'],
)
@AssertInDeclarationsPhase(
targetName: 'B',
constructorsOf: ['()', 'h()', 'i()'],
fieldsOf: ['int j', 'String k'],
methodsOf: ['int l()', 'String m()'],
)
class C {}
void main() {}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2023, 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.
// SharedOptions=--enable-experiment=macros
import 'impl/assert_in_declarations_phase_macro.dart';
import 'impl/assert_in_definitions_phase_macro.dart';
import 'impl/assert_in_types_phase_macro.dart';
// If any of the "assert" macros is broken then tests that use them might
// pass with a false positive. This test should start failing at the same time.
@AssertInTypesPhase(
// [error line 13, column 1, length 218]
// [analyzer] COMPILE_TIME_ERROR.MACRO_ERROR
targetLibrary: 'dart:core',
targetName: 'int',
resolveIdentifier: '<intentional mismatch and failure>',
)
@AssertInDefinitionsPhase(
// [error line 20, column 1, length 191]
// [analyzer] COMPILE_TIME_ERROR.MACRO_ERROR
targetName: 'A',
constructorsOf: ['<intentional mismatch and failure>'],
)
@AssertInDeclarationsPhase(
// [error line 26, column 1, length 191]
// [analyzer] COMPILE_TIME_ERROR.MACRO_ERROR
targetName: 'A',
constructorsOf: ['<intentional mismatch and failure'],
)
class A {}
// TODO(davidmorgan): add CFE error coverage.

View file

@ -0,0 +1,57 @@
// Copyright (c) 2023, 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.
// ignore_for_file: deprecated_member_use
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:expect/expect.dart';
import 'impl.dart';
macro class AssertInDeclarationsPhase
implements ClassDeclarationsMacro {
final String? targetLibrary;
final String targetName;
final List? constructorsOf;
final List? fieldsOf;
final List? methodsOf;
const AssertInDeclarationsPhase(
{this.targetLibrary,
required this.targetName,
this.constructorsOf,
this.fieldsOf,
this.methodsOf});
@override
Future<void> buildDeclarationsForClass(
ClassDeclaration clazz, MemberDeclarationBuilder builder) =>
_assert(clazz, builder);
// TODO(davidmorgan): support asserting in more places.
Future<void> _assert(TypeDeclaration typeDeclaration,
DeclarationPhaseIntrospector builder) async {
final targetIdentifier = await builder.resolveIdentifier(
targetLibrary == null
? typeDeclaration.library.uri
: Uri.parse(targetLibrary!),
targetName);
final declaration = await builder.typeDeclarationOf(targetIdentifier);
if (constructorsOf != null) {
Expect.deepEquals(
constructorsOf, stringify(await builder.constructorsOf(declaration)));
}
if (fieldsOf != null) {
Expect.deepEquals(
fieldsOf, stringify(await builder.fieldsOf(declaration)));
}
if (methodsOf != null) {
Expect.deepEquals(
methodsOf, stringify(await builder.methodsOf(declaration)));
}
// TODO(davidmorgan): cover typeDeclarationsOf, typesOf, valuesOf.
}
}

View file

@ -0,0 +1,57 @@
// Copyright (c) 2023, 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.
// ignore_for_file: deprecated_member_use
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:expect/expect.dart';
import 'impl.dart';
macro class AssertInDefinitionsPhase implements ClassDefinitionMacro {
final String? targetLibrary;
final String targetName;
final List? constructorsOf;
final List? fieldsOf;
final List? methodsOf;
const AssertInDefinitionsPhase(
{this.targetLibrary,
required this.targetName,
this.constructorsOf,
this.fieldsOf,
this.methodsOf});
@override
Future<void> buildDefinitionForClass(
ClassDeclaration clazz, TypeDefinitionBuilder builder) =>
_assert(clazz, builder);
// TODO(davidmorgan): support asserting in more places.
Future<void> _assert(TypeDeclaration typeDeclaration,
DefinitionPhaseIntrospector builder) async {
final targetIdentifier = await builder.resolveIdentifier(
targetLibrary == null
? typeDeclaration.library.uri
: Uri.parse(targetLibrary!),
targetName);
final declaration = await builder.typeDeclarationOf(targetIdentifier);
if (constructorsOf != null) {
Expect.deepEquals(
constructorsOf, stringify(await builder.constructorsOf(declaration)));
}
if (fieldsOf != null) {
Expect.deepEquals(
fieldsOf, stringify(await builder.fieldsOf(declaration)));
}
if (methodsOf != null) {
Expect.deepEquals(
methodsOf, stringify(await builder.methodsOf(declaration)));
}
// TODO(davidmorgan): cover typeDeclarationsOf, typesOf, valuesOf.
// TODO(davidmorgan): cover DefinitionPhaseIntrospector methods.
}
}

View file

@ -0,0 +1,38 @@
// Copyright (c) 2023, 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.
// ignore_for_file: deprecated_member_use
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:expect/expect.dart';
import 'impl.dart';
macro class AssertInTypesPhase implements ClassTypesMacro {
final String targetLibrary;
final String targetName;
final String? resolveIdentifier;
const AssertInTypesPhase(
{required this.targetLibrary,
required this.targetName,
this.resolveIdentifier});
@override
Future<void> buildTypesForClass(
ClassDeclaration clazz, ClassTypeBuilder builder) =>
_assert(clazz, builder);
// TODO(davidmorgan): support asserting in more places.
Future<void> _assert(
TypeDeclaration typeDeclaration, TypePhaseIntrospector builder) async {
if (resolveIdentifier != null) {
Expect.deepEquals(
resolveIdentifier,
stringify(await builder.resolveIdentifier(
Uri.parse(targetLibrary), targetName)));
}
}
}

View file

@ -0,0 +1,27 @@
import 'package:_fe_analyzer_shared/src/macros/api.dart';
/// Converts an object into primitives.
///
/// This allows test expectations for the object to be passed to the macro
/// application.
///
/// Conversion might not contain all the data in the object: there is a
/// tradeoff, more data makes the test more precise but also more brittle.
Object stringify(Object? object) {
if (object is List) {
return object.map(stringify).toList();
} else if (object is ConstructorDeclaration) {
return '${object.identifier.name}()';
} else if (object is FieldDeclaration) {
return '${stringify(object.type)} ${object.identifier.name}';
} else if (object is Identifier) {
return object.name;
} else if (object is MethodDeclaration) {
return '${stringify(object.returnType)} ${object.identifier.name}()';
} else if (object is NamedTypeAnnotation) {
return object.identifier.name;
} else {
throw new UnsupportedError('Dont know how to stringify with type '
'${object.runtimeType}: "$object"');
}
}