-
Notifications
You must be signed in to change notification settings - Fork 59
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
base: master
Are you sure you want to change the base?
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Hello @jacksonjude, thanks for the PR! This is something reflectable should support. I'm really busy now, but I'll take a look as soon as possible. |
Hey there, sorry if you are still busy, but can you take another look at this? |
Sorry about the delay — yep, I'm still busy, but I will take a look at it tomorrow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for the input! We need to make the code a bit more robust. Here is the beginnings of a test that is intended to verify that the new feature is working:
@c
library test_reflectable.test.metadata_test;
import 'package:reflectable/reflectable.dart';
import 'package:test/test.dart';
import 'metadata_test.reflectable.dart';
class MyReflectable extends Reflectable {
const MyReflectable()
: super(metadataCapability, instanceInvokeCapability,
staticInvokeCapability, declarationsCapability, libraryCapability);
}
const myReflectable = MyReflectable();
const c = [
Bar<num>({'a': 14})
];
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]);
});
}
The test probably needs to be corrected here and there (but it already shows that the new code gets a null reference error, so it's a useful starting point).
The test should be added to the repository at test_reflectable/test/generic_metadata_test.dart
. The lower bound of the SDK constraint in test_reflectable/pubspec.yaml` should be changed to 2.17 (such that the generic-metadata feature is included in the language).
String typeArguments = annotationNode.typeArguments!.arguments | ||
.map((TypeAnnotation typeArgument) { | ||
LibraryElement library = typeArgument.type!.element!.library!; | ||
String prefix = importCollector._getPrefix(library); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the prefix we need to check that the given library is importable (_isImportable
). For instance, dart:_internal
is not importable by user code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've resolved this, I currently have the type argument default to 'dynamic'
if the library can't be imported, though I'm not sure if this is the correct behavior.
.map((TypeAnnotation typeArgument) { | ||
LibraryElement library = typeArgument.type!.element!.library!; | ||
String prefix = importCollector._getPrefix(library); | ||
return '$prefix$typeArgument'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this won't work in the case where the type argument is a parameterized type of the form C<D>
where C
is imported from prefix1
and D
is imported from prefix2
: We should get prefix1.C<prefix2.D>
, but the code above simply uses toString()
on the given TypeAnnotation
, and that will yield syntax which is only valid for the location where that type annotation actually occurs in the program (and at this point we are writing some code that should denote the same type, but in a different library).
Check out _typeCodeOfTypeArgument
, you might be able to call that function in order to get the right string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having some trouble calling _typeCodeOfTypeArgument
since it is a function of _ReflectorDomain
, which I can't seem to access from within _extractMetadataCode
. However, it is very possible I may have missed something given that I'm not super familiar with the layout of the library.
Co-authored-by: Erik Ernst <eernst@google.com>
Co-authored-by: Erik Ernst <eernst@google.com>
One more question: the variables |
(No, I haven't forgotten this. Just busy again. Will try to get to it real soon now. ;-) |
(Busy, busy, busy, but I will take a look later today.) |
Hi again @eernstg, what changes need to be made for this PR to be ready to merge? |
Hi @jacksonjude, thanks for contributing! I'm sincerely sorry about not being able to get around to this for so long. I took another look at it today, and I can see that it generates code where a prefix is missing when this test is executed: @c
library test_reflectable.test.generic_metadata_test;
import 'package:reflectable/reflectable.dart';
import 'package:test/test.dart';
import '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]);
});
} The error from the analyzer looks as follows:
If I edit the generated code to add the missing prefix then I get 'Null check operator used on a null value'. I think it shouldn't be hard to fix. It would be great to get it landed! |
Hi again @eernstg, I just patched the bug relating to nested type arguments. The remaining null errors seem to be the result of the |
Hey @eernstg, it turns out I was using an older version of the test script you sent, and the new one (I believe) had a typo at the top: ( On another note, I did realize I was repeating a lot of the code from Onto the metadata test itself, I am a little confused on why the first expect ( |
Overview
Adds support for generic annotations, a feature added in Dart SDK 2.14.
Take this class for example:
The annotation would have previously been identified as having the type argument
dynamic
:It will now be correctly identified as having the type argument
Soul
:This is done by appending the type arguments (if any exist) after the annotation name during the
_extractMetadataCode
function ofbuilder_implementation.dart
.