diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart index c6bee4b..a147325 100644 --- a/incubation/container/container/lib/src/container.dart +++ b/incubation/container/container/lib/src/container.dart @@ -1033,6 +1033,41 @@ class Container { } } + /// Call a method on a resolved instance using Class@method syntax. + /// + /// This allows you to resolve and call a method in one step: + /// ```dart + /// container.call('Logger@log', ['Hello world']); + /// ``` + dynamic call(String target, [List parameters = const []]) { + var parts = target.split('@'); + if (parts.length != 2) { + throw ArgumentError('Invalid Class@method syntax: $target'); + } + + var className = parts[0]; + var methodName = parts[1]; + + // Find the type by name + var type = reflector.findTypeByName(className); + if (type == null) { + throw ArgumentError('Class not found: $className'); + } + + // Resolve the instance + var instance = make(type); + + // Find and call the method + var method = reflector.findInstanceMethod(instance, methodName); + if (method == null) { + throw ArgumentError('Method not found: $methodName on $className'); + } + + return method + .invoke(Invocation.method(Symbol(methodName), parameters)) + .reflectee; + } + /// Check if we're in danger of a circular dependency. void _checkCircularDependency(Type type) { if (_buildStack.contains(type)) { diff --git a/incubation/container/container/lib/src/reflector.dart b/incubation/container/container/lib/src/reflector.dart index 3b72136..a9bd47c 100644 --- a/incubation/container/container/lib/src/reflector.dart +++ b/incubation/container/container/lib/src/reflector.dart @@ -43,6 +43,22 @@ abstract class Reflector { ReflectedType reflectFutureOf(Type type) { throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.'); } + + /// Find a type by its name. + /// + /// This method is used to support Class@method syntax by finding a type + /// from its string name. + Type? findTypeByName(String name) { + throw UnsupportedError('`findTypeByName` requires `dart:mirrors`.'); + } + + /// Find an instance method by its name. + /// + /// This method is used to support Class@method syntax by finding a method + /// on an instance from its string name. + ReflectedFunction? findInstanceMethod(Object instance, String methodName) { + throw UnsupportedError('`findInstanceMethod` requires `dart:mirrors`.'); + } } /// Represents a reflected instance of an object. @@ -241,6 +257,9 @@ abstract class ReflectedFunction { other.isGetter == isGetter && other.isSetter == isSetter; + /// Invoke this function with the given invocation. + /// + /// This method is used to support dynamic method invocation. ReflectedInstance invoke(Invocation invocation); } diff --git a/incubation/container/container/test/class_method_syntax_test.dart b/incubation/container/container/test/class_method_syntax_test.dart new file mode 100644 index 0000000..769fc4a --- /dev/null +++ b/incubation/container/container/test/class_method_syntax_test.dart @@ -0,0 +1,151 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +class Logger { + String log(String message) => message; + void debug(String message, {int? level}) {} + int count(List items) => items.length; +} + +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) => null; + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); + + @override + Type? findTypeByName(String name) { + if (name == 'Logger') return Logger; + return null; + } + + @override + ReflectedFunction? findInstanceMethod(Object instance, String methodName) { + if (instance is Logger) { + switch (methodName) { + case 'log': + return MockMethod('log', (invocation) { + var args = invocation.positionalArguments; + return MockReflectedInstance(instance.log(args[0] as String)); + }); + case 'debug': + return MockMethod('debug', (invocation) { + var args = invocation.positionalArguments; + instance.debug(args[0] as String); + return MockReflectedInstance(null); + }); + case 'count': + return MockMethod('count', (invocation) { + var args = invocation.positionalArguments; + return MockReflectedInstance( + instance.count(args[0] as List)); + }); + } + } + return null; + } +} + +class MockMethod implements ReflectedFunction { + final String methodName; + final ReflectedInstance Function(Invocation) handler; + + MockMethod(this.methodName, this.handler); + + @override + List get annotations => []; + + @override + bool get isGetter => false; + + @override + bool get isSetter => false; + + @override + String get name => methodName; + + @override + List get parameters => []; + + @override + ReflectedType? get returnType => null; + + @override + List get typeParameters => []; + + @override + ReflectedInstance invoke(Invocation invocation) => handler(invocation); +} + +class MockReflectedInstance implements ReflectedInstance { + final dynamic value; + + MockReflectedInstance(this.value); + + @override + ReflectedClass get clazz => throw UnimplementedError(); + + @override + ReflectedInstance getField(String name) => throw UnimplementedError(); + + @override + dynamic get reflectee => value; + + @override + ReflectedType get type => throw UnimplementedError(); +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + container.registerSingleton(Logger()); + }); + + group('Class@method syntax', () { + test('can call method with return value', () { + var result = container.call('Logger@log', ['Hello world']); + expect(result, equals('Hello world')); + }); + + test('can call void method', () { + expect(() => container.call('Logger@debug', ['Debug message']), + returnsNormally); + }); + + test('can call method with list parameter', () { + var result = container.call('Logger@count', [ + ['one', 'two', 'three'] + ]); + expect(result, equals(3)); + }); + + test('throws on invalid syntax', () { + expect(() => container.call('Logger'), throwsArgumentError); + expect(() => container.call('Logger@'), throwsArgumentError); + expect(() => container.call('@log'), throwsArgumentError); + }); + + test('throws on unknown class', () { + expect(() => container.call('Unknown@method'), throwsArgumentError); + }); + + test('throws on unknown method', () { + expect(() => container.call('Logger@unknown'), throwsArgumentError); + }); + }); +}