0
def test[T: ClassTag]: T = {
  println(classTag[T])
  null.asInstanceOf[T]
}
val x1: Int = test
val x2: Int = test[Int]

prints

Nothing
Int

I would expect the compiler to guess the Int type without the need to provide it explicitly (EDIT: on the right hand side, i.e. make val x1: Int = test work).

Is there perhaps a workaround to get rid of the explicit type annotation?

Kamil Kloch
  • 333
  • 1
  • 9
  • You are probably expecting wrong things. As your question does not clarify what you were expecting and how Scala is not matching your expectations, you will have to add clear details. – sarveshseri Dec 23 '22 at 12:08
  • From what I can see, Scala can perfectly infer the type of `x2` as `Int` in `val x2 = test[Int]` even without explicitly saying `val x2: Int`. What exactly is not working for you ? – sarveshseri Dec 23 '22 at 12:13
  • 2
    I would expect Scala to infer the type paramter `T == Int` (not `Nothing `) in the line `val x1: Int = test` – Kamil Kloch Dec 23 '22 at 12:20
  • You can get what you expected in Scala3 ^_^,there are many enhance of type inference in Scala3. – Eastsun Dec 25 '22 at 15:06
  • 1
    @Eastsun Well, the difference with Scala 3 is now that in Scala 3 `Nothing` doesn't have class tag https://scastie.scala-lang.org/DmytroMitin/OAEoGDbmTiafBCrK5KGcmw/3 while in Scala 2 it does https://scastie.scala-lang.org/DmytroMitin/OAEoGDbmTiafBCrK5KGcmw/4 – Dmytro Mitin Dec 28 '22 at 13:32

3 Answers3

5

I suspect that compiler can't infer Int in val x1: Int = test[???] because both options:

val x1: Int = test[Int] // returns Int

and

val x1: Int = test[Nothing] // returns Nothing <: Int

are valid. So compiler just has to guess what option you meant.

When compiler has a choice it often selects the minimal type out of the options. And currently this is Nothing.

Why Scala Infer the Bottom Type when the type parameter is not specified?


In principle, if you'd like to explore the type of left hand side you can make test a macro. Then it can be even not generic. Making it whitebox means that it can return a type more precise than declared (Any) e.g. Int.

import scala.reflect.macros.whitebox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.language.experimental.macros

def test: Any = macro testImpl

def testImpl(c: whitebox.Context): c.Tree = {
  import c.universe._
  val T = c.internal.enclosingOwner.typeSignature
  println(s"T=$T")
  q"""
    _root_.scala.Predef.println(_root_.scala.reflect.runtime.universe.typeOf[$T])
    null.asInstanceOf[$T]
  """
}
// in a different subproject

val x1: Int = test // prints at runtime: Int

  // scalacOptions += "-Ymacro-debug-lite"
//scalac: T=Int

//scalac: {
//  _root_.scala.Predef.println(_root_.scala.reflect.runtime.universe.typeOf[Int]);
//  null.asInstanceOf[Int]
//}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Yeah this one also makes complete sense, I tried implementing a more verbose version of the same exact method like this: `def testImpl[T, U >: T : ClassTag, L <: T : ClassTag]: T = { println(classTag[L]); println(classTag[U]; null.asInstanceOf[T] }`, and then delegating the implementation is `test[T]` method to `testImpl[T, T, T]`. And what I saw is that both upper bound and lower bounds of T are `Nothing`, so it makes sense that `T` is also Nothing! – AminMal Dec 23 '22 at 17:09
  • I guess at least in Scala2, this problem cannot be solved, it should be handled with alternatives such as `Either` or something. @KamilKloch – AminMal Dec 23 '22 at 17:12
  • @AminMal Well, I solved it eventually but with macros. – Dmytro Mitin Dec 23 '22 at 20:21
2

Actually there's nothing wrong with type inference here, type inference here means that the compiler should figure out that the T type parameter is Int, and the returned value from your method expression is an integer, and that's working properly:

x1: Int = 0 // this is the result of your first line

You can also try printing this:

println(x1.getClass) // int

What's not working as expected is implicit resolution for a generic type "ClassTag[_]". The reason it prints Nothing is that the compiler found the object scala.reflect.ClassTag.Nothing : ClassTag[Nothing] suitable for your case. Now about this new thing, there are loads of content on SO and the internet for why this happens and how to deal with it in different cases.

Here's another piece of code to differentiate type inference with type erasure in your case:

def nullAs[T]: T = null.asInstanceOf[T]

val int: Int = nullAs // 0: Int

// type erasure:
case class Container[T](value: T)
implicit val charContainer: Container[Char] = Container('c')

def nullWithContainer[T](implicit container: Container[T]): T = {
  println(container)
  null.asInstanceOf[T]
}

val long: Long = nullWithContainer
// prints Container(c)
// 0L

Which means type inference is done correctly, but type erasure has happened, because in runtime, the type of charContainer is Container, not Container[Char].

AminMal
  • 3,070
  • 2
  • 6
  • 15
0
val x1: Int = test

In this line, the Int you have given is the type for the value x1 which would hold the result of the function test and as for the classTag of T the compiler finds no information which is why it returns Nothing, in which case its taking val x1: Int = test[Nothing] so you would always have to mention a Type for test else it would print Nothing

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66