Skip to content

Add support for generic type arguments in annotations #283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions reflectable/lib/src/builder_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class _ReflectionWorld {
await reflector._generateCode(this, importCollector, typedefs);
if (typedefs.isNotEmpty) {
for (DartType dartType in typedefs.keys) {
String body = await reflector._typeCodeOfTypeArgument(
String body = await _ReflectorDomain._typeCodeOfTypeArgument(
dartType, importCollector, typeVariablesInScope, typedefs,
useNameOfGenericFunctionType: false);
typedefsCode +=
Expand Down Expand Up @@ -1918,7 +1918,7 @@ class _ReflectorDomain {
/// Returns true iff the given [type] is not and does not contain a free
/// type variable. [typeVariablesInScope] gives the names of type variables
/// which are in scope (and hence not free in the relevant context).
bool _hasNoFreeTypeVariables(DartType type,
static bool _hasNoFreeTypeVariables(DartType type,
[Set<String>? typeVariablesInScope]) {
if (type is TypeParameterType &&
(typeVariablesInScope == null ||
Expand Down Expand Up @@ -1951,7 +1951,7 @@ class _ReflectorDomain {
/// is used to decide whether the output should be a simple `typedef`
/// name or a fully spelled-out generic function type (and it has no
/// effect when [dartType] is not a generic function type).
Future<String> _typeCodeOfTypeArgument(
static Future<String> _typeCodeOfTypeArgument(
DartType dartType,
_ImportCollector importCollector,
Set<String> typeVariablesInScope,
Expand All @@ -1963,8 +1963,7 @@ class _ReflectorDomain {
'Attempt to generate code for an '
'unsupported kind of type: $dartType (${dartType.runtimeType}). '
'Generating `dynamic`.',
element,
_resolver));
element));
return 'dynamic';
}

Expand Down Expand Up @@ -5034,7 +5033,20 @@ Future<String> _extractMetadataCode(Element element, Resolver resolver,
if (_isPrivateName(name)) {
await _severe('Cannot access private name $name', element);
}
metadataParts.add('const $prefix$name($arguments)');
var typeArguments = annotationNode.typeArguments;
if (typeArguments != null) {
var typedefs = <FunctionType, int>{};
var typeVariablesInScope = <String>{}; // None at top level.

final typeArgumentsList = typeArguments.arguments.map((annotation) => annotation.type);
final typeArgumentsString = (await Future.wait(typeArgumentsList.map((type) async =>
type != null ? await _ReflectorDomain._typeCodeOfTypeArgument(type, importCollector, typeVariablesInScope, typedefs, useNameOfGenericFunctionType: false) : 'dynamic'
))).join(', ');
// print(typeArgumentsString);
metadataParts.add('const $prefix$name<$typeArgumentsString>($arguments)');
} else {
metadataParts.add('const $prefix$name($arguments)');
}
} else {
// A field reference.
if (_isPrivateName(annotationNode.name.name)) {
Expand Down Expand Up @@ -5641,7 +5653,7 @@ Future<void> _fine(String message,
/// Returns a string containing the given [message] and identifying the
/// associated source code location as the location of the given [target].
Future<String> _formatDiagnosticMessage(
String message, Element? target, Resolver resolver) async {
String message, Element? target, [Resolver? resolver]) async {
Source? source = target?.source;
if (source == null) return message;
String locationString = '';
Expand All @@ -5651,7 +5663,8 @@ Future<String> _formatDiagnosticMessage(
var targetLibrary = target?.library;
if (targetLibrary != null &&
nameOffset != null &&
!_isPlatformLibrary(targetLibrary)) {
!_isPlatformLibrary(targetLibrary) &&
resolver != null) {
final resolvedLibrary = await _getResolvedLibrary(targetLibrary, resolver);
if (resolvedLibrary != null) {
final targetDeclaration = resolvedLibrary.getElementDeclaration(target!);
Expand Down
94 changes: 94 additions & 0 deletions test_reflectable/test/generic_metadata_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@c
library test_reflectable.test.generic_metadata_test;

import 'package:reflectable/reflectable.dart';
import 'package:test/test.dart';
import 'generic_metadata_test.reflectable.dart';

class MyReflectable extends Reflectable {
const MyReflectable()
: super(metadataCapability, instanceInvokeCapability,
staticInvokeCapability, declarationsCapability, libraryCapability);
}

const myReflectable = MyReflectable();

const b = 0;

const c = [
Bar<num>({'a': 14})
];

const d = true;

class K {
static const p = 2;
}

@myReflectable
@Bar<Object?>({
b: deprecated,
c: Deprecated('tomorrow'),
1 + 2: (d ? 3 : 4),
identical(1, 2): 's',
K.p: 6
})
@Bar<Never>({})
@Bar<void>.namedConstructor({})
@c
class Foo {
@Bar<bool>({})
@Bar<Bar<Bar>>({})
@Bar<MyReflectable>.namedConstructor({})
@Bar<void Function()>.namedConstructor({})
@c
void foo() {}
var x = 10;
}

class Bar<X> {
final Map<Object, X> m;
const Bar(this.m);
const Bar.namedConstructor(this.m);

@override
String toString() => 'Bar<$X>($m)';
}

void main() {
initializeReflectable();

test('metadata on class', () {
expect(myReflectable.reflectType(Foo).metadata, const [
MyReflectable(),
Bar<Object?>({
b: deprecated,
c: Deprecated('tomorrow'),
3: 3,
false: 's',
2: 6,
}),
[
Bar<num>({'a': 14})
],
]);

var fooMirror = myReflectable.reflectType(Foo) as ClassMirror;
expect(fooMirror.declarations['foo']!.metadata, const [
Bar<bool>({}),
Bar<Bar<Bar<dynamic>>>({}),
Bar<MyReflectable>({}),
Bar<void Function()>({}),
[
Bar<num>({'a': 14})
],
]);

// The synthetic accessors do not have metadata.
expect(fooMirror.instanceMembers['x']!.metadata, []);
expect(fooMirror.instanceMembers['x=']!.metadata, []);

// Test metadata on libraries
expect(myReflectable.reflectType(Foo).owner!.metadata, [c]);
});
}