gaeljw / typetrees   0.5.0

Apache License 2.0 GitHub

This library is intended to solve the use case of getting the erased types of a (generic) parameter. Something which was possible in Scala 2 with TypeTag and is now a bit more complex with Scala 3.

Scala versions: 3.x

TypeTrees

typetrees 3
badge

This library is intended to solve the use case of getting the erased types of a (generic) parameter. Something which was possible in Scala 2 with TypeTag and is now a bit more complex with Scala 3.

Get it

sbt
libraryDependencies += "io.github.gaeljw" %% "typetrees" % typetreesVersion
Maven
<dependency>
  <groupId>io.github.gaeljw</groupId>
  <artifactId>typetrees_${scala.version}</artifactId>
  <version>${typetrees.version}</version>
</dependency>

Usage

Two implementations are available:

  • using implicits (given s)

  • using macro

Implementation Pros 👍 Cons 👎

Implicits

Easier

Only supports up to 4 generic parameters (i.e. a type T[A, B, C, D])

Macro

Can handle all cases

Requires to be used in inline method

Using implicits

import io.github.gaeljw.typetrees.TypeTree
import io.github.gaeljw.typetrees.TypeTreeTag

import scala.reflect.ClassTag

val tag: TypeTreeTag = summon[TypeTree[T]].tag // (1)

val classTag: ClassTag[_] = tag.self // (2)
val actualClass: Class[_] = classTag.runtimeClass

val typeParameters: List[TypeTreeTag] = tag.args // (3)
  1. Get a TypeTreeTag for a generic type T

  2. Get a ClassTag for this type

  3. Get a TypeTreeTag for each type parameters if any

Examples

The main usage is as follows:

def someGenericMethod[T](t: T)(using typeTree: TypeTree[T]): String = {
    val tag: TypeTreeTag = typeTree.tag
    s"I have been called with a parameter of type $tag"
}

someGenericMethod(Map[String, Int]())
// Gives: I have been called with a parameter of type TypeTreeTag(scala.collection.immutable.Map,List(TypeTreeTag(java.lang.String,List()), TypeTreeTag(Int,List())))

Or with context bounds:

def someGenericMethod[T : TypeTree](t: T): String = {
    val tag: TypeTreeTag = summon[TypeTree[T]].tag
    s"I have been called with a parameter of type $tag"
}

You can find more examples in our tests.

Using macro

import io.github.gaeljw.typetrees.TypeTreeTag
import io.github.gaeljw.typetrees.TypeTreeTagMacros.typeTreeTag

import scala.reflect.ClassTag

val tag: TypeTreeTag = typeTreeTag[T] // (1)

val classTag: ClassTag[_] = tag.self // (2)
val actualClass: Class[_] = classTag.runtimeClass

val typeParameters: List[TypeTreeTag] = tag.args // (3)
  1. Get a TypeTreeTag for a generic type T

  2. Get a ClassTag for this type

  3. Get a TypeTreeTag for each type parameters if any

Examples

The main usage is as follows, within a generic inline method:

inline def someGenericMethod[T](t: T): String = {
    val tag: TypeTreeTag = typeTreeTag[T]
    s"I have been called with a parameter of type $tag"
}

someGenericMethod(Map[String, Int]())
// Gives: I have been called with a parameter of type TypeTreeTag(scala.collection.immutable.Map,List(TypeTreeTag(java.lang.String,List()), TypeTreeTag(Int,List())))

Or:

inline def someGenericMapMethod[T <: Map[_,_]](map: T): String = {
    val mapTag: TypeTreeTag = typeTreeTag[T]
    val keyTag: TypeTreeTag = mapTag.args(0)
    val valueTag: TypeTreeTag = mapTag.args(1)
    s"I have been called with a Map where key is of type $keyTag and value is of type $valueTag"
}

It can also be applied to non generic types: in such case it doesn’t need to be part of a inline def but you probably can just use regular ClassTag then.

You can find more examples in our tests.