language
language copied to clipboard
Specify how macros interact with default constructors
Currently the spec doesn't talk at all about default constructors, but it should.
My initial thought is that default constructors should only be added immediately following the declarations phase. If after that point a class does not have a constructor, then the default one is implicitly added, and in the definitions phase you will be able to introspect over it.
Fix for the analyzer: https://dart-review.googlesource.com/c/sdk/+/335383
Another issue worth mentioning here, is if the default constructor is only added at the end of the declarations phase, then macros running in the declarations phase on that class will not ever be able to see the default constructor. This might be perfectly fine though, or at least reasonable.
If the declaration phase only sees declarations, then not seeing implicitly added member implementations may be fine.
Would it see an int x;
declaration, or an int get x {...}
+set x(int _){...}
pair?
The former is the declaration, the latter are the semantic members introduced by that declaration.
FYI, in the analyzer we:
- Don't create the synthetic default constructor until after the declarations phase. So, if an actual constructor is added, we don't add the default one.
- We do create synthetic getter/setter for
int x;
as soon as we see the variable declaration. Nothing in the running macro can change the fact that these getter/setter exist. However we don't return synthetic getter/setter frommethodsOf()
, so macros don't see them.
Does it look like there is anything needed here? Design, implementation? Thanks.
I think the spec needs to be updated here to say exactly when the default constructor appears in introspection results. I think we are leaning towards it appearing after the declarations phase, as there isn't anything else reasonable that we can really do.
Whether or not we show synthetic members in general is another open question.
About this. Would a phase 2 or 3 be able to add a default constructor if none yet exist? In particular, would that added constructor be able to have required parameters and such?
A case would be when default constructors need to have access to the fields/methods of that class first ; which isn't doable in phase 1.
For example:
@data
class Example {
final int a;
}
// Adds:
augment class Example {
Example(this.a);
}
If the constructor was previously introspected as "There's an empty default synthetic constructor", but then another macro changes that, it could get weird.
Unless we're arguing that any synthetic member should be assumed to possibly change at a later stage?
I'd say that there is no "default constructor" until after all augmentations have been applied. If at that point there is still no constructor declared, the default constructor gets added.
That measn you can't augment the default constructor, by definition.
I'd say that there is no "default constructor" until after all augmentations have been applied. If at that point there is still no constructor declared, the default constructor gets added.
That means you can't augment the default constructor, by definition.
This does mean you can't get an identifier for the default constructor in the definitions phase of macros. Maybe that is fine, idk. You could get an identifier for the type and just add the string ()
, assuming it exists.
If the constructor was previously introspected as "There's an empty default synthetic constructor", but then another macro changes that, it could get weird.
This is why the suggestion is to not synthesize the default constructor until the definitions phase, it is the only sensible thing to do. Better to say there are no constructors than change a constructor out from under you.
Better to say there are no constructors than change a constructor out from under you.
This principle applies to every declaration. Changing anything from under you is equally problematic. As a minimum, the macro must be able to "lock" any declaration it relies upon while generating the code. (this is not enough though. If the macro generates a method based on the assumption that the fields a,b,c are all the fields with a certain property, then it should be able to prevent the addition of other fields with that property. Otherwise we will get buggy code that compiles just fine, but doesn't work).
I'd say that there is no "default constructor" until after all augmentations have been applied. If at that point there is still no constructor declared, the default constructor gets added.
This is how it is implemented in the analyzer today :-)