Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type members are shadowed by self type's type members #7255

Closed
scabug opened this issue Mar 14, 2013 · 7 comments
Closed

Type members are shadowed by self type's type members #7255

scabug opened this issue Mar 14, 2013 · 7 comments
Assignees

Comments

@scabug
Copy link

scabug commented Mar 14, 2013

The following code

trait A { type C <: Any }

trait B { this: A =>
  type C <: D
  trait D { def foo: Unit }
  def bar(c: C) = c.foo
}

produces the error:

/Users/szeiger/code/stest/typeerror.scala:6: error: value foo is not a member of B.this.C
  def bar(c: C) = c.foo
                    ^

Apparently C in B's self type A shadows B.C instead of getting unified with it. This compiles if A.C is removed or if B extends A.

@scabug
Copy link
Author

scabug commented Mar 14, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7255?orig=1
Reporter: @szeiger
Affected Versions: 2.10.0, 2.10.1-RC3, 2.10.1
See #7278

@scabug
Copy link
Author

scabug commented Mar 14, 2013

@paulp said:
Somewhat reduced.

trait A { type C }
trait B { this: A =>
  type C <: { def foo }
  (??? : C).foo
}

@scabug
Copy link
Author

scabug commented Mar 14, 2013

@paulp said:
This seems to be a pretty fundamental failure of the type system, and maybe part of the motivation for dotty.

Abstract type members aren't unified as normally implied by the word; they can only be refined. What that means in this context is that a type is created which is the intersection of B with its self type, that is, B with A. The intersection type is created assuming it can just use the last 'C' in the linearization, since the compile will fail in refchecks unless the last 'C' is a refinement of any earlier 'C's.

This system breaks down with self types, because a class sees itself as having the intersection of the declared class and the declared self type. Either X with Y or Y with X might be valid instantiations; there is no way a priori to know which is the "last" member C.

// Declare this way, one can create both new A with B and new B with A
trait A { self: B => type M }
trait B { self: A => type M }

// Given some constraints, it can only be created in one direction
trait A { self: B => type M <: AnyRef }
trait B { self: A => type M <: String }

scala> new A with B { }
res2: A with B = $anon$1@3e59503b

scala> new B with A { }
<console>:13: error: overriding type M in trait B with bounds <: String;
 type M in trait A with bounds <: AnyRef has incompatible type
              new B with A { }
                  ^

...but that analysis hasn't taken place yet, and performing that analysis during typing would be a huge change. So it uses the last C it finds, which (at random) is the self type. It could as easily have been the class, and if I switch them, your example compiles:

 --- i/src/compiler/scala/tools/nsc/typechecker/Namers.scala
 +++ w/src/compiler/scala/tools/nsc/typechecker/Namers.scala
 @@ -773,7 +773,7 @@ trait Namers extends MethodSynthesis {
        val selftpe = typer.typedType(tree).tpe
        sym setInfo {
          if (selftpe.typeSymbol isNonBottomSubClass sym.owner) selftpe
 -        else intersectionType(List(sym.owner.tpe, selftpe))
 +        else intersectionType(List(selftpe, sym.owner.tpe))
        }
      }

...except of course that would mean this modified example, which does compile right now, would turn into an error.

trait A {
  type C <: D
  trait D { def foo: Unit }
}

trait B { this: A =>
  type C <: Any
  def bar(c: C) = c.foo
}

tl;dr it should definitely be fixed, but it may require nontrivial changes to language, specification, and/or implementation.

@scabug
Copy link
Author

scabug commented Mar 14, 2013

@paulp said:
Adriaan, can you correct me anywhere I need correcting.

@scabug
Copy link
Author

scabug commented Mar 15, 2013

@odersky said:
Paul, you are correct on all counts. This is indeed a fundamental problem in Scala's type system (also in its theory, \nu-Obj): Abstract type members are linearized and the last one wins. It is later checked that that last one has a constraint that implies all others. But there is never a unification of constraints. DOT fixes this.

@scabug
Copy link
Author

scabug commented Mar 23, 2013

@paulp said (edited on Mar 23, 2013 6:19:27 PM UTC):
See #7278 for unsoundness related to this behavior.

@scabug scabug closed this as completed Jul 9, 2015
@scabug
Copy link
Author

scabug commented Jul 9, 2015

@retronym said:
Closing as out of scope.

Workaround is to explicitly include the enclosing class in the self type so as to be able to control the linearization order:

trait A { type C <: Any }
 
trait B { this: A with B =>
  type C <: D
  trait D { def foo: Unit }
  def bar(c: C) = c.foo
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants