Compiler error with case class extending AnyVal and overriding readResolve

How do I eliminated the following compiler error:

type mismatch;
 found   : org.public_domain.Test
 required: Object
Note that Test extends Any, not AnyRef.
Such types can participate in value classes, but instances
cannot appear in singleton types or in reference comparisons.
      Test(value)

Occurring on the following code:

object Test extends (Double => Test) {
  def apply(value: Double): Test =
    new Test(value)
}
final case class Test private(value: Double) extends AnyVal {
  //@annotation.unused
  private def readResolve(): Object =
    Test(value)
}

val t = Test(10d)
println(s"t=$t")

Here’s the code and error on Scastie:

Changing AnyVal to AnyRef fixes it, but I suppose that’s not what you want.

I want it to act as an AnyVal explicitly.

Is there a way to make the compiler happy with an AnyVal?

Or does the deserialization logic not route through this method in Scala for primitives; i.e. AnyVal descendants?

Ultimately, I am trying to ensure an invalid “instance” cannot be “injected/created” during deserialization. Is there another and/or better way to accomplish that than moving it to the far less performant AnyRef?

Maybe you could Test(value).asInstanceOf[AnyRef] to force boxing?

1 Like

Huh – does that work? I usually think of asInstanceOf as being a runtime no-op; hadn’t occurred to me that it could be used to force a box.

In general, the whole thing makes me nervous. The Double is a primitive at the Java level, right? Given that, and Java not knowing much about Scala AnyVals, it seems likely that ObjectInputStream (assuming that’s what this is for – I don’t know the Java side well) is going to get pretty confused…

1 Like

I’d propose package public for this purpose, if the empty package does not suffice.

That could result in some justified pain in java interop for example code.

1 Like

I tried to provide the actual primitive, but got this message which makes even less sense.
the result type of an implicit conversion must be more specific than Object

Here’s the code:

I decided to try Seth’s idea combined with the primitive like this:
Test(value).value.asInstanceOf[java.lang.Object]

And this appeared to work:

Dotty’s message is

-- Error: unresolved.scala:8:40 ----------------------------------------------------------------------------------------
8 |  private def readResolve(): Object = DT(value)
  |                                      ^^^^^^^^^
  |                                      the result of an implicit conversion must be more specific than Object
1 Like

Huh – does that work? I usually think of asInstanceOf as being a runtime no-op; hadn’t occurred to me that it could be used to force a box.

Anything that extends AnyVal must also have a boxed representation for use in generic contexts.

asInstanceOf[AnyRef] works on primitive types as well:

scala 2.13.8> 3.asInstanceOf[AnyRef]
val res5: AnyRef = 3

scala 2.13.8> res5.getClass
val res6: Class[_ <: AnyRef] = class java.lang.Integer

This behavior falls out of a combination of how asInstanceOf is defined in SLS 12.1 and the compiler’s ability to box and unbox as needed in generic contexts.

It’s a bit of a tangent, but since you said that you thought of asInstanceOf being a runtime no-op, I’ll mention that that often isn’t the case on the JVM. For example:

scala 2.13.8> class C; class D
class C
class D

scala 2.13.8> (new C).asInstanceOf[D]
java.lang.ClassCastException: class C cannot be cast to class D

The JVM’s safety guarantees mandate this behavior. You can lie to the compiler all day about what type something is, but the JVM will not allow you to lie at runtime about what a value’s erased type is. (I don’t know what Scala.js or Scala Native do, here.)

2 Likes