language
language copied to clipboard
Macro API can't generate new types based on methods and properties from the base class
I was trying to use the new dart feature Macro to generate the repetitive code in the Mix framework (@leoafarias). Initially, I aimed to simplify the API by generating new classes from a base class. For instance:
@GenerateModifier()
class OpacityModifier {
final double opacity;
const OpacityModifier(this.opacity);
@override
Widget build(Widget child) {
assert(
opacity >= 0.0 && opacity <= 1.0,
'The opacity must be between 0.0 and 1.0 (inclusive).',
);
return Opacity(opacity: opacity, child: child);
}
}
and then it will generate three more classes based on these properties and methods, OpacityModifierSpec, OpacityModifierAttribute, and OpacityUtility, they will be like:
class OpacityModifierSpec extends WidgetModifierSpec<OpacityModifierSpec> {
final double opacity;
const OpacityModifierSpec(this.opacity);
@override
OpacityModifierSpec lerp(OpacityModifierSpec? other, double t) {
return OpacityModifierSpec(
lerpDouble(opacity, other?.opacity, t) ?? opacity,
);
}
@override
OpacityModifierSpec copyWith({double? opacity}) {
return OpacityModifierSpec(opacity ?? this.opacity);
}
@override
get props => [opacity];
@override
Widget build(Widget child) {
assert(
opacity >= 0.0 && opacity <= 1.0,
'The opacity must be between 0.0 and 1.0 (inclusive).',
);
return Opacity(opacity: opacity, child: child);
}
}
class OpacityModifierAttribute extends WidgetModifierAttribute<
OpacityModifierAttribute, OpacityModifierSpec> {
final double opacity;
const OpacityModifierAttribute(this.opacity);
@override
OpacityModifierAttribute merge(OpacityModifierAttribute? other) {
return OpacityModifierAttribute(other?.opacity ?? opacity);
}
@override
OpacityModifierSpec resolve(MixData mix) {
return OpacityModifierSpec(opacity);
}
@override
get props => [opacity];
}
class OpacityUtility<T extends Attribute>
extends MixUtility<T, OpacityModifierAttribute> {
const OpacityUtility(super.builder);
T call(double value) => builder(OpacityModifierAttribute(value));
}
The problem is that the ClassTypeBuilder on ClassTypesMacro can't return the properties and methods. Why can't ClassTypesMacro access these values? What do you suggest to solve this?
The problem is that the
ClassTypeBuilderonClassTypesMacrocan't return the properties and methods. Why can'tClassTypesMacroaccess these values? What do you suggest to solve this?
Before I try to explain why (it's complicated), can I ask exactly what you need it for exactly? I don't see anything that specifically looks like you need to do this, in the types phase, in the example provided here.
First one assumption I am making, is that there were some typos here, correct me if I am wrong about them:
@GenerateModifier()
class OpacityModifier {
final double opacity;
/// Should this have been OpacityModifier? I am in general a bit confused how this class comes into play.
/// Is it really an interface?
const OpacityModifierSpec(this.opacity);
...
}
// Should the extended type have been WidgetModifierSpec<OpacityModifier>?
class OpacityModifierSpec extends WidgetModifierSpec<OpacityModifierSpec> { ... }
As far as generating the OpacityModifierSpec, OpacityModifierAttribute, and OpacityUtility classes, all you need to generate is the header initially. It sounds like you are trying to generate the entire class up front. Instead, you should try to fill in the declarations and definitions in later phases, by applying macros to the classes which you add in the first phase.
So, you should have a ClassTypesMacro, which when it calls declareType, it only generates the following code, with additional macro annotations that will fill in the later code:
@GenerateModifierSpec()
class OpacityModifierSpec extends WidgetModifierSpec<OpacityModifier> {}
@GenerateModifierAttribute()
class OpacityModifierAttribute extends WidgetModifierAttribute<
OpacityModifierAttribute, OpacityModifierSpec> {}
@GenerateUtility()
class OpacityUtility<T extends Attribute>
extends MixUtility<T, OpacityModifierAttribute> {}
Each of these macros will implement ClassDeclarationsMacro, and possibly ClassDefinitionsMacro. These will add all the methods, and the implementations of those methods, to each of the classes.
If you need access to the original class you would provide that as an argument to the macros - so they would look like @GenerateModifierSpec(OpacityModifier) as an example. This will not work today I don't think though, as it isn't implemented yet.
Let me know if I can help further, or if there is some additional thing I am missing here.
@jakemac53, Thank you for the answer!
Currently, for each attribute, we need to create those 3 classes, so my idea with the macros features is to generate them from a new simple class that contains only the attributes, the constructor, and the build method. However, as you said it's impossible to do today because I can't get those infos in the type phase.
By attribute, do you mean field, as in final double opacity;?
You could require the macro to be applied to each field, although that isn't as nice to be sure.
It is possible we might in the future allow looking at the user written macros in this types phase also, but note that it means your macro would not compose well with other macros that want to add fields.
So, let's say somebody wrote a data class macro which generate fields based on a constructor signature. Your macro would not ever be able to see those fields.
Sorry, I didn't give you the context. Our package is to help Flutter Developers stylize their Widgets, and attributes are how we named the methods that can apply style to the widget.
If you need access to the original class you would provide that as an argument to the macros - so they would look like @GenerateModifierSpec(OpacityModifier) as an example. This will not work today I don't think though, as it isn't implemented yet.
In your first answer, you mention providing the class as an argument. Will I be able to access the constructors, methods, and properties of this class in this manner?
In your first answer, you mention providing the class as an argument. Will I be able to access the constructors, methods, and properties of this class in this manner?
After the types phase, yes
Sorry, I didn't give you the context. Our package is to help Flutter Developers stylize their Widgets, and
attributesare how we named the methods that can apply style to the widget.
In the example above, which thing is an attribute?
It is the class Attribute. Each attribute can be applied as a Style for our own Widgets, and then they can be resolved in the Spec.