Last active
October 25, 2018 06:38
-
-
Save DylanLukes/6789799a177839abf7d5db9f203a71bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.emg.slidedeck.js.macros | |
import scala.collection.immutable.Seq | |
import scala.meta._ | |
import scala.meta.dialects.Paradise211 | |
class ScalaJSDefinedApply extends scala.annotation.StaticAnnotation { | |
inline def apply(defn: Any): Any = meta { | |
def extract(trt: Defn.Trait): Seq[(Term.Name, Option[String], Type, Term)] = { | |
trt.collect { | |
case dv @ Defn.Val(mods, Seq(Pat.Var.Term(name)), Some(tpe), rhs @ q"js.undefined") => | |
val jsName = mods.collectFirst { | |
case mod"@JSName(${lit: Lit})" => | |
lit.value match { | |
case s: String => s | |
case _ => abort(s"@ScalaJSDefinedApply: @JSName must be called with a string literal.") | |
} | |
// todo: add case of js.Symbol | |
} | |
(name, jsName, tpe, rhs) | |
case dv @ Defn.Val(_, Seq(_, _, _*), None, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must only declare one left-hand side.") | |
case dv @ Defn.Val(_, _, None, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must have an explicit type declaration.") | |
case dv @ Defn.Val(_, _, _, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must be initialized to js.undefined.") | |
} | |
} | |
def generate(tname: Type.Name, schema: Seq[(Term.Name, Option[String], Type, Term)]): Defn.Def = { | |
val params = schema.map { | |
case (name, _, tpe, default) => | |
param"$name: $tpe = $default" | |
} | |
val args = schema.map { | |
case (name, jsName, _, _) => | |
val namestr = Lit.String(jsName.getOrElse(name.value)) | |
arg"($namestr, $name.asInstanceOf[UndefOr[js.Any]])" | |
} | |
q"""def apply(..$params): $tname = { | |
import js.JSConverters._ | |
val args = Map[String, js.UndefOr[js.Any]](..$args).collect { | |
case (k, v) if !js.isUndefined(v) => (k, v.asInstanceOf[js.Any]) | |
}; | |
args.toJSDictionary.asInstanceOf[$tname] | |
}""" | |
} | |
defn match { | |
// A companion Object already exists in scope. Modify it. | |
case Term.Block(Seq( | |
dt @ Defn.Trait(_, tname, _, _, _), | |
companion: Defn.Object | |
)) => | |
val fields = extract(dt) | |
val applyMethod = generate(tname, fields) | |
Term.Block(Seq(dt, companion.copy( | |
templ = companion.templ.copy( | |
stats = companion.templ.stats.map { applyMethod +: _ } | |
)) | |
)) | |
// A companion Object does not yet exist. | |
case dt @ Defn.Trait(_, tname, _, _, _) => | |
val schema = extract(dt) | |
val apply = generate(tname, schema) | |
val companion = q"object ${Term.Name(tname.value)} { $apply }" | |
Term.Block(Seq(dt, companion)) | |
case _ => | |
abort("@ScalaJSDefinedApply must annotate a trait.") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi! Is this the latest version of your
@ScalaJSDefinedApply
macro?