SemanticType
Source:
SemanticType.scala
SemanticType
is a sealed data structure that encodes the Scala type system.
sealed abstract class SemanticType extends Product with Serializable {
final override def toString: String = Pretty.pretty(this).render(80)
final def toString(width: Int): String = Pretty.pretty(this).render(width)
final def isEmpty: Boolean = this == NoType
final def nonEmpty: Boolean = !isEmpty
}
final case class TypeRef(prefix: SemanticType, symbol: Symbol, typeArguments: List[SemanticType]) extends SemanticType
final case class SingleType(prefix: SemanticType, symbol: Symbol) extends SemanticType
final case class ThisType(symbol: Symbol) extends SemanticType
final case class SuperType(prefix: SemanticType, symbol: Symbol) extends SemanticType
final case class ConstantType(constant: Constant) extends SemanticType
final case class IntersectionType(types: List[SemanticType]) extends SemanticType
final case class UnionType(types: List[SemanticType]) extends SemanticType
final case class WithType(types: List[SemanticType]) extends SemanticType
final case class StructuralType(tpe: SemanticType, declarations: List[SymbolInformation]) extends SemanticType
final case class AnnotatedType(annotations: List[Annotation], tpe: SemanticType) extends SemanticType
final case class ExistentialType(tpe: SemanticType, declarations: List[SymbolInformation]) extends SemanticType
final case class UniversalType(typeParameters: List[SymbolInformation], tpe: SemanticType) extends SemanticType
final case class ByNameType(tpe: SemanticType) extends SemanticType
final case class RepeatedType(tpe: SemanticType) extends SemanticType
final case class LambdaType(parameters: List[SymbolInformation], returnType: SemanticType) extends SemanticType
final case class MatchType(scrutinee: SemanticType, cases: List[CaseType]) extends SemanticType
case object NoType extends SemanticType
Cookbook
All code examples in this document assume you have the following imports in scope
import scalafix.v1._
Lookup symbol of type
Consider the following program.
// Main.scala
package example
object Main {
val a = 42
val b = List(42)
}
Use MethodSignature.returnType
to get the types of vals.
def getType(symbol: Symbol): SemanticType =
symbol.info.get.signature match {
case MethodSignature(_, _, returnType) =>
returnType
}
println(getType(Symbol("example/Main.a.")))
// Int
println(getType(Symbol("example/Main.a.")).structure)
// TypeRef(NoType, Symbol("scala/Int#"), List())
println(getType(Symbol("example/Main.b.")))
// List[Int]
println(getType(Symbol("example/Main.b.")).structure)
// TypeRef(
// NoType,
// Symbol("scala/collection/immutable/List#"),
// List(TypeRef(NoType, Symbol("scala/Int#"), List()))
// )
Use TypeRef.symbol
to obtain the symbols scala/Int#
and
scala/collection/immutable/List#
from the types Int
or List[Int]
.
def printTypeSymbol(tpe: SemanticType): Unit =
tpe match {
case TypeRef(_, symbol, _) =>
println(symbol)
case error =>
println(s"MatchError: $error (of ${error.productPrefix})")
}
printTypeSymbol(getType(Symbol("example/Main.a.")))
// scala/Int#
printTypeSymbol(getType(Symbol("example/Main.b.")))
// scala/collection/immutable/List#
Beware however that not all types are TypeRef
.
// Main.scala
package example
class Main
object Main {
val a: Main with Serializable = ???
val b: Main.type = this
}
The printTypeSymbol
method crashes on non-TypeRef
types.
printTypeSymbol(getType(Symbol("example/Main.a.")))
// MatchError: Main with Serializable {} (of StructuralType)
printTypeSymbol(getType(Symbol("example/Main.b.")))
// MatchError: Main.type (of SingleType)
Known limitations
The SemanticType
API is new and intentionally comes with a small public API.
Some missing functionality is possible to implement outside of Scalafix.
Lookup type of a term
Consider the following program.
object Main {
val list = List(1).map(_ + 1)
}
There is no API to inspect the type of tree nodes such as List(1)
. It is only
possible to lookup the signature of the symbol Main.list
.
Test for subtyping
Consider the following program.
sealed abstract class A[+T]
case class B(n: Int) extends A[Int]
There is no API to query whether the type B
(structure:
TypeRef(NoType, "B#", Nil)
) is a subtype of A[Int]
(structure
TypeRef(NoType, "A#", List(TypeRef(NoType, "scala/Int#", Nil)))
).
Dealias types
Consider the following program.
// Main.scala
package example
object Main {
val a = "message"
val b: String = "message"
val c: List[String] = List()
}
The a
and b
variables have semantically the same type but their types are
structurally different because the explicit : String
annotation for b
references a type alias.
println(getType(Symbol("example/Main.a.")).structure)
// TypeRef(NoType, Symbol("java/lang/String#"), List())
println(getType(Symbol("example/Main.b.")).structure)
// TypeRef(NoType, Symbol("scala/Predef.String#"), List())
There is no public API to dealias type. It's possible to prototype roughly
similar functionality with the simpleDealias
method below, but beware that it
is an incomplete implementation.
def simpleDealias(tpe: SemanticType): SemanticType = {
def dealiasSymbol(symbol: Symbol): Symbol =
symbol.info.get.signature match {
case TypeSignature(_, lowerBound @ TypeRef(_, dealiased, _), upperBound)
if lowerBound == upperBound =>
dealiased
case _ =>
symbol
}
tpe match {
case TypeRef(prefix, symbol, typeArguments) =>
TypeRef(prefix, dealiasSymbol(symbol), typeArguments.map(simpleDealias))
}
}
println(simpleDealias(getType(Symbol("example/Main.a."))).structure)
// TypeRef(NoType, Symbol("java/lang/String#"), List())
println(simpleDealias(getType(Symbol("example/Main.b."))).structure)
// TypeRef(NoType, Symbol("java/lang/String#"), List())
The simpleDealias
method also handles TypeRef.typeArguments
so that
scala.List[scala.Predef.String]
becomes
scala.collection.immutable.List[java.lang.String]
.
println(getType(Symbol("example/Main.c.")).structure)
// TypeRef(
// NoType,
// Symbol("scala/package.List#"),
// List(TypeRef(NoType, Symbol("scala/Predef.String#"), List()))
// )
println(simpleDealias(getType(Symbol("example/Main.c."))).structure)
// TypeRef(
// NoType,
// Symbol("scala/collection/immutable/List#"),
// List(TypeRef(NoType, Symbol("java/lang/String#"), List()))
// )
SemanticDB
The structure of SemanticType
mirrors SemanticDB Type
. For comprehensive
documentation about SemanticDB types, consult the SemanticDB specification:
The SemanticType
data structure diverges from SemanticDB Type
in few minor
details.
SemanticType
instead of Type
Scalafix uses the name SemanticType
instead of Type
in order to avoid
ambiguous references with scala.meta.Type
when importing the two packages
together.
import scalafix.v1._
import scala.meta._
List[SymbolInformation]
instead of Scope
The SemanticType
data structure uses List[SymbolInformation]
instead of
Scope
, where applicable. This change avoids the notion of "soft-linked" and
"hard-linked" symbols, resulting in a higher-level API without loss of
expressiveness.