Skip to content

Commit

Permalink
better error for external rules ran with old scalafix-cli version
Browse files Browse the repository at this point in the history
metaconfig-pprint brought by metaconfig 0.10.0 will not be available to
external rules when classloaded by an old scalafix-cli version as
the runtime scalafix-core and its dependencies are driven by
scalafix-cli dependencies, not the rule dependencies.

We obviously have no control over old scalafix-cli versions, but since
the reference to the potentially-missing type is from a macro-generated
block, this wraps it to provide a more actionnable error message.
  • Loading branch information
bjaglin committed Mar 12, 2022
1 parent c288838 commit 3b03255
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package metaconfig

import metaconfig.internal.ScalafixHijackMacros

import scala.language.experimental.macros

// shadows https://github.com/scalameta/metaconfig/blob/v0.10.0/metaconfig-core/shared/src/main/scala-2/metaconfig/generic/package.scala
package object generic {
def deriveSurface[T]: Surface[T] =
macro ScalafixHijackMacros.deriveSurfaceImpl[T]
def deriveDecoder[T](default: T): ConfDecoder[T] =
macro metaconfig.internal.Macros.deriveConfDecoderImpl[T]
def deriveEncoder[T]: ConfEncoder[T] =
macro metaconfig.internal.Macros.deriveConfEncoderImpl[T]
def deriveCodec[T](default: T): ConfCodec[T] =
macro metaconfig.internal.Macros.deriveConfCodecImpl[T]
def deriveDecoderEx[T](default: T): ConfDecoderEx[T] =
macro metaconfig.internal.Macros.deriveConfDecoderExImpl[T]
def deriveCodecEx[T](default: T): ConfCodecEx[T] =
macro metaconfig.internal.Macros.deriveConfCodecExImpl[T]
}
21 changes: 21 additions & 0 deletions scalafix-core/src/main/scala-2.13/metaconfig/generic/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package metaconfig

import metaconfig.internal.ScalafixHijackMacros

import scala.language.experimental.macros

// shadows https://github.com/scalameta/metaconfig/blob/v0.10.0/metaconfig-core/shared/src/main/scala-2/metaconfig/generic/package.scala
package object generic {
def deriveSurface[T]: Surface[T] =
macro ScalafixHijackMacros.deriveSurfaceImpl[T]
def deriveDecoder[T](default: T): ConfDecoder[T] =
macro metaconfig.internal.Macros.deriveConfDecoderImpl[T]
def deriveEncoder[T]: ConfEncoder[T] =
macro metaconfig.internal.Macros.deriveConfEncoderImpl[T]
def deriveCodec[T](default: T): ConfCodec[T] =
macro metaconfig.internal.Macros.deriveConfCodecImpl[T]
def deriveDecoderEx[T](default: T): ConfDecoderEx[T] =
macro metaconfig.internal.Macros.deriveConfDecoderExImpl[T]
def deriveCodecEx[T](default: T): ConfCodecEx[T] =
macro metaconfig.internal.Macros.deriveConfCodecExImpl[T]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scalafix.internal.config
import metaconfig.ConfDecoder
import metaconfig.generic
import metaconfig.generic.Surface
import scalafix.internal.util.MetaconfigCompatMacros
import scalafix.patch.Patch
import scalafix.patch.Patch.internal._

Expand All @@ -17,7 +18,7 @@ case class ConfigRulePatches(

object ConfigRulePatches {
implicit val surface: Surface[ConfigRulePatches] =
generic.deriveSurface[ConfigRulePatches]
MetaconfigCompatMacros.deriveSurfaceOrig[ConfigRulePatches]
val default: ConfigRulePatches = ConfigRulePatches()
implicit val configRuleDecoder: ConfDecoder[ConfigRulePatches] =
generic.deriveDecoder[ConfigRulePatches](default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package scalafix.internal.config
import metaconfig.ConfDecoder
import metaconfig.generic
import metaconfig.generic.Surface
import scalafix.internal.util.MetaconfigCompatMacros

case class DebugConfig(
printSymbols: Boolean = false
)
object DebugConfig {
implicit val surface: Surface[DebugConfig] =
generic.deriveSurface[DebugConfig]
MetaconfigCompatMacros.deriveSurfaceOrig[DebugConfig]
val default: DebugConfig = DebugConfig()
implicit val decoder: ConfDecoder[DebugConfig] =
generic.deriveDecoder[DebugConfig](default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package scalafix.internal.config
import metaconfig.ConfDecoder
import metaconfig.generic
import metaconfig.generic.Surface
import scalafix.internal.util.MetaconfigCompatMacros
import scalafix.lint.LintSeverity

case class LintConfig(
Expand All @@ -21,7 +22,8 @@ case class LintConfig(
}

object LintConfig {
implicit val surface: Surface[LintConfig] = generic.deriveSurface[LintConfig]
implicit val surface: Surface[LintConfig] =
MetaconfigCompatMacros.deriveSurfaceOrig[LintConfig]
lazy val default: LintConfig = LintConfig()
implicit val decoder: ConfDecoder[LintConfig] =
generic.deriveDecoder[LintConfig](default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.meta._

import metaconfig._
import metaconfig.generic.Surface
import scalafix.internal.util.MetaconfigCompatMacros
import scalafix.Versions
import scalafix.internal.config.ScalafixConfig._

Expand Down Expand Up @@ -38,7 +39,7 @@ object ScalafixConfig {
def decoder(default: ScalafixConfig): ConfDecoder[ScalafixConfig] =
generic.deriveDecoder[ScalafixConfig](default)
implicit lazy val surface: Surface[ScalafixConfig] =
generic.deriveSurface[ScalafixConfig]
MetaconfigCompatMacros.deriveSurfaceOrig[ScalafixConfig]
implicit lazy val ScalafixConfigDecoder: ConfDecoder[ScalafixConfig] =
decoder(default)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package scalafix.internal.util

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

object MetaconfigCompatMacros {
// metaconfig.generic.deriveSurface cannot be used within scalafix-core as macro implementations cannot
// be used in the same compilation run that defines them
def deriveSurfaceOrig[T]: metaconfig.generic.Surface[T] =
macro metaconfig.internal.Macros.deriveSurfaceImpl[T]
}

class MetaconfigCompatMacros(override val c: blackbox.Context)
extends metaconfig.internal.Macros(c) {

import c.universe._

// Provide an actionnable error in case of linking issue, see https://github.com/scalacenter/scalafix/pull/1530#issuecomment-1061174301
override def deriveSurfaceImpl[T: c.WeakTypeTag]: Tree = {
val unsafeTree = super.deriveSurfaceImpl[T]

val buildVersion = scalafix.Versions.nightly
q"""
val runtimeVersion = _root_.scalafix.Versions.nightly
try {
new java.util.concurrent.Callable[${weakTypeOf[metaconfig.generic.Surface[T]]}] {
// Since the unsafeTree gets lifted into a static block, we wrap its execution
// inside a nested class to be able to intercept the NoClassDefFoundError
override def call: ${weakTypeOf[metaconfig.generic.Surface[T]]} = $unsafeTree
}.call
} catch {
case e: NoClassDefFoundError =>
throw new RuntimeException(
"Scalafix version " + runtimeVersion + " detected. Please upgrade to " + $buildVersion + " or later",
e
)
}
"""
}
}

0 comments on commit 3b03255

Please sign in to comment.