Changing visibility by casting - is it an intended behaviour?

I am sorry if this is a wrong forum/category, I wasn’t sure where such questitons belonged and I would like to hear a semi-official answer.

I have recently discovered a neat trick:

   class BaseValueClass(private val name :String) extends AnyVal {
        protected[BaseValueClass] def amSubclass :String = name
   }
   type ValueSubclass = BaseValueClass { def amSubclass :String }
   
   def ValueSubclass(name :String) = new BaseValueClass(name).asInstanceOf[ValueSubclass]
   val sub = ValueSubclass("hey!")
   val name = sub.amSubclass

This not only compiles, but in Scala 2 (not Scala 3), the call doesn’t use reflection and straigth down takes advantage of being ‘Java public’. I say neat, because I promptly used it to simulate a class hierarchy among value types, but it is also a bit surprising and a potential API leak, so I am not sure if I can safely depend on it? Then again, a protected[scope] declaration can be overriden by extending classes from any package, so there is some consistency. If this is intended, should it really use reflection and require reflectiveSelectable?

2 Likes

I don’t see how it’s a leak. Subclasses are allowed to expand visibility (although not the other way around).

I don’t think it needs to use reflection because it knows exactly which method it needs to call: BaseValueClass's amSubclass method. If you used Any { def amSubclass: String }, then amSubclass could refer to any of a host of methods, but since we know that ValueSubclass is a subtype of BaseValueClass, it could only refer to that protected method. If you make amSubclass private instead, I think it will need to use reflection then because a subclass of BaseValueClass could define its own amSubclass method.

It does use reflection: https://scastie.scala-lang.org/bsQrrERZSNOo0m0drk8LSg

And expanding the visibility of invisible is certainly not obvious - I was pretty surprised. If I declare something as private[myproject] I’d like it to be transparent to anything outside, just as private and ‘package protected’ in Java (hell, now that I say it I’m not even sure if in Java this is forbidden). The fact that private[myproject] is public in the bytecode of course makes complete transparency impossible, but at least I would not want it not to be able to mess with it. One can restrict override by including a package protected type in the signature, but it’s ugly and often inconvenient (espaccialy for values). Consider how for example the companion object doesn’t have access to private methods of its companion class, which often requires making them package protected - this is a very typical use case.

1 Like

Oh, I thought you meant that it didn’t use reflection. That code compiling if it’s just going to error is unexpected, yeah.

There are very few guarantees once you cast. Essentially, you can cast to any type you choose and if the erased type of the value matches the erased target type the cast succeeds at runtime.

So, if you want guarantees, don’t cast. That’s why the name is intentionally awkward to write,

3 Likes

Actually a companion object does have access to the private methods of its companion class, and vice versa.

1 Like

Yes, I like the choice of asInstanceOf. The question was motivated by my interest in various hacks made possible by this behaviour and thus wanting to know if it is going to stay (for the foreseeable future). If I may take the opportunity to ask a somewhat related question: is the fact that private[package] and protected[package] methods can be overriden outside the package a conscious choice or an implementation artifact? I was once surprised to discover that this compiles:

package scope {
    trait Super {
      private[scope] def scopedMethod = "42"
    }  
}
package other {
  trait Sub extends Super {
    override def scopedMethod = "*"
  }
}

It went completely against my intuition that package-private declarations make them transparent to the outside (with the obvious limitation of being public in the bytecode).
I can’t override a package-protected method in Java in a class outside the package (it works like private), I can’t override a protected[scope] class with a class from outside scope and the Scala compiler warns if I override a @deprecated method, all of which seems a bit inconsistent to me. It’s hard for me to imagine a situation where this would be a good idea, but of course I haven’t thought it out through-and-through as SIP members.

@Jasper-M indeed, I was completely wrong; I conflated the cases of private and private[MyClass], and it is the latter that fails (because ‘prefix MyClass does not conform to ‘MyClass$’ where access takes place’ or something to that effect).

1 Like