diff --git a/packages/reflectable/lib/mirrors.dart b/packages/reflectable/lib/mirrors.dart index 1e75afcb..64019f6b 100644 --- a/packages/reflectable/lib/mirrors.dart +++ b/packages/reflectable/lib/mirrors.dart @@ -674,7 +674,7 @@ abstract class CombinatorMirror implements Mirror { /// A [TypeMirror] reflects a Dart language class, typedef, /// function type or type variable. -abstract class TypeMirror implements DeclarationMirror { +abstract class TypeMirror implements DeclarationMirror { /// Returns true if this mirror reflects dynamic, a non-generic class or /// typedef, or an instantiated generic class or typedef with support in /// the execution mode. Otherwise, returns false. @@ -817,10 +817,16 @@ abstract class TypeMirror implements DeclarationMirror { /// Returns true iff this type mirror represents a potentially non-nullable /// type. bool get isPotentiallyNonNullable; + + /// Invokes the provided function with the reflected type as a type argument. + /// + /// Allows executing generic code with the actual runtime type represented + /// by this mirror. + R callWithReflectedType(R Function() f); } /// A [ClassMirror] reflects a Dart language class. -abstract class ClassMirror implements TypeMirror, ObjectMirror { +abstract class ClassMirror implements TypeMirror, ObjectMirror { /// A mirror on the superclass on the reflectee. /// /// If this type is [:Object:], the superclass will be null. @@ -1077,7 +1083,7 @@ abstract class FunctionTypeMirror implements ClassMirror { } /// A [TypeVariableMirror] represents a type parameter of a generic type. -abstract class TypeVariableMirror extends TypeMirror { +abstract class TypeVariableMirror extends TypeMirror { /// A mirror on the type that is the upper bound of this type variable. /// /// Required capabilities: [upperBound] requires a [TypeCapability]. diff --git a/packages/reflectable/lib/reflectable.dart b/packages/reflectable/lib/reflectable.dart index de0f4caf..afb07686 100644 --- a/packages/reflectable/lib/reflectable.dart +++ b/packages/reflectable/lib/reflectable.dart @@ -40,7 +40,7 @@ abstract class ReflectableInterface { /// /// Throws a [NoSuchCapabilityError] if the class of [o] has not been marked /// for reflection. - InstanceMirror reflect(Object o); + InstanceMirror reflect(T o); /// Returns true if this reflector has capabilities for the given Type. bool canReflectType(Type type); diff --git a/packages/reflectable/lib/src/reflectable_builder_based.dart b/packages/reflectable/lib/src/reflectable_builder_based.dart index 8544bd25..99be197f 100644 --- a/packages/reflectable/lib/src/reflectable_builder_based.dart +++ b/packages/reflectable/lib/src/reflectable_builder_based.dart @@ -148,14 +148,15 @@ class ReflectorData { return typeToTypeMirrorCache[type]; } - ClassMirror? classMirrorForInstance(Object? instance) { + ClassMirror? classMirrorForInstance(Object? instance) { TypeMirror? result = typeMirrorForType(instance.runtimeType); + if (result is ClassMirror) return result; // Check if we have a generic class that matches. for (TypeMirror typeMirror in _typeToTypeMirrorCache!.values) { if (typeMirror is GenericClassMirrorImpl) { if (typeMirror._isGenericRuntimeTypeOf(instance)) { - return _createInstantiatedGenericClass( + return _createInstantiatedGenericClass( typeMirror, instance.runtimeType, null, @@ -205,7 +206,7 @@ abstract class _DataCaching { } } -class _InstanceMirrorImpl extends _DataCaching implements InstanceMirror { +class _InstanceMirrorImpl extends _DataCaching implements InstanceMirror { @override final ReflectableImpl _reflector; @@ -213,7 +214,7 @@ class _InstanceMirrorImpl extends _DataCaching implements InstanceMirror { final Object? reflectee; _InstanceMirrorImpl(this.reflectee, this._reflector) { - _type = _data.classMirrorForInstance(reflectee) as ClassMirrorBase?; + _type = _data.classMirrorForInstance(reflectee) as ClassMirrorBase?; if (_type == null) { // If there is no `TypeCapability` and also no `InstanceInvokeCapability` // then can do almost nothing. Nevertheless, we can still offer a @@ -390,7 +391,8 @@ class _InstanceMirrorImpl extends _DataCaching implements InstanceMirror { typedef MethodMirrorProvider = MethodMirror? Function(String methodName); -abstract class ClassMirrorBase extends _DataCaching implements ClassMirror { +abstract class ClassMirrorBase extends _DataCaching + implements ClassMirror { /// The reflector which represents the mirror system that this /// mirror belongs to. @override @@ -974,9 +976,12 @@ abstract class ClassMirrorBase extends _DataCaching implements ClassMirror { if (_superclassIndex == null) return null; // Superclass of [Object]. return _data.typeMirrors[_superclassIndex] as ClassMirrorBase?; } + + @override + D callWithReflectedType(D Function() f) => f(); } -class NonGenericClassMirrorImpl extends ClassMirrorBase { +class NonGenericClassMirrorImpl extends ClassMirrorBase { NonGenericClassMirrorImpl( super.simpleName, super.qualifiedName, @@ -1067,7 +1072,7 @@ class NonGenericClassMirrorImpl extends ClassMirrorBase { typedef InstanceChecker = bool Function(Object?); -class GenericClassMirrorImpl extends ClassMirrorBase { +class GenericClassMirrorImpl extends ClassMirrorBase { /// Used to enable instance checks. Let O be an instance and let C denote the /// generic class modeled by this mirror. The check is then such that the /// result is true iff there exists a list of type arguments X1..Xk such that @@ -1216,7 +1221,7 @@ class GenericClassMirrorImpl extends ClassMirrorBase { String toString() => 'GenericClassMirrorImpl($qualifiedName)'; } -class InstantiatedGenericClassMirrorImpl extends ClassMirrorBase { +class InstantiatedGenericClassMirrorImpl extends ClassMirrorBase { final GenericClassMirrorImpl _originalDeclaration; final Type? _reflectedType; @@ -1360,11 +1365,14 @@ class InstantiatedGenericClassMirrorImpl extends ClassMirrorBase { @override int get hashCode => originalDeclaration.hashCode ^ _reflectedType.hashCode; + @override + D callWithReflectedType(D Function() f) => f(); + @override String toString() => 'InstantiatedGenericClassMirrorImpl($qualifiedName)'; } -InstantiatedGenericClassMirrorImpl _createInstantiatedGenericClass( +InstantiatedGenericClassMirrorImpl _createInstantiatedGenericClass( GenericClassMirrorImpl genericClassMirror, Type? reflectedType, List? reflectedTypeArguments, [ @@ -1373,7 +1381,7 @@ InstantiatedGenericClassMirrorImpl _createInstantiatedGenericClass( // TODO(eernst) implement: Pass a representation of type arguments to this // method, and create an instantiated generic class which includes that // information. - return InstantiatedGenericClassMirrorImpl( + return InstantiatedGenericClassMirrorImpl( genericClassMirror.simpleName, genericClassMirror.qualifiedName, descriptor ?? genericClassMirror._descriptor, @@ -1573,6 +1581,11 @@ class TypeVariableMirrorImpl extends _DataCaching } return _data.typeMirrors[_ownerIndex]; } + + @override + D callWithReflectedType(D Function() f) => throw UnsupportedError( + 'Attempt to call `callWithReflectedType` on type variable $simpleName', + ); } class LibraryMirrorImpl extends _DataCaching implements LibraryMirror { @@ -1820,7 +1833,7 @@ class LibraryMirrorImpl extends _DataCaching implements LibraryMirror { throw unimplementedError('libraryDependencies'); } -class MethodMirrorImpl extends _DataCaching implements MethodMirror { +class MethodMirrorImpl extends _DataCaching implements MethodMirror { /// An encoding of the attributes and kind of this mirror. final int _descriptor; @@ -1997,7 +2010,7 @@ class MethodMirrorImpl extends _DataCaching implements MethodMirror { if (_hasClassReturnType) { TypeMirror typeMirror = _data.typeMirrors[_returnTypeIndex]; return _hasGenericReturnType - ? _createInstantiatedGenericClass( + ? _createInstantiatedGenericClass( typeMirror as GenericClassMirrorImpl, null, _reflectedTypeArgumentsOfReturnType, @@ -2307,7 +2320,7 @@ class ImplicitSetterMirrorImpl extends ImplicitAccessorMirrorImpl { String toString() => 'ImplicitSetterMirrorImpl($qualifiedName)'; } -abstract class VariableMirrorBase extends _DataCaching +abstract class VariableMirrorBase extends _DataCaching implements VariableMirror { final String _name; final int _descriptor; @@ -2401,7 +2414,7 @@ abstract class VariableMirrorBase extends _DataCaching if (typeMirror.isNullable != _isNullable || typeMirror.isNonNullable != _isNonNullable) { if (_isGenericType) { - return _createInstantiatedGenericClass( + return _createInstantiatedGenericClass( typeMirror as GenericClassMirrorImpl, hasReflectedType ? reflectedType : null, _reflectedTypeArguments, @@ -2409,7 +2422,7 @@ abstract class VariableMirrorBase extends _DataCaching ); } else { var classMirror = typeMirror as NonGenericClassMirrorImpl; - return NonGenericClassMirrorImpl( + return NonGenericClassMirrorImpl( classMirror.simpleName, classMirror.qualifiedName, _descriptor, @@ -2431,7 +2444,7 @@ abstract class VariableMirrorBase extends _DataCaching } } else { return _isGenericType - ? _createInstantiatedGenericClass( + ? _createInstantiatedGenericClass( typeMirror as GenericClassMirrorImpl, hasReflectedType ? reflectedType : null, _reflectedTypeArguments, @@ -2487,7 +2500,7 @@ abstract class VariableMirrorBase extends _DataCaching int get hashCode => simpleName.hashCode ^ owner.hashCode; } -class VariableMirrorImpl extends VariableMirrorBase { +class VariableMirrorImpl extends VariableMirrorBase { @override DeclarationMirror get owner { if (_ownerIndex == noCapabilityIndex) { @@ -2532,7 +2545,7 @@ class VariableMirrorImpl extends VariableMirrorBase { int get hashCode; } -class ParameterMirrorImpl extends VariableMirrorBase +class ParameterMirrorImpl extends VariableMirrorBase implements ParameterMirror { final Object? _defaultValue; @@ -2601,7 +2614,7 @@ class ParameterMirrorImpl extends VariableMirrorBase int get hashCode; } -abstract class SpecialTypeMirrorImpl implements TypeMirror { +abstract class SpecialTypeMirrorImpl implements TypeMirror { @override bool get isPrivate => false; @@ -2637,11 +2650,14 @@ abstract class SpecialTypeMirrorImpl implements TypeMirror { @override List get metadata => []; + + @override + D callWithReflectedType(D Function() f) => f(); } Type _typeOf() => X; -class DynamicMirrorImpl extends SpecialTypeMirrorImpl { +class DynamicMirrorImpl extends SpecialTypeMirrorImpl { @override bool get isNullable => true; @@ -2669,7 +2685,7 @@ class DynamicMirrorImpl extends SpecialTypeMirrorImpl { bool isAssignableTo(TypeMirror other) => true; } -class VoidMirrorImpl extends SpecialTypeMirrorImpl { +class VoidMirrorImpl extends SpecialTypeMirrorImpl { @override bool get isNullable => true; @@ -2702,7 +2718,7 @@ class VoidMirrorImpl extends SpecialTypeMirrorImpl { other is DynamicMirrorImpl || other is VoidMirrorImpl; } -class NeverMirrorImpl extends SpecialTypeMirrorImpl { +class NeverMirrorImpl extends SpecialTypeMirrorImpl { final bool hasQuestionMark; NeverMirrorImpl({this.hasQuestionMark = false}); @@ -2761,8 +2777,8 @@ abstract class ReflectableImpl extends ReflectableBase } @override - InstanceMirror reflect(Object reflectee) { - return _InstanceMirrorImpl(reflectee, this); + InstanceMirror reflect(T reflectee) { + return _InstanceMirrorImpl(reflectee, this); } bool get _hasTypeCapability { diff --git a/packages/reflectable_builder/lib/src/builder_implementation.dart b/packages/reflectable_builder/lib/src/builder_implementation.dart index cd500997..2ee85032 100644 --- a/packages/reflectable_builder/lib/src/builder_implementation.dart +++ b/packages/reflectable_builder/lib/src/builder_implementation.dart @@ -860,6 +860,49 @@ class _ReflectorDomain { return 'const $prefix${_reflector.name}()'; } + /// Returns code for generic type arguments to use in mirror declarations. + /// + /// Generates a string like `` for generic type parameters + /// in mirror constructors. Returns empty string if the type cannot be + /// represented (e.g., private classes, mixin applications, or non-interface + /// types). + Future _typeGenericCode( + _ImportCollector importCollector, { + DartType? type, + InterfaceElement? element, + }) async { + if (type == null && element == null) { + return ''; + } + + if (type != null && type is! InterfaceType) { + return ''; + } + + InterfaceElement classElement = + (type as InterfaceType?)?.element ?? element!; + + // MixinApplications cannot be used as type arguments + if (classElement is MixinApplication) { + return ''; + } + + if (classElement.isPrivate) { + return ''; + } + + String prefix = importCollector._getPrefix(classElement.library); + if (_isPrivateName(classElement.name)) { + await _severe( + 'Cannot access private name `${classElement.name}`', + classElement, + ); + return ''; + } + + return '<$prefix${classElement.name}>'; + } + /// Generate the code which will create a `ReflectorData` instance /// containing the mirrors and other reflection data which is needed for /// `_reflector` to behave correctly. @@ -1706,7 +1749,7 @@ class _ReflectorDomain { } if (interfaceElement.typeParameters.isEmpty) { - return "r.NonGenericClassMirrorImpl(r'${classDomain._simpleName}', " + return "r.NonGenericClassMirrorImpl${await _typeGenericCode(importCollector, element: classDomain._interfaceElement)}(r'${classDomain._simpleName}', " "r'${_qualifiedName(interfaceElement)}', $descriptor, $classIndex, " '${await _constConstructionCode(importCollector)}, ' '$declarationsCode, $instanceMembersCode, $staticMembersCode, ' @@ -1792,7 +1835,7 @@ class _ReflectorDomain { typedefs, ); - return "r.GenericClassMirrorImpl(r'${classDomain._simpleName}', " + return "r.GenericClassMirrorImpl${await _typeGenericCode(importCollector, element: classDomain._interfaceElement)}(r'${classDomain._simpleName}', " "r'${_qualifiedName(interfaceElement)}', $descriptor, $classIndex, " '${await _constConstructionCode(importCollector)}, ' '$declarationsCode, $instanceMembersCode, $staticMembersCode, ' @@ -1889,7 +1932,7 @@ class _ReflectorDomain { _generatedLibraryId, ) : null; - return "r.MethodMirrorImpl(r'${element.name}', $descriptor, " + return "r.MethodMirrorImpl${await _typeGenericCode(importCollector, type: element.returnType)}(r'${element.name}', $descriptor, " '$ownerIndex, $returnTypeIndex, $reflectedReturnTypeIndex, ' '$dynamicReflectedReturnTypeIndex, ' '$reflectedTypeArgumentsOfReturnType, $parameterIndicesCode, ' @@ -1950,7 +1993,7 @@ class _ReflectorDomain { // it is a `List`, which has no other natural encoding. metadataCode = null; } - return "r.VariableMirrorImpl(r'${element.name}', $descriptor, " + return "r.VariableMirrorImpl${await _typeGenericCode(importCollector, type: element.type)}(r'${element.name}', $descriptor, " '$ownerIndex, ${await _constConstructionCode(importCollector)}, ' '$classMirrorIndex, $reflectedTypeIndex, ' '$dynamicReflectedTypeIndex, $reflectedTypeArguments, ' @@ -2011,7 +2054,7 @@ class _ReflectorDomain { // it is a `List`, which has no other natural encoding. metadataCode = null; } - return "r.VariableMirrorImpl(r'${element.name}', $descriptor, " + return "r.VariableMirrorImpl${await _typeGenericCode(importCollector, type: element.type)}(r'${element.name}', $descriptor, " '$ownerIndex, ${await _constConstructionCode(importCollector)}, ' '$classMirrorIndex, $reflectedTypeIndex, ' '$dynamicReflectedTypeIndex, $reflectedTypeArguments, $metadataCode)'; @@ -2664,7 +2707,7 @@ class _ReflectorDomain { ? '#${element.name}' : 'null'; - return "r.ParameterMirrorImpl(r'${element.name}', $descriptor, " + return "r.ParameterMirrorImpl${await _typeGenericCode(importCollector, type: element.type)}(r'${element.name}', $descriptor, " '$ownerIndex, ${await _constConstructionCode(importCollector)}, ' '$classMirrorIndex, $reflectedTypeIndex, $dynamicReflectedTypeIndex, ' '$reflectedTypeArguments, $metadataCode, $defaultValueCode, ' diff --git a/packages/test_reflectable/test/runtime_type_access_test.dart b/packages/test_reflectable/test/runtime_type_access_test.dart new file mode 100644 index 00000000..cfc642d9 --- /dev/null +++ b/packages/test_reflectable/test/runtime_type_access_test.dart @@ -0,0 +1,169 @@ +// Copyright (c) 2025, 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. + +// Tests the new runtime type access feature through callWithReflectedType. + +library test_reflectable.test.runtime_type_access_test; + +import 'package:test/test.dart'; +import 'package:reflectable/reflectable.dart' as r; +import 'package:reflectable/capability.dart'; +import 'runtime_type_access_test.reflectable.dart'; + +// ignore_for_file: omit_local_variable_types + +class RuntimeTypeReflectable extends r.Reflectable { + const RuntimeTypeReflectable() + : super(typeRelationsCapability, typeCapability, reflectedTypeCapability); +} + +const runtimeTypeReflectable = RuntimeTypeReflectable(); + +@runtimeTypeReflectable +class TestClass { + String getValue() => 'TestClass'; +} + +@runtimeTypeReflectable +class GenericClass { + T? value; + + GenericClass([this.value]); + + String getTypeName() => T.toString(); + + T? getValue() => value; + + void setValue(T newValue) { + value = newValue; + } +} + +@runtimeTypeReflectable +class StringClass extends GenericClass { + StringClass([super.value]); +} + +@runtimeTypeReflectable +class IntClass extends GenericClass { + IntClass([super.value]); +} + +// Helper function to test type access +R testTypeAccess(R Function() callback) { + return callback(); +} + +void main() { + setUp(() { + initializeReflectable(); + }); + + group('Runtime Type Access Tests', () { + test('callWithReflectedType on simple class', () { + TestClass instance = TestClass(); + r.InstanceMirror instanceMirror = runtimeTypeReflectable.reflect( + instance, + ); + r.ClassMirror classMirror = instanceMirror.type; + + String result = classMirror.callWithReflectedType(() { + expect(T, equals(TestClass)); + return 'ok'; + }); + + expect(result, equals('ok')); + }); + + test('callWithReflectedType on generic class with String', () { + StringClass instance = StringClass('test'); + r.InstanceMirror instanceMirror = runtimeTypeReflectable.reflect( + instance, + ); + r.ClassMirror classMirror = instanceMirror.type; + + bool isString = classMirror.callWithReflectedType(() { + return T == StringClass; + }); + + expect(isString, isTrue); + }); + + test('callWithReflectedType on generic class with int', () { + IntClass instance = IntClass(42); + r.InstanceMirror instanceMirror = runtimeTypeReflectable.reflect( + instance, + ); + r.ClassMirror classMirror = instanceMirror.type; + + String typeName = classMirror.callWithReflectedType(() { + return T.toString(); + }); + + expect(typeName, equals('IntClass')); + }); + + test('callWithReflectedType with Type comparison', () { + TestClass instance = TestClass(); + r.InstanceMirror instanceMirror = runtimeTypeReflectable.reflect( + instance, + ); + r.ClassMirror classMirror = instanceMirror.type; + + Type actualType = classMirror.callWithReflectedType(() { + return T; + }); + + expect(actualType, same(TestClass)); + expect(instance.runtimeType, same(actualType)); + }); + + test('callWithReflectedType on TypeMirror from reflectType', () { + r.TypeMirror typeMirror = runtimeTypeReflectable.reflectType(TestClass); + + bool isCorrectType = typeMirror.callWithReflectedType(() { + return T == TestClass; + }); + + expect(isCorrectType, isTrue); + }); + + test('callWithReflectedType with generic function behavior', () { + StringClass stringInstance = StringClass('hello'); + IntClass intInstance = IntClass(123); + + r.InstanceMirror stringMirror = runtimeTypeReflectable.reflect( + stringInstance, + ); + r.InstanceMirror intMirror = runtimeTypeReflectable.reflect(intInstance); + + String stringResult = stringMirror.type.callWithReflectedType( + () { + return T == StringClass ? 'String specialization' : 'Unexpected'; + }, + ); + + String intResult = intMirror.type.callWithReflectedType(() { + return T == IntClass ? 'Int specialization' : 'Unexpected'; + }); + + expect(stringResult, equals('String specialization')); + expect(intResult, equals('Int specialization')); + }); + + test('callWithReflectedType preserves generic type information', () { + GenericClass genericString = GenericClass('test'); + r.InstanceMirror instanceMirror = runtimeTypeReflectable.reflect( + genericString, + ); + r.ClassMirror classMirror = instanceMirror.type; + + Type genericType = classMirror.callWithReflectedType(() { + return T; + }); + + expect(genericType.toString(), contains('GenericClass')); + }); + }); +}