-
Notifications
You must be signed in to change notification settings - Fork 214
Can Code objects be introspected? #1933
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
Comments
IMHO I can't see much gain in doing so. Do you see a strong use-case for this? Given the nature of the approach for static meta-programming (macros), I think it could lead to confusion. If we used a AST-based approach, then it would make sense. |
I think it could be useful for dependency tracking. This enables use cases like automatic cache invalidation, machine learning (automatic differentiation), and automatic rebuilds of widgets. Also I know @rrousselGit is interested in this for state management dependency tracking. |
I'd definitely need the ability to inspect the content of functions/expressions. I need to track all references of a variable, and track all method call on that variable (including unresolved method call – which would be generated by a macro) |
As long as we don't try to resolve the code, I don't see any feasibility issues. As soon as we give you a And possibly we could allow you to resolve parts of the code in later macro phases? |
@jakemac53 I think that is a good solution. On top of that minimal API macro authors could create a minimal AST parser for different language versions if they need more info, and the dart authors don't need to worry about breaking macros, instead it places breaking changes to the AST as a burden on the external AST package and macro authors. Along with that it would be useful for macros to know what language version they are running on, so that they can emit a warning if the language version of the user's code might cause errors in the macro. (such as when they depend on the AST structure). Obviously simple macros should avoid the AST altogether in order to minimize breakage, but more advanced ones can emit warnings that they do not claim to support newer language versions. I also think being able to resolve identifiers in later macro phases would then be very useful. DartType builder.typeOf(String identifier) or something |
If we don't resolve it, is it useful to introspect on it? The use cases I've seen are around things like tracking dependencies, but that only works reliably if you know what identifiers refer to.
I think this is the main potential downside. If we treat Code objects as opaque, then the API is basically invariant even as the language syntax evolves. If we expose any destructuring API, then we have to figure out how that API handles language changes. We could do that by making it very minimal and essentially dynamically typed. That at least means language changes might not statically break the API. But any macro expecting certain properties to exist on some syntax node could still fail at execution time if the property isn't there. I'm not convinced that that's a net win. I think unless we have solid use cases, we should probably consider Code objects to be essentially write-only. You can build and compose them, but not introspect or decompose them. |
Do we have use cases for making them introspectable outside of statement/expression level macros? Possibly we could just punt on this until we have the time to evaluate those. |
The only one I know offhand is the request to hunt through the body of a function to find dependencies for observability. I'm OK with kicking this down the road. |
What does "kicking this down the road" means? As I'll definitely need this for dependency tracking. Considering we can do that using build_runner today, it'd be a bit unexpected IMO to no-longer be able to, |
Basically just deciding on this later when we are further along with more fundamental design questions and implementation feasibility studies. I think we can decide whether or not the feature needs to support this after making other decisions since the overall design doesn't hinge on it. |
Coming back to this, another use-case I have is making some runtime errors compile errors instead In particular, one case I have is, users may define the following: @Macro(flag: true)
void a(Class class) {}
@Macro()
void b(Class class) {
class.doSomething(a);
} And I would like the line:
to fail to compile because @Macro()
void a(Class class) {}
@Macro()
void b(Class class) {
class.doSomething(a);
} or: @Macro(flag: true)
void a(Class class) {}
@Macro(flag: true)
void b(Class class) {
class.doSomething(a);
} There are some workarounds to achieve this using the type system (by having a Riverpod managed to do this without macros, but it caused Riverpod to have 50+ public classes instead of ~20 (see its API reference https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html). That's significant pollution of the public API just for a single compilation error. And the error message is fairly unclear too with no way of customizing it. It currently is something among the lines of:
It's a fairly obscure error with no way of clarifying it. No beginner seeing this compilation error will naturally guess what the fix to this error it If Code objects could be introspected, then Riverpod's public API could be cut in half and the error message could become something much more human-friendly, such as:
|
We've discussed whether there is any API to let you destructure a Code object and introspect over its syntactic subcomponents. For example, given a Code object for
a + b
, is there a way to determine that it's a+
expression and what the two operands are?The text was updated successfully, but these errors were encountered: