-
Notifications
You must be signed in to change notification settings - Fork 214
Define the macro API for introspecting on metadata annotations (and enum values) #1930
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
I think we should basically do what the analyzer does here - provide a |
From a user's perspective, it would be very helpful to access annotations that depend on constant macro-produced types and declarations. Use CaseSay I have: class InfoAnnotation {
const InfoAnnotation(Object obj);
}
@someFunctionDeclarationMacro
void fn(
@InfoAnnotation(macroProducedObj) int someIntParameter,
) {} In the above example, The ProblemI think I see why this would be problematic as-is: there is no way to know for certain whether this Would it be possible to hint at the order in which some phase 2 macros are evaluated to allow for this? I would hate to have to resort to build_runner to handle something like this. Proposed SolutionI have what I think is a good solution/compromise: a macro can directly specify what other macros it "depends on." I.e., In the example above, Seeing as a macro that that depends on declarations produced by another macro would (in most cases) have access to the other macro, the above solution makes a lot of sense to me. It would act just like dependency resolution, giving a deterministic way to run macros in phase 2. You could even give users a helpful warning/error at compile time hinting that their macro should depend on another macro if the declaration the first macro needed was created later on in phase 2. Could look something like: macro class IntroducesSomeDeclaration {
// ...
}
@DependsOnMacros([IntroducesSomeDeclaration])
macro class UsesAnnotationWithSomeDeclaration {
// ...
} Anti-SolutionAn anti-solution would be for a macro class to ask to be run at a certain point in phase 2, perhaps even a "phase 2.5." This is non-deterministic, and because of that, nothing like this (IMO) should be supported. |
I actually just realized my own use case relies on having the macro depend on other instances where it was applied; which is an issue. But, I know exactly what I want to declare in phase 1 given the Or, if you go the unevaluated syntax route mentioned above, would we be able to spit that out (as a string) in a code declaration elsewhere verbatim? For my case, I don’t really need to know anything about the metadata other than the name (as a string) of the object it contains. It would be nice to have other information, but I could get by without. Ideally, in a final API, I’d love to be able to do either of what I just mentioned. I can give a code example of each tomorrow to help illustrate what I’m suggesting |
Question: Is the We do already have a general mechanism for taking an unresolved piece of code, and emitting that unresolved code in your own generated code. It is better than just copying the syntax because it will actually ensure all the contained identifiers resolve to the same thing as the original (the augmentation library where macro code goes has its own scope, so this is necessary). |
Either is possible. It could be specified in the same library, or it could be specified in an imported library (which the user must // library a.dart
@myMacro // generates: const someIntObject = ...;
int someInt() => 0;
// library b.dart
import 'a.dart';
@myMacro // generates: const someIntPlusOneObject = ...;
int someIntPlusOne(
@C(someIntObject) int i,
) => i + 1;
@myMacro // generates: const someStrObject = ...;
String someStr(
@C(someIntPlusOneObject) int i,
) => i.toString();
// From some other package:
macro class MyMacro {...}
class C {
const C(this.object);
final MyCustomObject object;
} In the above example,
int _userDefinedFunction(
@C(contentsOfTheAnnotation) int a,
@C(contentsOfAnotherParameterAnnotation) int b,
) => 0;
int _$generatedFunction() {
final d1 = doSomething(contentsOfTheAnnotation);
final d2 = doSomething(contentsOfAnotherParameterAnnotation);
return _userDefinedFunction(d1, d2);
} |
If all you need is access to the code chunk representing the argument to the annotation, then yes this should be feasible, regardless of if its referencing generated identifiers or not. But note that annotations can only reference const objects, so that may be a limitation you run into as well. |
Great, thanks! Gives me good faith for a package I'm working on based around macros. The current prototype implementation is already using |
Not sure if this is the right issue for this question (putting it here because it is related to accessing a member of an annotation/macro), but say I have a macro like: macro class MyMacro<T, R> {
const MyMacro(this.func);
final R Function(T) func;
// ...
} Would I then be able to introspect on the Specifically, can we introspect/use Alternatively, if that is not possible, could we pass in an instance of a class type or similar into a macro and then be able to introspect on it, like: class A {}
macro class MyMacro {
const MyMacro(this.clazz);
final Type clazz;
}
@MyMacro(A)
extern void fn(); |
@GregoryConrad See https://github.com/dart-lang/language/blob/master/working/macros/feature-specification.md#macro-arguments which describes how arguments of various types to macros are handled. This is an interesting corner of the proposal to be sure, feel free to ask additional questions about that if you have them. The basic summary though is that for function arguments the only really useful thing you can accept is an |
It looks like we don't talk about generic type parameters for macro classes in the proposal today, I can add a note. For now at least they are going to be disallowed, for the same reasons as not being able to run user defined functions. The program in which the macro is actually running will not have access to types from the users code. |
Updated the title, I was just starting to go through and update the proposal for enhanced enums and I realized enum value arguments are very similar to metadata annotations. Users may want to dig into the values provided to the constructor. |
@johnniwinther As I start working on this, I am wondering if the lazy constant evaluation of some platforms might make it not possible to allow macro authors to get the actual const values of annotations? |
Also related, @johnniwinther @scheglov how would you feel about a general API for evaluating The result would still always be a |
Yet another hiccup I just realized if we do allow const evaluation - const variables might actually be augmented? Or at least we don't block that today. That means the value could change in phase 3 compared to the value previously. I am somewhat leaning towards just blocking augmenting const variables at all? This would mean any const expression can always be evaluated in any phase safely, as long as all the references to variables within it have been defined (I would still allow generating new const variable declarations within augmentations). cc @munificent |
For anybody following along, I have the first PR of two sent out here https://dart-review.googlesource.com/c/sdk/+/313640. This just gives the very basic level of introspection, without const evaluation. Basically enough to know "what kind of metadata is this?". I also chose not to try and give direct access to the arguments, because I think the fields on the evaluated object will be more useful. Const evaluation will be in a followup. |
@jakemac53 Just want to check on something based on what you said here; I recently filed #3210, and was wondering if that would be compatible with the work you're doing here. It'd be nice to be able to re-emit the const object (or in this case function) that is being used as the annotation: void foo(){}
@myMacro
void bar(
@func int foobar,
) {}
// myMacro will be able to re-emit the `func` from the `@func` in its generated code Thanks. |
Yes - that would be somewhat supported already with the first PR I just sent out yesterday. The The main thing missing right now would be the ability to figure out what the type of that identifier is, (ie: make sure it's a function). This is a more general hole I think, we don't have the ability to go from an identifier to its declaration for anything except types. It will take a bit of thought just because we really don't want to allow doing that until phase 3 (not all declarations exist until then), but I will add an issue for it. |
Bug: dart-lang/language#1930 Change-Id: I3ba6facd4c0487b0af18108c8d1db21ee6d5a498 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313640 Auto-Submit: Jake Macdonald <[email protected]> Reviewed-by: Bob Nystrom <[email protected]> Commit-Queue: Jake Macdonald <[email protected]>
Supporting const evaluation of arbitrary metadata for the introspection is a can of worms that I'd rather that we avoided if possible. Yes, it does conflict with lazy evaluation of constants (for the For instance, is this a circular dependency? @patch
class Object {}
class Patch extends Object {
const Patch();
}
const Patch patch = const Patch(); Or this? class Class {
final int value;
const Class(@Class(0) int value) : value = value + 42;
} |
@johnniwinther What if we only allowed evaluating constants from libraries not in the current strongly connected component? There are strong use cases for this, many Builders today use annotations to configure things. But almost always those annotations are defined in the same package as the Builder which should avoid most of these issues. |
Bug: dart-lang/language#1930 Change-Id: I3ba6facd4c0487b0af18108c8d1db21ee6d5a498 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313640 Auto-Submit: Jake Macdonald <[email protected]> Reviewed-by: Bob Nystrom <[email protected]> Commit-Queue: Jake Macdonald <[email protected]>
See #3408 which handles this by allowing general purpose const evaluation with the following constraints:
Hopefully the further restrictions on environment variables and Dart defines also helps here. I need to do a bit more work for the enum values portion though. |
I looked at enum values a bit today, but given that we allow you to fill in the definition of an enum value in the definition phase, I don't think we should give access to them directly in the EnumValue API. We do still need to add a way of getting the arguments for a metadata annotation. It will also still be possible to get the constant value of an enum through const evaluation (if the enum exists in a different strongly connected component). |
Bug: dart-lang/language#1930 Change-Id: I2595ebbdb18f5eabc5e78933ce016be71e66287f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/334680 Auto-Submit: Jake Macdonald <[email protected]> Commit-Queue: Bob Nystrom <[email protected]> Reviewed-by: Bob Nystrom <[email protected]>
Closing in favour of #3847 as this falls into the broader topic of macro metadata. |
The proposal has a section discussing this, but it's written like an open-ended design discussion. We should figure out what we actually want and then pin it down in the proposal. The key question is how arguments in metadata annotations are seen by the macro. Are they actual values, or unevaluated syntax? The former might not be possible (since it may refer to code produced by macros). The latter might not be useful.
The text was updated successfully, but these errors were encountered: