Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exclude a specific implicit from a Scala project

How can I prevent the usage of a specific implicit in my scala code?

For example, I was recently bit by the default Codec provided by https://github.com/scala/scala/blob/68bad81726d15d03a843dc476d52cbbaf52fb168/src/library/scala/io/Codec.scala#L76. Is there a way to ensure that any code that calls for an implicit codec: Codec never uses the one provided by fallbackSystemCodec? Alternatively, is it possible to block all implicit Codecs?

Is this something that should be doable using scalafix?

like image 772
Klugscheißer Avatar asked May 14 '18 18:05

Klugscheißer


2 Answers

Scalafix can inspect implicit arguments using SemanticTree. Here is an example solution by defining a custom scalafix rule.

Given

import scala.io.Codec

object Hello {
  def foo(implicit codec: Codec) = 3
  foo
}

we can define a custom rule

class ExcludedImplicitsRule(config: ExcludedImplicitsRuleConfig)
    extends SemanticRule("ExcludedImplicitsRule") {

...

  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree.collect {
      case term: Term if term.synthetic.isDefined => // TODO: Use ApplyTree(func, args)
        val struct = term.synthetic.structure
        val isImplicit = struct.contains("implicit")
        val excludedImplicit = config.blacklist.find(struct.contains)
        if (isImplicit && excludedImplicit.isDefined)
          Patch.lint(ExcludedImplicitsDiagnostic(term, excludedImplicit.getOrElse(config.blacklist.mkString(","))))
        else
          Patch.empty
    }.asPatch
  }

}

and corresponding .scalafix.conf

rule = ExcludedImplicitsRule
ExcludedImplicitsRuleConfig.blacklist = [
  fallbackSystemCodec
]

should enable sbt scalafix to raise the diagnostic

[error] /Users/mario/IdeaProjects/scalafix-exclude-implicits/example-project/scalafix-exclude-implicits-example/src/main/scala/example/Hello.scala:7:3: error: [ExcludedImplicitsRule] Attempting to pass excluded implicit fallbackSystemCodec to foo'
[error]   foo
[error]   ^^^
[error] (Compile / scalafix) scalafix.sbt.ScalafixFailed: LinterError

Note the output of println(term.synthetic.structure)

Some(ApplyTree(
  OriginalTree(Term.Name("foo")),
  List(
    IdTree(SymbolInformation(scala/io/LowPriorityCodecImplicits#fallbackSystemCodec. => implicit lazy val method fallbackSystemCodec: Codec))
  )
))

Clearly the above solution is not efficient as it searches strings, however it should give some direction. Perhaps matching on ApplyTree(func, args) would be better.

scalafix-exclude-implicits-example shows how to configure the project to use ExcludedImplicitsRule.

like image 132
Mario Galic Avatar answered Oct 26 '22 21:10

Mario Galic


You can do this by using a new type altogether; this way, nobody will be able to override it in your dependencies. It's essentially the answer I posted to create an ambiguous low priority implicit

It may not be practical though, if for example you can't change the type.

like image 2
Luciano Avatar answered Oct 26 '22 22:10

Luciano