How to get the type of a variable with scalameta if the decltpe is empty?

591 views Asked by At

If I have the following type

Defn.Var(mods, pats, decltpe, rhs) 

in scalameta it might happen that decltype is set to None for a variable like this:

var x = 10

I still want to know the exact type of the variable x which Scala has inferred without checking the type of the assignment expression myself. I know that I can just get the information that 10 is an Int literal but for more complex expressions it might be helpful to have some helper function for the type. Is there any function in scalameta which gives you the inferred type?

1

There are 1 answers

6
Dmytro Mitin On BEST ANSWER

Scalameta

libraryDependencies += "org.scalameta" %% "scalameta" % "4.2.0"

mostly works with source code (parses it to trees, transforms trees) before its compilation i.e. when there can't be any information about symbols and types. To get information about symbols and types one should start compiler (one of).

This is exactly what SemanticDB is for. If you switch on semanticdb-scalac compiler plugin

addCompilerPlugin("org.scalameta" % "semanticdb-scalac" % "4.1.0" cross CrossVersion.full)
scalacOptions ++= Seq(
  "-Yrangepos",
  "-P:semanticdb:synthetics:on",
)

then upon compilation it will generate .semanticdb files near to .class files. Information about symbols and types will be there. These files can be parsed with semanticdb

libraryDependencies += "org.scalameta" %% "semanticdb" % "4.1.0"

For example if you have App1.scala

object App1 {
  var x = 10
}

then

import java.nio.file.Paths
import scala.meta.internal.semanticdb.Locator
Locator(
  Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
  println(textDocuments)
)

produces

TextDocuments(Vector(TextDocument(SEMANTICDB4,src/main/scala/App1.scala,,29E9BFD566BEFD436FBE59679524E53D,SCALA,Vector(SymbolInformation(_empty_/App1.`x_=`().,SCALA,METHOD,2048,x_=,MethodSignature(Some(Scope(Vector(),Vector())),Vector(Scope(Vector(_empty_/App1.`x_=`().(x$1)),Vector())),TypeRef(Empty,scala/Unit#,Vector())),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.,SCALA,OBJECT,8,App1,ClassSignature(Some(Scope(Vector(),Vector())),Vector(TypeRef(Empty,scala/AnyRef#,Vector())),Empty,Some(Scope(Vector(_empty_/App1.x()., _empty_/App1.`x_=`().),Vector()))),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.`x_=`().(x$1),SCALA,PARAMETER,0,x$1,ValueSignature(TypeRef(Empty,scala/Int#,Vector())),Vector(),Empty), SymbolInformation(_empty_/App1.x().,SCALA,METHOD,2048,x,MethodSignature(Some(Scope(Vector(),Vector())),Vector(),TypeRef(Empty,scala/Int#,Vector())),Vector(),PublicAccess())),Vector(SymbolOccurrence(Some(Range(1,6,1,7)),_empty_/App1.x().,DEFINITION), SymbolOccurrence(Some(Range(0,7,0,11)),_empty_/App1.,DEFINITION)),Vector(),Vector())))

You can pretty-print this file with Metap

import scala.meta.cli.Metap
Metap.main(Array("target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb"))

Output

src/main/scala/App1.scala
-------------------------

Summary:
Schema => SemanticDB v4
Uri => src/main/scala/App1.scala
Text => empty
Language => Scala
Symbols => 4 entries
Occurrences => 2 entries

Symbols:
_empty_/App1. => final object App1 extends AnyRef { +2 decls }
_empty_/App1.`x_=`(). => var method x_=(x$1: Int): Unit
_empty_/App1.`x_=`().(x$1) => param x$1: Int
_empty_/App1.x(). => var method x: Int

Occurrences:
[0:7..0:11) <= _empty_/App1.
[1:6..1:7) <= _empty_/App1.x().

And

import scala.meta.internal.semanticdb.TypeRef
import scala.meta.internal.semanticdb.SignatureMessage.SealedValue.{ClassSignature, MethodSignature, TypeSignature, ValueSignature, Empty}

Locator(
  Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
  for {
    document <- textDocuments.documents
    symbol <- document.symbols
  } println(s"symbol=${symbol.displayName}, ${symbol.signature.asMessage.sealedValue match {
    case v: ValueSignature => s"ValueSignature, type=${v.value.tpe match { case t: TypeRef => t.symbol}}"
    case m: MethodSignature => s"MethodSignature, returnType=${m.value.returnType match { case t: TypeRef => t.symbol}}"
    case c: ClassSignature => "ClassSignature"
    case t: TypeSignature => "TypeSignature"
    case Empty => "Empty"
  }}")
)

produces

symbol=x_=, MethodSignature, returnType=scala/Unit#
symbol=App1, ClassSignature
symbol=x$1, ValueSignature, type=scala/Int#
symbol=x, MethodSignature, returnType=scala/Int#

Scheme is here.

Also you can try Scalafix

sbt new scalacenter/scalafix.g8 --repo="scalafixdemo"
cd scalafix
sbt ~tests/test

If you write in input/src/main/scala/fix/Scalafixdemo.scala

package fix

object Scalafixdemo {
  var x = 10
}

and in rules/src/main/scala/fix/Scalafixdemo.scala

package fix

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

class Scalafixdemo extends SemanticRule("Scalafixdemo") {

  override def fix(implicit doc: SemanticDocument): Patch = {
//    println("Tree.syntax: " + doc.tree.syntax)
//    println("Tree.structure: " + doc.tree.structure)
//    println("Tree.structureLabeled: " + doc.tree.structureLabeled)

    doc.tree.traverse {
      case t@q"..$mods var ..$patsnel: $tpeopt = $expropt" =>
        println(t.symbol.info.get.signature)
    }

    Patch.empty
  }
}

then it will print : Int

https://github.com/DmytroMitin/scalafix-codegen