slinky icon indicating copy to clipboard operation
slinky copied to clipboard

`@react` macro annotation for Scala 3.5.0

Open steinybot opened this issue 1 year ago • 5 comments

It looks like macro annotations can now modify their companion objects in Scala 3.5.0: https://github.com/scala/scala3/pull/19677

steinybot avatar Aug 04 '24 23:08 steinybot

Wait a minute... why was I thinking that this needs to go on the props class?

steinybot avatar Aug 05 '24 09:08 steinybot

The best solution I can think of which has the fewest breaking changes is to:

  1. Add a scalafix rule which rewrites objects with @react annotations to:
    1. Extract the Props to a companion case class.
    2. Add a parent trait to the object for which a @react macro annotation can implement.
  2. The parent trait defines an abstract given Conversion[Props, KeyAddingStage] (and possibly others to avoid having to chain implicits)
  3. The @react macro annotation implements the given Conversion[Props, KeyAddingStage] using the component.

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.

steinybot avatar Aug 12 '24 05:08 steinybot

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)
}

threeseed avatar Dec 07 '24 09:12 threeseed

And discussion on the state of Scala 3 macros here.

threeseed avatar Dec 16 '24 09:12 threeseed

@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 ?

threeseed avatar Jan 08 '25 09:01 threeseed