0

If I do this:

object Parent {
    class Inner extends Testable { type Self <: Inner }
    def inner = new Inner()
}

object Child {
    class Inner extends Parent.Inner { type Self <: Inner }
    def inner = new Inner()
}

trait Testable {
    type Self
    def test[T <: Self] = {}
}

object Main {
    // this works
    val p: Parent.Inner = Child.inner
    // this doesn't
    val h = Parent.inner
    h.test[Child.Inner]
}

I get this error:

error: type arguments [Child.Inner] do not conform to method test's type parameter bounds [T <: Main.h.Self]
    h.test[Child.Inner]

Why does this error when I my Self type is Parent.Inner and Child.Inner <: Parent.Inner?


And if I change type Self <: Inner to type Self = Inner and then override type Self = Inner, I get this error:

overriding type Self in class Inner, which equals Parent.Inner;
 type Self has incompatible type
    class Inner extends Parent.Inner { override type Self = Inner }

2 Answers 2

4

This is a problem with path dependent types.

The test method of object h does not expect, as you would assume, a subtype of Parent.Inner. It expects a subtype of h.Self which is a slightly different type. Even though Child.Inner is a subtype of Parent.Inner, it is not a subtype of h.Self and that's why the compiler complains.

The problem with type members is that they are path dependent - they're bound to their enclosing instance and scalac will not allow you to pass type member of one instance where type member of another instance is expected. Child.Inner is not bound to any instance at all and will also be rejected.

Why is this needed? Look at this very similar code:

object Main {
  class C extends Child.Inner { type Self = C }

  val h: Parent.Inner = new C
  h.test[Child.Inner]
}

When looking at types, this code is exactly the same as yours (in particular, the type of h is exactly the same). But this code is clearly incorrect, because h.Self is actually C and Child.Inner is not a subtype of C. That's why scalac correctly rejects it.

You would expect that in your snippet scalac should remember that the type of h is exactly Parent.Inner, but unfortunately it doesn't keep that information. It only knows that h is some subtype of Parent.Inner.

Sign up to request clarification or add additional context in comments.

3 Comments

Well, yes to the first part, no to the second. The second thing, about static types differing from dynamic types, is exactly what I'm trying to beat. The point is for the object's dynamic type to be checked "statically" (for lack of better words).
Can you think of a way to beat the path-dependent types problem?
-1

It appears that you are thinking of def inner = new Inner() as a field instead of thinking of it as a val. The difference is that a value is determined at the time a class is loaded, while the value using the def keyword is determined at run-time. This may not be exactly what you are looking for, as it answers another question, but there seems to be something funky going on here.

Since I am curious myself:

val p : Parent.Inner = Child.inner

This is obviously true, with this line:

class Inner extends Parent.Inner

however if we do:

val h = Parent.inner h.test[Parent.Inner]

does not work either with the following error:

type arguments [Parent.Inner] do not conform to method test's type parameter bounds [T <: h.Self] h.test[Parent.Inner]

even more curiously (or maybe not curiously, since this is a subtype of everything) this works: h.test[Nothing]

2 Comments

-1: This is a type error and has nothing to do when some values are loaded.
Yeah, I want it to be a factory. This isn't my actual code, just an isolation of the code causing the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.