Scalafix: resolving object apply method

182 views Asked by At

I have an object with some apply method defined and then use it

object Ob {
  def apply(i: Int) = ()
  def apply(s: String) = ()
}

object Use {
  def someMethod(i: Int) = ()

  Ob(1)
  someMethod(1)
}

When using scalafix/scalameta, I'm unable to find a way to get the handle of the actual apply method (In my case I'm trying to inspect the argument names and type for a scalafix rule)

When I match and print the resolved SymbolInformation, I get a reference to the object. So this

import scalafix.v1._
import scala.meta._

class NamedLiteralArguments extends SemanticRule("NamedLiteralArguments") {
  val minParam = 2

  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree
      .collect {
        case Term.Apply(fun, args) =>
          println(fun.symbol.info)

          Patch.empty
      }
  }

prints

Some(test/Ob. => final object Ob extends AnyRef { +2 decls })
Some(test/Use.someMethod(). => method someMethod(i: Int): Unit)

But I want it to resolve the exact apply method instead.

(Scalafix version 0.9.20)

1

There are 1 answers

1
Dmytro Mitin On BEST ANSWER

Switch on "-P:semanticdb:synthetics:on" in build.sbt

scalacOptions ++= List(
  "-Yrangepos",
  "-P:semanticdb:synthetics:on",
)

Then

import scalafix.v1._
import scala.meta._

class MyRule extends SemanticRule("MyRule") {
  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree
      .collect {
        case t: Term =>
          println(s"t=$t=${t.structure}, t.symbol.info=${t.symbol.info}, t.synthetics=${t.synthetics.map(_.symbol.map(_.info))}")

          Patch.empty
      }.asPatch
  }
}

prints

t=Ob=Term.Name("Ob"), t.symbol.info=Some(_empty_/Ob. => final object Ob extends AnyRef { +2 decls }), t.synthetics=List()
t=apply=Term.Name("apply"), t.symbol.info=Some(_empty_/Ob.apply(). => method apply(i: Int): Unit), t.synthetics=List()
t=i=Term.Name("i"), t.symbol.info=Some(_empty_/Ob.apply().(i) => param i: Int), t.synthetics=List()
t=()=Lit.Unit, t.symbol.info=None, t.synthetics=List()
t=apply=Term.Name("apply"), t.symbol.info=Some(_empty_/Ob.apply(+1). => method apply(s: String): Unit), t.synthetics=List()
t=s=Term.Name("s"), t.symbol.info=Some(_empty_/Ob.apply(+1).(s) => param s: String), t.synthetics=List()
t=()=Lit.Unit, t.symbol.info=None, t.synthetics=List()
t=Use=Term.Name("Use"), t.symbol.info=Some(_empty_/Use. => final object Use extends AnyRef { +1 decls }), t.synthetics=List()
t=someMethod=Term.Name("someMethod"), t.symbol.info=Some(_empty_/Use.someMethod(). => method someMethod(i: Int): Unit), t.synthetics=List()
t=i=Term.Name("i"), t.symbol.info=Some(_empty_/Use.someMethod().(i) => param i: Int), t.synthetics=List()
t=()=Lit.Unit, t.symbol.info=None, t.synthetics=List()
t=Ob(1)=Term.Apply(Term.Name("Ob"), List(Lit.Int(1))), t.symbol.info=Some(_empty_/Ob. => final object Ob extends AnyRef { +2 decls }), t.synthetics=List()
t=Ob=Term.Name("Ob"), t.symbol.info=Some(_empty_/Ob. => final object Ob extends AnyRef { +2 decls }), t.synthetics=List(Some(Some(_empty_/Ob.apply(). => method apply(i: Int): Unit)))
t=1=Lit.Int(1), t.symbol.info=None, t.synthetics=List()
t=someMethod(1)=Term.Apply(Term.Name("someMethod"), List(Lit.Int(1))), t.symbol.info=Some(_empty_/Use.someMethod(). => method someMethod(i: Int): Unit), t.synthetics=List()
t=someMethod=Term.Name("someMethod"), t.symbol.info=Some(_empty_/Use.someMethod(). => method someMethod(i: Int): Unit), t.synthetics=List()
t=1=Lit.Int(1), t.symbol.info=None, t.synthetics=List()

Notice the line

t=Ob=Term.Name("Ob"), ..., t.synthetics=List(Some(Some(_empty_/Ob.apply(). => method apply(i: Int): Unit)))

See:

https://scalameta.org/docs/semanticdb/specification.html#synthetic

https://scalameta.org/docs/semanticdb/specification.html#synthetic-1

https://scalacenter.github.io/scalafix/docs/developers/semantic-tree.html#look-up-inferred-type-parameter

So

class MyRule extends SemanticRule("MyRule") {
  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree
      .collect {
        case Term.Apply(fun, args) =>
          println(fun.symbol.info + ", " + fun.synthetics.map(_.symbol.map(_.info)))
          Patch.empty
      }.asPatch
  }
}

will print

Some(_empty_/Ob. => final object Ob extends AnyRef { +2 decls }), List(Some(Some(_empty_/Ob.apply(). => method apply(i: Int): Unit)))
Some(_empty_/Use.someMethod(). => method someMethod(i: Int): Unit), List()