`@react` macro annotation for Scala 3.5.0
It looks like macro annotations can now modify their companion objects in Scala 3.5.0: https://github.com/scala/scala3/pull/19677
Wait a minute... why was I thinking that this needs to go on the props class?
The best solution I can think of which has the fewest breaking changes is to:
- Add a scalafix rule which rewrites objects with
@reactannotations to:- Extract the
Propsto a companion case class. - Add a parent trait to the object for which a
@reactmacro annotation can implement.
- Extract the
- The parent trait defines an abstract
given Conversion[Props, KeyAddingStage](and possibly others to avoid having to chain implicits) - The
@reactmacro annotation implements thegiven Conversion[Props, KeyAddingStage]using thecomponent.
The exact shape of the props companion case class and member to implement in the companion object needs a bit more thought but this is the basic idea.
Class Component
@react
class MyComponent extends StatelessComponent {
case class Props(int: Int, children: List[String])
def render() = props.children
}
Expanded to:
class MyComponent(jsProps: js.Object) extends MyComponent.Definition(jsProps) {
import MyComponent.{Props, State, Snapshot};
override def render(): ReactElement = props.children
};
object MyComponent extends StatelessComponent.Wrapper {
case class Props(int: Int, children: List[String])
type Def = MyComponent;
def apply(int: Int)(children: List[String]): slinky.core.KeyAndRefAddingStage[Def] =
this.apply(Props(int, children))
}
External Component
@react
class MyComponent extends ExternalComponent {
case class Props(int: Int)
val component = MyComponent
}
Expanded to:
object MyComponent extends slinky.core.ExternalComponentWithAttributesWithRefType[Nothing, js.Object] {
case class Props(int: Int)
def apply(int: Int): BuildingComponent[Nothing, js.Object] = this.apply(Props.apply(int)).asInstanceOf[BuildingComponent[Nothing, js.Object]];
def apply(mods: Seq[TagMod[Nothing]]): BuildingComponent[Nothing, js.Object] = new BuildingComponent[Nothing, js.Object](js.Array(component.asInstanceOf[js.Any], js.Dictionary.empty)).apply((mods: _*));
def withKey(key: String): BuildingComponent[Nothing, js.Object] = new BuildingComponent[Nothing, js.Object(js.Array(component.asInstanceOf[js.Any], js.Dictionary.empty)).withKey(key);
def withRef(ref: Function1[js.Object, Unit]): BuildingComponent[Nothing, js.Object] = new BuildingComponent[Nothing, js.Object](js.Array(component.asInstanceOf[js.Any], js.Dictionary.empty)).withRef(ref);
def withRef(ref: ReactRef[js.Object]): BuildingComponent[Nothing, js.Object] = new BuildingComponent[Nothing, js.Object](js.Array(component.asInstanceOf[js.Any], js.Dictionary.empty)).withRef(ref)
}
Functional Component
@react object MyComponent {
case class Props[T](in: Seq[T])
val component = FunctionalComponent[Props[_]] { case Props(in) =>
in.mkString(" ")
}
}
Expanded to:
object MyComponent {
case class Props[T](in: Seq[T])
val component = FunctionalComponent[Props[_]] { case Props(in) =>
in.mkString(" ")
}
def apply[T](in: Seq[T]) = component.apply(Props.apply(in));
def apply(props: component.Props) = component.apply(props)
}
And discussion on the state of Scala 3 macros here.
@steinybot @shadaj .. Have submitted a PR which adds a new SBT Plugin for transforming the code.
I did try to use Scalafix but I couldn't get it work. But at least the Scala Meta code in the plugin should be easily transferrable.
What is nice though is that because the code is written out to src_managed you can easily debug any further changes you want to make e.g. using Scala 3 givens. Also not sure what the implication is for IntelliJ i.e. do we need that plugin any more ?