-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Macros: Could ClassTypeBuilder
have access to user-defined class methods?
#3809
Comments
This is critical for being able to migrate Riverpod to macros. Maybe we could have
|
I do think it is a critical part of the macros feature that macros can compose well - and that means supporting other macros generating the That being said it should be possible to allow an API which only gives you the user defined methods, or maybe we would specify it more specifically as the pre-augmentation members (which would also mean not including explicit hand written augmentations). From a technical perspective that should be safe to do. |
The whole point of this macro is so that users don't write this I do agree that is it valuable for macros is to be composable. But I don't think that should be a strong requirement for all macro APIs. Otherwise I'd just not be able to upgrade my current syntax to macros at all, and would have to find an API that's likely worse for user (such as with code duplicates or more verbose). |
Thinking about it, Freezed would have the same issue. |
Alternatively, what is the reason why the "declaration" phase can't emit classes? |
Because it is allowed to resolve types to their declarations. If macros in this phase could emit new types, it could change how types resolve (maybe changing an error to a success, or maybe a success to a totally different declaration in the case of shadowing). We try hard to eliminate the cases where a macro might ask a question and get a wrong (or inconsistent) answer. Sometimes, the answer can be incomplete (when asking for members in the declarations phase), but this is the one exception today. |
Are the generated types intended to be user visible? Or would they always be private to this library, and in particular private to code generated by this macro? We have considered, and I think maybe the proposal even mentions, the ability to emit macro-private code in later phases, that otherwise wouldn't be allowed. This code would also not be visible to other macros ever. |
(I deleted my previous message, I think that was incorrect). My classes may be public. But they aren't used by others. I'm supposed to be the sole consumer of those types, but they are technically visible. Given: @riverpod
class Foo {
int build();
} We have: const fooProvider = FooProvider<int>._(Foo._);
class FooProvider extends Provider<int> {
// Only my macro can instantiate this new class, but it is public.
const FooProvider._(this._create);
[...]
}
augment class Foo with Notifier<int> {
// Only my macro is allowed to instantiate the annotated class.
Foo._(this.ref);
@override
final Ref<int> ref;
} |
Fun fact: My macro can also be applied on functions, like so: @riverpod
int foo() {
}
// generates:
const fooProvider = FooProvider<int>._(foo);
class FooProvider extends Provider<int> {
// Only my macro can instantiate this new class, but it is public.
const FooProvider._(this._create);
[...]
} But in this scenario, I have no issue. Because But I need to support classes too. |
I'm thing I'm currently looking into is doing: @Riverpod<int>()
class Foo {
int build();
} Although I don't see a way to convert that generic into an Identifier. And I quite dislike how this involves duplicating the type information. |
Yes generic type parameters won't work for macros because we can't take the types from the users program and materialize them in the macro program. The intention is to pass these as regular arguments, and have them coerced into a TypeAnnotation implicitly, but that isn't implemented yet. |
I see thanks. With regards to removing the code duplication: Would it be possible to have users declare @Riverpod(int)
class Foo {
build() => ...;
}
augment class Foo {
augment int build();
} This doesn't work yet afaik. But I wonder if that would be reasonable? 🤔 |
We don't allow augmentations to change the type of something pre-existing, so no we wouldn't allow this. Same reason we don't allow adding optional parameters. I get why that is limiting, but it is an intentional design choice for readability/usability (fwiw, allowing adding mixins/extends etc I think violates this principle but it was a compromise to allow it, in limited ways). If all these classes are implementing a
|
No. The prototype of In particular, the following is valid too: @Riverpod();
class Foo {
Future<int> build(int a, {required int named, String optional = 42}) {...}
} Which then generates an object used as: ref.watch(fooProvider(42, named: 42)); So there's no shared interface. |
Although in this case, we're not replacing something existing. We're specifying something that wasn't specified yet. Kind of like how we support: @macro
int fn(); Then followed by an augmentation that adds a body. (... although this is unimplemented. I assume this will be supported, right?) |
Omitting a type annotation on a function means it is inferred - and augmentations are not allowed to affect inference (by removing it through making the type explicit or altering the inferred type). The only actual inference that can take place for return types of methods afaik is copying the type from the super declaration if one exists, otherwise you are getting In other words, while a user did not write a type there, that does not mean there is no type. They just didn't declare the type explicitly. Similarly, augmentations cannot add types to parameters where one was not declared initially, etc. |
Sounds like you're saying that there's no easy way forward. Do you think it is realistic to expect |
I don't think we want to put things directly on the |
Awesome, thanks! |
I was also experimenting with macros and came across this limitation. The ability to get constructors, methods and fields, even if only user-defined, in the type phase seems pretty important. I think this is necessary to migrate users of some packages from build_runner to the macro system without pain |
Hello again!
It appears that there is no way for a
ClassTypeBuilder
to obtain the methods of the annotated class.This is particularly problematic in my case, because I need to add a mixin to the class that's based on the return value of a method present in said class.
Consider:
I want to generate:
The problem is, both the newly declared type and the mixin applied on the existing type depends on the return value of that
build
method (and the output is not constant. There's some logic around it).So I need to find that
build
method in a phase that's able to add new types and mixin. But there doesn't seem to be a way to do so, as far as I know.For information, it is an error if users do not define that
build
method. And I don't care about cases where thatbuild
method is coming from a different macro or a mixins or a subclass. Thatbuild
method should always be directly specified by users.The text was updated successfully, but these errors were encountered: