-
Notifications
You must be signed in to change notification settings - Fork 208
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
Are macro applications always constructed? #1890
Comments
Good question, we could be ambiguous about this (add a line saying that a previously created instance "may" be used) or something. That might lead to observable differences though between tools. It might be best to just always create a new one, these should be cheap objects. |
But the metadata annotation refers to an actual object, how do you know you can construct it? What if it was constructed given arguments? What if its constructor is private? I'm sure this is tractable, but it feels like we'll want to tweak something here. |
What about:
|
The "as well as each phase" part feels kind of arbitrary. Can we simply say that the macro application is expected to const-evaluate to an instance of a macro class (either because it's a constant reference to an instance, or because it's a const constructor call) and then the runtime just uses that instance directly? That might raise some tricky questions about transferring that instance over to an isolate, but it seems semantically simplest. |
This is the problem - the macro expansion isolate doesn't even import the library where the macro application lives, so there is no constant object to refer to. So these instances can't be const. We could do runtime canonicalization or something else, but I doubt its worth the effort to do so. |
There's an even more fundamental problem:
We can't always do const evaluation before macro application. We can do constant evaluation in the library where the macro is defined because we know that that library has no import cycles and can be fully compiled (and macro expanded and const eval'd) before we get to the library containing the application. But you could imagine a foolish user trying to do: import 'some_macro.dart';
const reusableMacroObj = SomeMacro("some very long string I don't want to repeat...");
@reusableMacroObj class A {}
@reusableMacroObj class B {} But that definitely won't work. We can't reliably evaluate
I think my updated rules address that. The macro expansion isolate does import the library where the macro is defined, and that's where the constant must live. |
If the code runs in a different isolate (which it does because it runs, and the original program hasn't been compiled yet, so it has no isolate), you need to create an instance of the annotation in that isolate. You cannot use a value which belongs to a different isolate. The two objects are different because they close over different global scopes. It stretches our notion of identity if we claim the same object, even if it's constant, can exist in two different isolates at the same time. We could probably specify things in a way which allows that notion, but we haven't so far. We can serialize and deserialize a constant object through a |
What if we enabled an
So basically you could do: macro class DataClass implements ClassDeclarationsMacro as dataClass {...} And then refer to it as For macros that take arguments, you could still actually create aliases for certain argument combinations, by just creating a new macro that extends the other and provides specific arguments: macro class SomeMacroWithArgs extends SomeMacro as someMacroWithArgs {
SomeMacroWithArgs() : super(some, arguments, here);
} I worry a bit about the latter case, if that was a common pattern it could potentially cause an explosion of the number of macros that exist, which could come with a significant compilation time cost. |
It feels a little heavyweight to add new syntax (especially given how hard we've otherwise tried to avoid new syntax for macros). I believe my previous comment would address this. Instead of: macro class DataClass implements ClassDeclarationsMacro as dataClass {...} You could do: macro class DataClass implements ClassDeclarationsMacro {...}
const dataClass = DataClass(); |
Yes I think conceptually this could work but I also think people will end up wanting to do that in any library, but won't be able to. It also brings up weird questions - can you only do that in the library where If the former, then you end up still needing to create a whole new macro, just to be able to pass the custom arguments (although this isn't worse than my proposal above):
If we support the latter it encourages people making dummy macros that are never used just to be able to define these constants. Other thoughts
|
At least for now, I am going to require explicit constructor invocations for macro annotations. I think that greatly simplifies the model, and it isn't too much of a burden. We can always add the functionality for using constant references later on. I will leave this open though as its a good discussion, but move it to the backlog for later releases. |
Should we just require macro imports to be done using Or is that what we're already doing? |
Unfortunately that wouldn't be a good solution (to this specific problem) imo, especially for modular compiles but really for any compilation. Basically today, the macro expansion programs we create only need to import macro definitions, which is a good thing. If we start allowing other random user code we will end up with likely far more of these programs and we want as few as possible (they are separate apps that have to be compiled etc). We also want their import scopes to be as small as possible, and random users are less likely to understand that than macro authors (they are likely to jam these constants into a library with lots of other unrelated imports). |
(I do think we will want either macro imports or macro libraries for other reasons though, to ensure the code from them is only present at compile time) |
dart-lang/language#1890 (comment) dart-lang/language#3205 Change-Id: I20a181a01eab4fef9a8bf6e568745eb2b8e86d6d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/312881 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
dart-lang/language#1890 (comment) dart-lang/language#3205 Change-Id: I20a181a01eab4fef9a8bf6e568745eb2b8e86d6d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/312881 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
This looks obsolete with #3728 |
The macro proposal currently says:
But in most of the examples in the prototype, the macro application is a constant object like
@jsonSerializable
or@dataClass
, and not a constant constructor call like@foo(1, 2)
. In applications where the macro is an object, is it still the case that the compiler constructs a new instance of the applied macro's class? Or does it just call methods directly on that existing constant object?The text was updated successfully, but these errors were encountered: