11package japgolly .scalajs .react .hooks
22
33import japgolly .microlibs .compiletime .MacroUtils
4- import japgolly .scalajs .react .{Children , CtorType }
54import japgolly .scalajs .react .component .ScalaFn .Component
65import japgolly .scalajs .react .hooks .Api ._
7- import japgolly .scalajs .react .internal .Box
6+ import japgolly .scalajs .react .internal .{ Box , MacroLogger }
87import japgolly .scalajs .react .vdom .VdomNode
8+ import japgolly .scalajs .react .{Children , CtorType }
9+ import scala .annotation .{nowarn , tailrec }
910import scala .reflect .macros .blackbox .Context
1011
1112object HookMacros {
@@ -19,36 +20,166 @@ object HookMacros {
1920 }
2021}
2122
22- @ annotation.nowarn(" cat=unused" ) // TODO: remove
23+ // =====================================================================================================================
24+
2325class HookMacros (val c : Context ) extends MacroUtils {
2426 import c .universe ._
2527
26- def render [P , C <: Children , Ctx , CtxFn [_], Step <: SubsequentStep [Ctx , CtxFn ]]
27- (f : c.Tree )(step : c.Tree , s : c.Tree ): c.Tree = {
28+ private implicit def autoTagToType [A ](t : c.WeakTypeTag [A ]): Type = t.tpe
2829
29- var debug = false // showCode(c.macroApplication).contains("counter.value")
30+ private def Box : Tree = q " _root_.japgolly.scalajs.react.internal.Box "
31+ private def Box (t : Type ): Type = appliedType(c.typeOf[Box [_]], t)
32+ private def Hooks : Tree = q " _root_.japgolly.scalajs.react.hooks.Hooks "
33+ private def JsFn : Tree = q " _root_.japgolly.scalajs.react.component.JsFn "
34+ private def React : Tree = q " _root_.japgolly.scalajs.react.facade.React "
35+ private def ScalaFn : Tree = q " _root_.japgolly.scalajs.react.component.ScalaFn "
36+ private def withHooks = " withHooks"
3037
31- def println ( args : Any * ) : Unit =
32- if (debug) System .out.println( args.mkString )
38+ case class HookDefn ( propsType : Tree , steps : List [ HookStep ])
39+ case class HookStep ( name : String , targs : List [ Tree ], args : List [ List [ Tree ]] )
3340
34- println(" =" * 120 )
35- println()
41+ def render [P , C <: Children , Ctx , CtxFn [_], Step <: SubsequentStep [Ctx , CtxFn ]]
42+ (f : c.Tree )(step : c.Tree , s : c.Tree )
43+ (implicit P : c.WeakTypeTag [P ], C : c.WeakTypeTag [C ]): c.Tree = {
44+
45+ implicit val log = MacroLogger ()
46+ // log.enabled = showCode(c.macroApplication).contains("counter.value")
47+ log.header()
48+ log(" macroApplication" , showRaw(c.macroApplication))
3649
3750 val self = c.prefix
3851
39- val result = q """
40- {
41- val f = $step.squash( $f)
42- $self.render(f)( $s)
43- }
44- """
52+ val parsed = parseHookDefn(c.macroApplication, Nil , Nil , Nil )
4553
46- if (debug)
47- println(" RESULT:\n " + showCode(result))
54+ val inlined = parsed
55+ .flatMap(inlineHookDefn)
56+ .map(inlineHookRawComponent[P ])
57+ .map(inlineHookComponent[P , C ](_, s))
4858
49- println()
50- println(" =" * 120 )
59+ val result : Tree =
60+ inlined match {
61+ case Right (r) =>
62+ r
63+ case Left (e) =>
64+ log(e())
65+ q """
66+ val f = $step.squash( $f)
67+ $self.render(f)( $s)
68+ """
69+ }
5170
71+ log.footer(showCode(result))
5272 result
5373 }
74+
75+ @ tailrec
76+ private def parseHookDefn (tree : Tree , targs : List [Tree ], args : List [List [Tree ]], steps : List [HookStep ])
77+ (implicit log : MacroLogger ): Either [() => String , HookDefn ] =
78+ tree match {
79+
80+ case Apply (t, a) =>
81+ parseHookDefn(t, targs, a :: args, steps)
82+
83+ case TypeApply (t, a) =>
84+ if (targs.isEmpty)
85+ parseHookDefn(t, a, args, steps)
86+ else
87+ Left (() => " Multiple type arg clauses found at " + showRaw(tree))
88+
89+ case Select (t, n) =>
90+ val name = n.toString
91+ if (name == withHooks) {
92+ if (args.nonEmpty)
93+ Left (() => s " $withHooks called with args when none exepcted: ${args.map(_.map(showCode(_)))}" )
94+ else
95+ targs match {
96+ case props :: Nil => Right (HookDefn (props, steps))
97+ case Nil => Left (() => s " $withHooks called without targs, unable to discern Props type. " )
98+ case _ => Left (() => s " $withHooks called multiple targs: ${targs.map(showCode(_))}" )
99+ }
100+ } else {
101+ val step = HookStep (name, targs, args)
102+ log(s " Found step ' $name' " , step)
103+ parseHookDefn(t, Nil , Nil , step :: steps)
104+ }
105+
106+ case _ =>
107+ Left (() => " Don't know how to parse " + showRaw(tree))
108+ }
109+
110+ private type RenderInliner = (Tree , Init ) => Tree
111+
112+ private def inlineHookDefn (h : HookDefn )(implicit log : MacroLogger ): Either [() => String , RenderInliner ] = {
113+ val init = new Init (" hook" + _, lazyVals = false )
114+ val it = h.steps.iterator
115+ var stepId = 0
116+ var renderStep : HookStep = null
117+ var hooks = List .empty[TermName ]
118+ while (it.hasNext) {
119+ val step = it.next()
120+ if (it.hasNext) {
121+ stepId += 1
122+ inlineHookStep(stepId, step, init) match {
123+ case Right (termName) => hooks ::= termName
124+ case Left (e) => return Left (e)
125+ }
126+ } else
127+ renderStep = step
128+ }
129+ hooks = hooks.reverse
130+
131+ hookRenderInliner(renderStep, hooks.map(Ident (_))).map { f =>
132+ (props, init2) => {
133+ init2 ++= init.stmts
134+ f(props, init2)
135+ }
136+ }
137+ }
138+
139+ private def inlineHookStep (stepId : Int , step : HookStep , init : Init )(implicit log : MacroLogger ): Either [() => String , TermName ] = {
140+ log(" inlineHookStep." + step.name, step)
141+ step.name match {
142+ case " useState" =>
143+ val stateType = step.targs.head
144+ val arg = step.args.head.head
145+ val rawName = TermName (" hook" + stepId + " _raw" )
146+ val name = TermName (" hook" + stepId)
147+ init += q " val $rawName = $React.useStateFn(() => $Box[ $stateType]( $arg)) "
148+ init += q " val $name = $Hooks.UseState.fromJsBoxed[ $stateType]( $rawName) "
149+ Right (name)
150+
151+ case _ =>
152+ Left (() => s " Inlining of hook method ' ${step.name}' not yet supported. " )
153+ }
154+ }
155+
156+ private def hookRenderInliner (step : HookStep , hooks : List [Tree ])(implicit log : MacroLogger ): Either [() => String , RenderInliner ] = {
157+ log(" inlineHookRender." + step.name, step)
158+ step.name match {
159+ case " render" =>
160+ @ nowarn(" msg=exhaustive" ) val List (List (renderFn), _) = step.args
161+ Right { (props, _) =>
162+ val args = props :: hooks
163+ Apply (Select (renderFn, TermName (" apply" )), args)
164+ }
165+
166+ case _ =>
167+ Left (() => s " Inlining of hook render method ' ${step.name}' not yet supported. " )
168+ }
169+ }
170+
171+ private def inlineHookRawComponent [P ](renderInliner : RenderInliner )(implicit P : c.WeakTypeTag [P ]): Tree = {
172+ val props_unbox = q " props.unbox "
173+ val init = new Init (" _i" + _)
174+ val render1 = renderInliner(props_unbox, init)
175+ val render2 = init.wrap(q " $render1.rawNode " )
176+ q " (props => $render2): $JsFn.RawComponent[ ${Box (P )}] "
177+ }
178+
179+ private def inlineHookComponent [P , C <: Children ](rawComp : Tree , summoner : c.Tree )(implicit P : c.WeakTypeTag [P ], C : c.WeakTypeTag [C ]): Tree = {
180+ c.untypecheck(q """
181+ val rawComponent = $rawComp
182+ $ScalaFn.fromBoxed( $JsFn.fromJsFn[ ${Box (P )}, $C](rawComponent)( $summoner))
183+ """ )
184+ }
54185}
0 commit comments