google / google/reflectable.dart
Support dynamic discovery of actual type arguments
- Dominant language
- Dart
- Stars
- 386
- Forks
- 67
- PR merge metrics
- No merged PRs in 30d
Description
Reflectable (like any other approach to Dart reflection based on code generation) cannot obtain the actual type arguments of a given instance:
```dart
class A {}
class B implements A {}
As a consequence, the following will execute both print statements:
```dart
import 'package:reflectable/reflectable.dart';
import 'main.reflectable.dart';
class Reflector extends Reflectable {
const Reflector() : super(invokingCapability, typingCapability,
reflectedTypeCapability);
}
const Reflector reflector = const Reflector();
@reflector
class SecurityService {}
@reflector
class SpecialSecurityService implements SecurityService {}
@reflector
class MyGenericService {}
main() {
initializeReflectable();
MyGenericService service = MyGenericService();
InstanceMirror instanceMirror = reflector.reflect(service);
ClassMirror classMirror = instanceMirror.type;
try {
classMirror.typeArguments;
} on UnimplementedError catch (_) {
print("Cannot obtain actual type arguments as mirrors.");
}
try {
classMirror.reflectedTypeArguments;
} on UnimplementedError catch (_) {
print("Cannot obtain actual type arguments as `Type` values.");
}
}
```
Until now, this feature (the ability to extract actual type arguments from a given instance) has been marked as unimplemented, stating that we cannot get this feature until some additional primitives are made available. That is still true, in the general case.
However, we could adopt an approach where the developer of a given class could opt in to provide support for extracting actual type arguments, because that's easy to do as soon as we allow the implementation to exist in the target class:
```dart
class SecurityService {}
class SpecialSecurityService implements SecurityService {
void special() => print('Doing very special stuff!');
}
class Client {
X service;
Client(this.service);
Y openClient(Y Function(Client) f) {
return f(this);
}
}
main() {
final Client exactlyTypedClient =
Client(SpecialSecurityService());
// Forget the actual type argument.
final Client client = exactlyTypedClient;
// Create a function which can be used to open a client.
void showActualTypeArgument(Client self)
=> print(X);
// Prints 'SpecialSecurityService': Accesses the actual value of `X`.
client.openClient(showActualTypeArgument);
// We may even use `X` as a type.
client.openClient((self) {
X service = self.service;
self.service = service;
// We can't statically establish that `X <: SpecialSecurityService`,
// but we can of course still perform a direct type test.
if (service is SpecialSecurityService) {
// Promotion gives `service` an intersection type such that we can
// both use the features of `SpecialSecurityService` and preserve the
// relationship to `X`.
service.special();
X xService = service; // Accepted also with `--no-implicit-casts`.
}
});
}
```
We would then check for the existence of a method named `openC` during processing of any given class `C` which has a suitable signature (that is, it must declare the same type argument list as `C`, and it must accept an argument which can be `this`, with the most specific type), and the code generated by reflectable would then be able to use those `open...` methods in generated code.
It is also possible that this won't help enough to be worthwhile, but this issue serves to discuss that and reach a decision.
Finally, you could of course say that reflectable can just edit the code of existing classes (that's also a kind of code generation, you are just generating a modified variant of "the whole universe" of libraries that a given program uses), but we opted out of doing any such thing a long time ago:
- First, it will not work for entities that have no code (and nobody guarantees that `dynamic` or `int` exist as declarations in Dart, and even if they do, we can't expect to be able to "edit the code of `int`" and still make compiled programs work, because the runtimes can make special assumptions about such built-in classes).
- Second, the `build` package obtains a number of good properties from the design decision of only generating fresh code, never editing existing code.
Contributor guide
Assessment
This issue has not been assessed yet.