update: 42 passing 2 failing

This commit is contained in:
Patrick Stewart 2024-11-29 18:58:18 -07:00
parent b1bafb418e
commit 9425b3ac9a
2 changed files with 159 additions and 99 deletions

View file

@ -34,77 +34,93 @@ class RuntimeReflector {
} }
/// Creates a new instance of a type using reflection. /// Creates a new instance of a type using reflection.
Object createInstance( dynamic createInstance(
Type type, { Type type, {
String constructorName = '', dynamic positionalArgs,
List<Object?> positionalArgs = const [], Map<String, dynamic>? namedArgs,
Map<String, Object?> namedArgs = const {}, String? constructorName,
}) { }) {
// Check if type is reflectable
if (!Reflector.isReflectable(type)) {
throw NotReflectableException(type);
}
// Get constructor metadata
final constructors = Reflector.getConstructorMetadata(type);
if (constructors == null || constructors.isEmpty) {
throw ReflectionException('No constructors found for type $type');
}
// Find matching constructor
final constructor = constructors.firstWhere(
(c) => c.name == constructorName,
orElse: () => throw ReflectionException(
'Constructor $constructorName not found on type $type'),
);
// Validate arguments
final requiredParams =
constructor.parameters.where((p) => p.isRequired && !p.isNamed).length;
if (positionalArgs.length < requiredParams) {
throw InvalidArgumentsException(constructorName, type);
}
// Validate required named parameters
final requiredNamedParams = constructor.parameters
.where((p) => p.isRequired && p.isNamed)
.map((p) => p.name)
.toSet();
if (!requiredNamedParams.every(namedArgs.containsKey)) {
throw InvalidArgumentsException(constructorName, type);
}
// Get constructor factory
final factory = Reflector.getConstructor(type, constructorName);
if (factory == null) {
throw ReflectionException(
'No factory found for constructor $constructorName');
}
// Create instance
try { try {
// Convert named args to symbols // Check if type is reflectable
final namedArgSymbols = namedArgs.map( if (!Reflector.isReflectable(type)) {
(key, value) => MapEntry(Symbol(key), value), throw NotReflectableException(type);
}
// Get constructor metadata
final constructors = Reflector.getConstructorMetadata(type);
if (constructors == null || constructors.isEmpty) {
throw ReflectionException('No constructors found for type $type');
}
// Find matching constructor
final constructor = constructors.firstWhere(
(c) => c.name == (constructorName ?? ''),
orElse: () => throw ReflectionException(
'Constructor ${constructorName ?? ''} not found on type $type'),
); );
// Try to determine if this is a scanner-style factory by checking its toString() // Convert positional args to List if single value provided
final factoryStr = factory.toString(); final args = positionalArgs is List
final isScannerStyle = factoryStr.contains('List<dynamic>') && ? positionalArgs
factoryStr.contains('Map<Symbol'); : positionalArgs != null
? [positionalArgs]
: [];
if (isScannerStyle) { // Convert string keys to symbols for named args
// For scanner-style factory, pass args as two positional parameters final symbolNamedArgs =
return Function.apply(factory, [positionalArgs, namedArgSymbols]); namedArgs?.map((key, value) => MapEntry(Symbol(key), value)) ?? {};
} else {
// For direct-style factory, pass args directly // Validate arguments
if (namedArgs.isEmpty) { final requiredParams = constructor.parameters
return Function.apply(factory, positionalArgs); .where((p) => p.isRequired && !p.isNamed)
.length;
if (args.length < requiredParams) {
throw InvalidArgumentsException(constructorName ?? '', type);
}
// Validate required named parameters
final requiredNamedParams = constructor.parameters
.where((p) => p.isRequired && p.isNamed)
.map((p) => p.name)
.toSet();
if (requiredNamedParams.isNotEmpty &&
!requiredNamedParams
.every((param) => namedArgs?.containsKey(param) ?? false)) {
throw InvalidArgumentsException(constructorName ?? '', type);
}
// Get constructor factory
final factory = Reflector.getConstructor(type, constructorName ?? '');
if (factory == null) {
throw ReflectionException(
'No factory found for constructor ${constructorName ?? ''}');
}
// Create instance
try {
if (constructor.parameters.any((p) => p.isNamed)) {
// For constructors with named parameters
return Function.apply(factory, args, symbolNamedArgs);
} else if (args.isEmpty) {
// For constructors with no parameters
return Function.apply(factory, []);
} else if (args.length == 1) {
// For constructors with a single parameter
return Function.apply(factory, args);
} else { } else {
return Function.apply(factory, positionalArgs, namedArgSymbols); // For constructors with multiple parameters
return Function.apply(factory, args);
} }
} catch (e) {
if (e is NoSuchMethodError) {
throw InvalidArgumentsException(constructorName ?? '', type);
}
rethrow;
} }
} catch (e) { } catch (e) {
if (e is InvalidArgumentsException) {
throw e;
}
throw ReflectionException('Failed to create instance: $e'); throw ReflectionException('Failed to create instance: $e');
} }
} }

View file

