From e2c1291cbe2d4a91fd0aef5fd88fed9a64bca7d5 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Tue, 19 Jul 2022 17:50:21 -0700 Subject: [PATCH 01/10] Add support for generic metadata annotations --- reflectable/lib/src/builder_implementation.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index ae13c4c7..8d45c873 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -4877,7 +4877,18 @@ Future _extractMetadataCode(Element element, Resolver resolver, if (_isPrivateName(name)) { await _severe('Cannot access private name $name', element); } - metadataParts.add('const $prefix$name($arguments)'); + if (annotationNode.typeArguments != null) { + String typeArguments = annotationNode.typeArguments!.arguments + .map((TypeAnnotation typeArgument) { + LibraryElement library = typeArgument.type!.element!.library!; + String prefix = importCollector._getPrefix(library); + return '$prefix$typeArgument'; + }) + .join(', '); + metadataParts.add('const $prefix$name<$typeArguments>($arguments)'); + } else { + metadataParts.add('const $prefix$name($arguments)'); + } } else { // A field reference. if (_isPrivateName(annotationNode.name.name)) { From 89f905c2d9941e90b8dbeb837905ac57ab2b7716 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 7 Nov 2022 21:53:37 -0800 Subject: [PATCH 02/10] Update reflectable/lib/src/builder_implementation.dart Co-authored-by: Erik Ernst --- reflectable/lib/src/builder_implementation.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 06af9a5f..24385b39 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5005,7 +5005,8 @@ Future _extractMetadataCode(Element element, Resolver resolver, if (_isPrivateName(name)) { await _severe('Cannot access private name $name', element); } - if (annotationNode.typeArguments != null) { + var typeArguments = annotationNode.typeArguments; + if (typeArguments != null) { String typeArguments = annotationNode.typeArguments!.arguments .map((TypeAnnotation typeArgument) { LibraryElement library = typeArgument.type!.element!.library!; From 43a5b2570330e793a41be02b938fbae520b20962 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 7 Nov 2022 21:53:47 -0800 Subject: [PATCH 03/10] Update reflectable/lib/src/builder_implementation.dart Co-authored-by: Erik Ernst --- reflectable/lib/src/builder_implementation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 24385b39..daf46778 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5007,7 +5007,7 @@ Future _extractMetadataCode(Element element, Resolver resolver, } var typeArguments = annotationNode.typeArguments; if (typeArguments != null) { - String typeArguments = annotationNode.typeArguments!.arguments + String typeArguments = typeArguments.arguments .map((TypeAnnotation typeArgument) { LibraryElement library = typeArgument.type!.element!.library!; String prefix = importCollector._getPrefix(library); From cc88ab5b27c983e8f8a8e0b3d3b1d1fa5f447864 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 7 Nov 2022 22:25:31 -0800 Subject: [PATCH 04/10] Fixed variable name conflict --- reflectable/lib/src/builder_implementation.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index daf46778..5b030da3 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5007,14 +5007,14 @@ Future _extractMetadataCode(Element element, Resolver resolver, } var typeArguments = annotationNode.typeArguments; if (typeArguments != null) { - String typeArguments = typeArguments.arguments + String typeArgumentsString = typeArguments.arguments .map((TypeAnnotation typeArgument) { LibraryElement library = typeArgument.type!.element!.library!; String prefix = importCollector._getPrefix(library); return '$prefix$typeArgument'; }) .join(', '); - metadataParts.add('const $prefix$name<$typeArguments>($arguments)'); + metadataParts.add('const $prefix$name<$typeArgumentsString>($arguments)'); } else { metadataParts.add('const $prefix$name($arguments)'); } From 17fc5877e83c7fc6a51c28001a8475d1f241ff9e Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 7 Nov 2022 22:54:31 -0800 Subject: [PATCH 05/10] Added _isImportable check --- .../lib/src/builder_implementation.dart | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 5b030da3..112c1479 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5007,13 +5007,23 @@ Future _extractMetadataCode(Element element, Resolver resolver, } var typeArguments = annotationNode.typeArguments; if (typeArguments != null) { - String typeArgumentsString = typeArguments.arguments - .map((TypeAnnotation typeArgument) { - LibraryElement library = typeArgument.type!.element!.library!; - String prefix = importCollector._getPrefix(library); - return '$prefix$typeArgument'; - }) - .join(', '); + List typeArgumentsStringParts = []; + for (TypeAnnotation typeArgument in typeArguments.arguments) { + DartType typeArgumentType = typeArgument.type!; + if (!await _isImportable(typeArgumentType.element!, dataId, resolver)) { + await _fine('Ignoring unresolved metadata $typeArgumentType', element); + // fallback to dynamic + typeArgumentsStringParts.add('dynamic'); + continue; + } + + LibraryElement annotationTypeArgumentLibrary = typeArgumentType.element!.library!; + importCollector._addLibrary(annotationTypeArgumentLibrary); + String prefix = importCollector._getPrefix(annotationTypeArgumentLibrary); + typeArgumentsStringParts.add('$prefix$typeArgument'); + } + + String typeArgumentsString = typeArgumentsStringParts.join(', '); metadataParts.add('const $prefix$name<$typeArgumentsString>($arguments)'); } else { metadataParts.add('const $prefix$name($arguments)'); From 171e9ee5a7d4600bcabc2fcbbe7cbc164860de85 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 7 Nov 2022 23:58:04 -0800 Subject: [PATCH 06/10] Added null check for typeArgumentType.element?.library --- reflectable/lib/src/builder_implementation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 112c1479..e932864c 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5010,7 +5010,7 @@ Future _extractMetadataCode(Element element, Resolver resolver, List typeArgumentsStringParts = []; for (TypeAnnotation typeArgument in typeArguments.arguments) { DartType typeArgumentType = typeArgument.type!; - if (!await _isImportable(typeArgumentType.element!, dataId, resolver)) { + if (typeArgumentType.element?.library == null || !await _isImportable(typeArgumentType.element!, dataId, resolver)) { await _fine('Ignoring unresolved metadata $typeArgumentType', element); // fallback to dynamic typeArgumentsStringParts.add('dynamic'); From 049c70c4458445916f634a8775ce7815a90400e9 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Thu, 5 Dec 2024 13:01:11 -0800 Subject: [PATCH 07/10] Add generic metadata test --- .../test/generic_metadata_test.dart | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 test_reflectable/test/generic_metadata_test.dart diff --git a/test_reflectable/test/generic_metadata_test.dart b/test_reflectable/test/generic_metadata_test.dart new file mode 100644 index 00000000..e918c6b3 --- /dev/null +++ b/test_reflectable/test/generic_metadata_test.dart @@ -0,0 +1,90 @@ +@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({'a': 14}) +]; + +class K { + static const p = 2; +} + +@myReflectable +@Bar({ + b: deprecated, + c: Deprecated('tomorrow'), + 1 + 2: (d ? 3 : 4), + identical(1, 2): 's', + K.p: 6 +}) +@Bar({}) +@Bar.namedConstructor({}) +@c +class Foo { + @Bar({}) + @Bar>({}) + @Bar.namedConstructor({}) +@Bar.namedConstructor({}) +@c +void foo() {} +var x = 10; +} + +class Bar { + final Map 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({ + b: deprecated, + c: Deprecated('tomorrow'), + 3: 3, + false: 's', + 2: 6, + }), + [ + Bar({'a': 14}) + ], + ]); + + var fooMirror = myReflectable.reflectType(Foo) as ClassMirror; + expect(fooMirror.declarations['foo']!.metadata, const [ + Bar({}), + Bar>>({}), + Bar({}), + Bar({}), + [ + Bar({'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]); + }); +} \ No newline at end of file From 5f168cab4ebdd2a67263d3841c32455e7cf19b07 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Thu, 5 Dec 2024 14:14:03 -0800 Subject: [PATCH 08/10] Fixed nested type arguments in annotations --- .../lib/src/builder_implementation.dart | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 32485bf5..7acac2f7 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -5036,23 +5036,36 @@ Future _extractMetadataCode(Element element, Resolver resolver, } var typeArguments = annotationNode.typeArguments; if (typeArguments != null) { - List typeArgumentsStringParts = []; - for (TypeAnnotation typeArgument in typeArguments.arguments) { - DartType typeArgumentType = typeArgument.type!; - if (typeArgumentType.element?.library == null || !await _isImportable(typeArgumentType.element!, dataId, resolver)) { - await _fine('Ignoring unresolved metadata $typeArgumentType', element); - // fallback to dynamic - typeArgumentsStringParts.add('dynamic'); - continue; + extractTypeArguments(List typeArguments) async { + List typeArgumentsStringParts = []; + for (DartType? typeArgumentType in typeArguments) { + if (typeArgumentType == null || typeArgumentType.element?.library == null || !await _isImportable(typeArgumentType.element!, dataId, resolver)) { + await _fine('Ignoring unresolved metadata $typeArgumentType', element); + // fallback to dynamic + typeArgumentsStringParts.add('dynamic'); + continue; + } + + LibraryElement annotationTypeArgumentLibrary = typeArgumentType.element!.library!; + importCollector._addLibrary(annotationTypeArgumentLibrary); + String prefix = importCollector._getPrefix(annotationTypeArgumentLibrary); + + final outerType = typeArgumentType.toString().split("<")[0]; + + if (typeArgumentType is ParameterizedType && typeArgumentType.typeArguments.isNotEmpty) { + final innerTypeArgs = await extractTypeArguments(typeArgumentType.typeArguments); + typeArgumentsStringParts.add('$prefix$outerType<$innerTypeArgs>'); + } else { + typeArgumentsStringParts.add('$prefix$outerType'); + } } - LibraryElement annotationTypeArgumentLibrary = typeArgumentType.element!.library!; - importCollector._addLibrary(annotationTypeArgumentLibrary); - String prefix = importCollector._getPrefix(annotationTypeArgumentLibrary); - typeArgumentsStringParts.add('$prefix$typeArgument'); + String typeArgumentsString = typeArgumentsStringParts.join(', '); + return typeArgumentsString; } - String typeArgumentsString = typeArgumentsStringParts.join(', '); + final typeArgumentsString = await extractTypeArguments(typeArguments.arguments.map((annotation) => annotation.type).toList()); + print(typeArgumentsString); metadataParts.add('const $prefix$name<$typeArgumentsString>($arguments)'); } else { metadataParts.add('const $prefix$name($arguments)'); From 8a0e5c1f0104e07e6da9cce90c1560a7e5f32ec6 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 10 Feb 2025 22:20:47 -0800 Subject: [PATCH 09/10] Update generic_metadata_test --- .../test/generic_metadata_test.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test_reflectable/test/generic_metadata_test.dart b/test_reflectable/test/generic_metadata_test.dart index e918c6b3..11ed1ab9 100644 --- a/test_reflectable/test/generic_metadata_test.dart +++ b/test_reflectable/test/generic_metadata_test.dart @@ -1,22 +1,26 @@ @c -library test_reflectable.test.metadata_test; +library test_reflectable.test.generic_metadata_test; import 'package:reflectable/reflectable.dart'; import 'package:test/test.dart'; -import 'metadata_test.reflectable.dart'; +import 'generic_metadata_test.reflectable.dart'; class MyReflectable extends Reflectable { const MyReflectable() : super(metadataCapability, instanceInvokeCapability, - staticInvokeCapability, declarationsCapability, libraryCapability); + staticInvokeCapability, declarationsCapability, libraryCapability); } const myReflectable = MyReflectable(); +const b = 0; + const c = [ Bar({'a': 14}) ]; +const d = true; + class K { static const p = 2; } @@ -36,10 +40,10 @@ class Foo { @Bar({}) @Bar>({}) @Bar.namedConstructor({}) -@Bar.namedConstructor({}) -@c -void foo() {} -var x = 10; + @Bar.namedConstructor({}) + @c + void foo() {} + var x = 10; } class Bar { From 6b283b47a4bd9fb2aacb3b9decc0c5c948f4a457 Mon Sep 17 00:00:00 2001 From: jacksonjude Date: Mon, 10 Feb 2025 23:31:32 -0800 Subject: [PATCH 10/10] Using _typeCodeOfTypeArgument inside _extractMetadataCode for generic annotations --- .../lib/src/builder_implementation.dart | 52 ++++++------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/reflectable/lib/src/builder_implementation.dart b/reflectable/lib/src/builder_implementation.dart index 7acac2f7..e3021348 100644 --- a/reflectable/lib/src/builder_implementation.dart +++ b/reflectable/lib/src/builder_implementation.dart @@ -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 += @@ -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? typeVariablesInScope]) { if (type is TypeParameterType && (typeVariablesInScope == null || @@ -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 _typeCodeOfTypeArgument( + static Future _typeCodeOfTypeArgument( DartType dartType, _ImportCollector importCollector, Set typeVariablesInScope, @@ -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'; } @@ -5036,36 +5035,14 @@ Future _extractMetadataCode(Element element, Resolver resolver, } var typeArguments = annotationNode.typeArguments; if (typeArguments != null) { - extractTypeArguments(List typeArguments) async { - List typeArgumentsStringParts = []; - for (DartType? typeArgumentType in typeArguments) { - if (typeArgumentType == null || typeArgumentType.element?.library == null || !await _isImportable(typeArgumentType.element!, dataId, resolver)) { - await _fine('Ignoring unresolved metadata $typeArgumentType', element); - // fallback to dynamic - typeArgumentsStringParts.add('dynamic'); - continue; - } - - LibraryElement annotationTypeArgumentLibrary = typeArgumentType.element!.library!; - importCollector._addLibrary(annotationTypeArgumentLibrary); - String prefix = importCollector._getPrefix(annotationTypeArgumentLibrary); - - final outerType = typeArgumentType.toString().split("<")[0]; - - if (typeArgumentType is ParameterizedType && typeArgumentType.typeArguments.isNotEmpty) { - final innerTypeArgs = await extractTypeArguments(typeArgumentType.typeArguments); - typeArgumentsStringParts.add('$prefix$outerType<$innerTypeArgs>'); - } else { - typeArgumentsStringParts.add('$prefix$outerType'); - } - } - - String typeArgumentsString = typeArgumentsStringParts.join(', '); - return typeArgumentsString; - } - - final typeArgumentsString = await extractTypeArguments(typeArguments.arguments.map((annotation) => annotation.type).toList()); - print(typeArgumentsString); + var typedefs = {}; + var typeVariablesInScope = {}; // 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)'); @@ -5676,7 +5653,7 @@ Future _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 _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 = ''; @@ -5686,7 +5663,8 @@ Future _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!);