-
Notifications
You must be signed in to change notification settings - Fork 213
add section on macro instantiation and parameters #1861
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
Conversation
af49483
to
9f683f8
Compare
This resolves to some extent jakemac53/macro_prototype#26 via the Is there a reason that |
Correct it does in effect provide the interface for doing that - since you get
Nope, I just took a look through the |
Macros should be able to be able to be instantiated using only the libraries | ||
that are available to the macro definition library. This allows us to compile an | ||
app (or spawn an isolate) that can run macro applications for that macro using | ||
only its own dependencies. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who performs this instantiation? Where do they get the arguments from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the other wording below starts to address this - I don't want to get overly specific as this could be implementation specific? For now its just "the compiler".
- bool | ||
- null (really this just allows nullable types) | ||
- Uri | ||
- DateTime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just allow any value which can be safely deserialized by the isolate which is going to run the macro?
(And make it a compile-time error to specify any argument which can't)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be an interesting approach. It gets complicated though because arguments to macros might themselves require macro expansion in the current library to happen before they are valid. We could make it an error to reference anything from the current library cycle though.
Forcing everything to be literals or provided as a Code object is primarily designed to avoid that problem.
### Macro Constructor Arguments | ||
|
||
When any type other than [Code][] is used, arguments are only allowed to be | ||
object literals. Or in the case of `Uri` and `DateTime` they must be direct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the source code for this macro occurs as an annotation in the library using the macro. This source code is then executed in a separate isolate. Therefore the source code must be valid in that library. Is that correctly understood?
(And the annotation is not available at run-time in the original library).
In that case, I'd definitely allow any literal (including symbol literals, but not including "type literals").
I'm vary about Uri
and DateTime
because they do not have const constructors, and therefore shouldn't be in an annotation to begin with. I guess we'll need special rules for macro annotations to allow them to do anything, becaus they'll be instantiated in a non-const
way eventually. Or you can just put in the string and parse it at the other end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the source code for this macro occurs as an annotation in the library using the macro. This source code is then executed in a separate isolate. Therefore the source code must be valid in that library. Is that correctly understood?
Right
In that case, I'd definitely allow any literal (including symbol literals, but not including "type literals").
Ya that might be an easier and more general way to define this.
I'm vary about
Uri
andDateTime
because they do not have const constructors, and therefore shouldn't be in an annotation to begin with. I guess we'll need special rules for macro annotations to allow them to do anything, becaus they'll be instantiated in a non-const
way eventually. Or you can just put in the string and parse it at the other end.
They really aren't necessary anyways, ill remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggestions on how to word it, but overall I'm excited to see you filling in this part of the proposal. :)
@@ -246,6 +246,41 @@ introspection APIs. | |||
These macros can fully introspect on any type reachable from the declarations | |||
they annotate, including introspecting on members of classes, etc. | |||
|
|||
## Macro Instantiation | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of drops the reader in the deep end. How about something like:
To apply a macro, a Dart compiler constructs an instance of the applied macro's class, and then invokes methods on it that implement the macro API. The arguments on the metadata annotation for the macro application get passed to the macro as constructor parameters. For example, on a macro application like:
@myMacro(1, 'a string')
class SomeClass {}
The compiler constructs an instance of the myMacro
class and passes 1
and 'a string'
to it. We want a Dart compiler to be able to construct and call methods on these macros while they are potentially running in a separate isolate or process. That in turn means that the constructor arguments need to be values that are easily serialized and sent to an isolate.
Macro constructor arguments can only be one of these types:
- The primitive types bool, double, Null, num, and String
- [Code][] and its subtypes
- Uri
- DateTime
Also, what about lists, sets, and maps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took that suggestion but s/arguments/parameters (the arguments can be anything, but the macro will get them as Code if they aren't these types).
Also, what about lists, sets, and maps?
I added those as well - but require that they are passed as literals (and all arguments are literals).
- DateTime | ||
|
||
### Macro Constructor Arguments | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little unclear to me about whether arguments are values or meta-values. How about something kind of like:
Arguments to macros are passed as unevaluated code. In other words, if you apply a macro like:
@myMacro(1 + 2)
class SomeClass {}
The argument passed is not the number 3, but a Code object representing the expression 1 + 2
. This is useful if a macro wants to take an arbitrary piece of syntax as an argument that it then injects into the generated code.
In many cases, though, what the macro author wants is an argument value. For example:
@coordinates(dimensions: 3)
class Vec3 {}
Here, the @coordinates
macro generates, x
, y
, etc. fields based on the given dimension passed to the macro. To support this, a macro constructor can accept parameters of primitive types and it will receive the arguments as values, not Code objects representing the value. However, the argument expression must be a literal of that type, and not any arbitrary expression that happens to evaluate to that type.
For example:
@coordinates(dimensions: 1 + 2) // Error.
class Vec3 {}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the arguments are recorded as expressions, then can you use variables references?
Say:
const string = "something or other";
...
@MyMacro(string, 42)
...
How about interpolations:
@MyMacro("Header:$string:Trailer")
...
If you can't access const variables, then you also can't use enums, which is likely going to affect the APIs that you'd want to build around the macro annotation.
(How about just serializing the macro annotation constant, deserializing it in the isolate running the macro, then failing compilation if you can't do both).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the arguments are recorded as expressions, then can you use variables references?
Yes
How about interpolations:
Yes
If you can't access const variables, then you also can't use enums, which is likely going to affect the APIs that you'd want to build around the macro annotation.
You could but you wouldn't be able to read their value, only a Code reference to them (might be an expression even). You can emit that reference in your own Code objects (maybe a switch statement etc) but you wouldn't be able to base the code you output on the value of those objects.
Loosening this would have some valid use cases for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took another shot at rewording this also. Borrowed some stuff from @munificent, but tried not to mislead people by oversimplifying things early on still.... I think its important to make the pass by value vs Code object distinction early on.
Followup to our discussion on making macro parameters more meta-y