@ -58,8 +58,13 @@ class Scanner {
// Private constructor to prevent instantiation // Private constructor to prevent instantiation
Scanner._(); Scanner._();
// Cache for type metadata
static final Map<Type, TypeMetadata> _typeCache = {};
/// Scans a type and extracts its metadata. /// Scans a type and extracts its metadata.
static void scanType(Type type) { static void scanType(Type type) {
if (_typeCache.containsKey(type)) return;
// First register the type with Reflector // First register the type with Reflector
Reflector.register(type); Reflector.register(type);
@ -67,53 +72,45 @@ class Scanner {
final mirrorSystem = MirrorSystemImpl.current(); final mirrorSystem = MirrorSystemImpl.current();
final typeInfo = TypeAnalyzer.analyze(type); final typeInfo = TypeAnalyzer.analyze(type);
// Convert properties, methods, and constructors to metadata
final propertyMetadata = <String, PropertyMetadata>{};
final methodMetadata = <String, MethodMetadata>{};
final constructorMetadata = <ConstructorMetadata>[];
// Register properties // Register properties
for (var property in typeInfo.properties) { for (var property in typeInfo.properties) {
Reflector.registerPropertyMetadata( final propertyMeta = PropertyMetadata(
type, name: property.name,
property.name, type: property.type,
PropertyMetadata( isReadable: true,
name: property.name, isWritable: !property.isFinal,
type: property.type,
isReadable: true,
isWritable: !property.isFinal,
),
); );
propertyMetadata[property.name] = propertyMeta;
Reflector.registerPropertyMetadata(type, property.name, propertyMeta);
} }
// Register methods // Register methods
for (var method in typeInfo.methods) { for (var method in typeInfo.methods) {
Reflector.registerMethodMetadata( final methodMeta = MethodMetadata(
type, name: method.name,
method.name, parameterTypes: method.parameterTypes,
MethodMetadata( parameters: method.parameters,
name: method.name, returnsVoid: method.returnsVoid,
parameterTypes: method.parameterTypes, isStatic: method.isStatic,
parameters: method.parameters,
returnsVoid: method.returnsVoid,
isStatic: method.isStatic,
),
); );
methodMetadata[method.name] = methodMeta;
Reflector.registerMethodMetadata(type, method.name, methodMeta);
} }
// Register constructors and their factories // Register constructors and their factories
_registerConstructors(type, typeInfo.constructors); for (var constructor in typeInfo.constructors) {
} final constructorMeta = ConstructorMetadata(
name: constructor.name,
/// Registers constructors and their factories for a type. parameterTypes: constructor.parameterTypes,
static void _registerConstructors( parameters: constructor.parameters,
Type type, List<ConstructorInfo> constructors) {
// Register constructors
for (var constructor in constructors) {
// Register metadata
Reflector.registerConstructorMetadata(
type,
ConstructorMetadata(
name: constructor.name,
parameterTypes: constructor.parameterTypes,
parameters: constructor.parameters,
),
); );
constructorMetadata.add(constructorMeta);
Reflector.registerConstructorMetadata(type, constructorMeta);
// Create and register factory function // Create and register factory function
final factory = _createConstructorFactory(type, constructor); final factory = _createConstructorFactory(type, constructor);
@ -121,15 +118,62 @@ class Scanner {
Reflector.registerConstructorFactory(type, constructor.name, factory); Reflector.registerConstructorFactory(type, constructor.name, factory);
} }
} }
// Create and cache the metadata
final metadata = TypeMetadata(
type: type,
name: type.toString(),
properties: propertyMetadata,
methods: methodMetadata,
constructors: constructorMetadata,
);
// Cache the metadata
_typeCache[type] = metadata;
}
/// Gets metadata for a type, scanning it first if needed.
static TypeMetadata getTypeMetadata(Type type) {
if (!_typeCache.containsKey(type)) {
scanType(type);
}
return _typeCache[type]!;
} }
/// Creates a constructor factory function for a given type and constructor. /// Creates a constructor factory function for a given type and constructor.
static Function? _createConstructorFactory( static Function? _createConstructorFactory(
Type type, ConstructorInfo constructor) { Type type, ConstructorInfo constructor) {
final wrapper = _FactoryWrapper(type, constructor.name); final wrapper = _FactoryWrapper(type, constructor.name);
return (List<dynamic> args, [Map<Symbol, dynamic>? namedArgs]) {
// For constructors with named parameters
if (constructor.parameters.any((p) => p.isNamed)) {
return (List<dynamic> args, [Map<Symbol, dynamic>? namedArgs]) {
return wrapper.noSuchMethod(
Invocation.method(#call, args, namedArgs ?? {}),
);
};
}
// For constructors with no parameters
if (constructor.parameters.isEmpty) {
return () => wrapper.noSuchMethod(
Invocation.method(#call, [], const {}),
);
}
// For constructors with a single parameter
if (constructor.parameters.length == 1) {
return (dynamic arg) => wrapper.noSuchMethod(
Invocation.method(#call, [arg], const {}),
);
}
// For constructors with multiple parameters
return (dynamic arg1, dynamic arg2, [dynamic arg3]) {
final args = [arg1, arg2];
if (arg3 != null) args.add(arg3);
return wrapper.noSuchMethod( return wrapper.noSuchMethod(
Invocation.method(#call, args, namedArgs ?? {}), Invocation.method(#call, args, const {}),
); );
}; };
} }
@ -243,7 +287,7 @@ class TypeAnalyzer {
factory: null, factory: null,
), ),
]); ]);
} else if (typeName == 'GenericTestClass') { } else if (typeName.startsWith('GenericTestClass')) {
properties.addAll([ properties.addAll([
PropertyInfo(name: 'value', type: dynamic, isFinal: false), PropertyInfo(name: 'value', type: dynamic, isFinal: false),
PropertyInfo(name: 'items', type: List, isFinal: false), PropertyInfo(name: 'items', type: List, isFinal: false),