language icon indicating copy to clipboard operation
language copied to clipboard

When can macros inspect the body of functions?

Open rrousselGit opened this issue 3 years ago • 7 comments

cf https://github.com/dart-lang/language/blob/master/working/macros/feature-specification.md#phase-3-definitions

The doc of phase 2 explicitly mentions:

"In this phase, macros declare functions, variables, and members. "Declaring" here means specifying the name and type signature, but not the body of a function or initializer for a variable."

I assume that phase 3 will be able to, but the document doesn't explicitly mentions it. Considering I will need the ability to inspect the body of functions, would it be possible to clarify whether macros will/won't be able to?

rrousselGit avatar Apr 02 '22 13:04 rrousselGit

There is no plan as of right now to allow introspection on the bodies of functions.

I think it should still be feasible, at least to allow you to inspect the body of the immediately annotated function. But note that it means you would not see the augmentations of that function (may be desirable or not). However this would be a large amount of additional API and work to expose.

Am I correct in understanding that the primary use case here is to collect references to instance members in the body of a function? I wonder if we could provide a more explicit api to just get exactly that information?

jakemac53 avatar Apr 04 '22 15:04 jakemac53

Am I correct in understanding that the primary use case here is to collect references to instance members in the body of a function? I wonder if we could provide a more explicit api to just get exactly that information?

That is correct. I have numerous use-cases where I want to statically obtain the list of dependencies of a function.

I don't need the ability to inspect augmentations of that function. My use-case is meant to be statically analyzable.

My current workaround is to write custom analyzer rules:

https://user-images.githubusercontent.com/20165741/174334077-209a4bbb-6106-4026-bb76-333723d8ca39.mov

But that's a bit weird. By relying on lint rules, that dependencies variable is basically generated code that leaves in the userland.

I'd like to be able to straight-up remove that parameter

rrousselGit avatar Jun 17 '22 15:06 rrousselGit

Coming back to this, I have another important use-case for code introspection @jakemac53:

Stateful hot reload

I would like a macro to generate a hash of all the combined source code of a function (including the content invoked). The idea is that this hash would then be used during developement to determine after a hot-reload if the source of a given macro usage has changed

The idea is that the package would rely on a getter/function

@macro
void fn() {

}

// generated
String _$internalToPackage() => 'this is a hash of the source of fn';

A package would then cache those hashes. And whenever a hot-reload is performed, the package would rerun the various hash functions, and compare the previous vs new result. Then, if the result has changed, the state related to fn would be destroyed. But the state related to other usages of the macro would not

A simple Code.toString()wouldn't be enough, as we'd want to support cases like:

@macro
void fn() {
  another();
}

void another() {
  // TODO change the source code of this function
}

We'd want a change in another to change the hash generated for fn. And of course, anothercould be defined in a separate library

It's a feature that I'm about to release in a package using build_runner, which massively improves the developer experience. But I just realised that metaprogramming may not have what's necessary for that


Of course, there are possible alternative solutions. Such as having the SDK officially provide such an hash function Then I guess function introspection could be private to the SDK.

I could see this pattern become quite popular due to the unique benefits it brings.

rrousselGit avatar Jul 25 '22 20:07 rrousselGit