Skip to content

Commit

Permalink
Replace java reflection with a macro based solution
Browse files Browse the repository at this point in the history
Necessary for cross compilation with scala native, since it does not
offer any reflection functionalities.
Instead of the previous method, we create a mapping between
strings (pointed out by the dialectOverride in scalafmt.conf) and
methods that allow us to replace dialect values.
  • Loading branch information
jchyb committed Sep 17, 2024
1 parent a8baa8b commit 2f9cb5a
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
13 changes: 12 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,22 @@ lazy val core = crossProject(JVMPlatform).in(file("scalafmt-core")).settings(
// scalatest.value % Test // must be here for coreJS/test to run anything
// )
// )
.jvmSettings(Test / run / fork := true).dependsOn(sysops, config)
.jvmSettings(Test / run / fork := true).dependsOn(sysops, config, macros)
.enablePlugins(BuildInfoPlugin)
lazy val coreJVM = core.jvm
// lazy val coreJS = core.js

lazy val macros = crossProject(JVMPlatform)
.in(file("scalafmt-macros")).settings(
moduleName := "scalafmt-macros",
buildInfoSettings,
scalacOptions ++= scalacJvmOptions.value,
libraryDependencies ++= Seq(
scalameta.value,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
)
)

import sbtassembly.AssemblyPlugin.defaultUniversalScript

val scalacJvmOptions = Def.setting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ object ScalafmtRunner {
implicit val encoder: ConfEncoder[ScalafmtRunner] = generic.deriveEncoder

private def overrideDialect[T: ClassTag](d: Dialect, k: String, v: T) = {
import org.scalafmt.config.ReflectOps._
val methodName =
if (k.isEmpty || k.startsWith("with")) k
else "with" + Character.toUpperCase(k.head) + k.tail
d.invokeAs[Dialect](methodName, v.asParam)
val map: Map[String, Any => Dialect] = DialectMacro.dialectMap(d)
map(methodName)(v)
}

implicit val decoder: ConfDecoderEx[ScalafmtRunner] = generic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.scalafmt.config

import scala.meta.Dialect

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

// Builds a map between string and dialect method application
object DialectMacro {
def dialectMap(dialect: Dialect): Map[String, (Any => Dialect)] =
macro dialectMap_impl

def dialectMap_impl(
c: blackbox.Context,
)(dialect: c.Tree): c.Expr[Map[String, (Any => Dialect)]] = {
import c.universe._
val mapElements = typeOf[Dialect].members.flatMap {
case v: TermSymbol if v.isVal =>
val valName = v.name.decodedName.toString().strip()
val tpe = v.typeSignature
val methodName = "with" + Character.toUpperCase(valName.head) +
valName.tail.strip()
val methodTermName = TermName(methodName)
if (typeOf[Dialect].members.exists(_.name.decodedName == methodTermName))
Some(q"""$methodName -> ((v: Any) => $dialect.$methodTermName(v.asInstanceOf[$tpe]))""")
else None
case _ => None
}
c.Expr[Map[String, (Any => Dialect)]](
q"""scala.collection.immutable.Map(..$mapElements)""",
)
}
}

0 comments on commit 2f9cb5a

Please sign in to comment.