8

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?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Klugscheißer
  • 1,575
  • 1
  • 11
  • 24
  • I'm not sure about the scale on which you want to ban the usage of this codec, but you are aware of the `import foo.bar.baz.{stuff_i_dont_need => _, _}` syntax? – Andrey Tyukin May 14 '18 at 19:39
  • 3
    @AndreyTyukin That's an excellent point and works in the small but I'd like to do this across multiple modules in an sbt project. This will also still allow the code to compile if I forget to import `=> _`. I want a way to protect myself from that mistake. – Klugscheißer May 15 '18 at 17:25

2 Answers2

6

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.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
2

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.

Luciano
  • 2,388
  • 1
  • 22
  • 33
  • Great idea but requires modifying all data models (e.g. can no longer use `Double` but rather `NewDouble`) which seems a bit unwieldy. – Klugscheißer Apr 02 '19 at 18:36