Skip to content

Unsoundness: constructor params must not be in scope for the parent types #16270

Closed
@sjrd

Description

@sjrd

Summary

It should not be legal to reference constructor parameters in the supertypes of a class:

class Evil(val outer: Outer) extends Foo[outer.type](...)
class Evil(val outer: Outer) extends outer.Foo(...)

Either of those two things can be used to create unsoundness in the form of a ClassCastException, as shown below.

Fundamentally, the specification should reject those, as in either case, the linearization of Evil is ill-defined. Since the linearization is ill-defined, so is baseType, and everything falls down.

Note that Scala 2 rejects both variants, at least as far back as 2.10.9. @smarter's thesis calculus does not include constructor parameters in the context when typing parent types either.

Compiler version

Today's main, commit 7fd161f

Minimized code

First variant: outer in a type parameter

class Outer {
  type Smuggler
  var smuggler: Option[Smuggler] = None
}
class Foo[T](var unpack: T)
class Evil(val outer: Outer, extract: outer.type => Unit) extends Foo[outer.type](outer) {
  def doExtract(): Unit = extract(unpack)
}

object Test {
  def main(args: Array[String]): Unit = {
    val outer1 = new Outer { type Smuggler = Int }
    outer1.smuggler = Some(5)
    val evil1 = new Evil(outer1, _ => ())

    val outer2 = new Outer { type Smuggler = String }
    var extractedOuter2: Option[outer2.type] = None
    val evil2 = new Evil(outer2, x => extractedOuter2 = Some(x))

    evil2.unpack = evil1.unpack
    evil2.doExtract()
    val smuggled: String = extractedOuter2.get.smuggler.get // <-- Line 32 is here
    println(smuggled)
  }
}

Second variant: outer in the path of the superclass

class Outer {
  class Foo(var unpack: Outer.this.type)

  type Smuggler
  var smuggler: Option[Smuggler] = None
}
class Evil(val outer: Outer, extract: outer.type => Unit) extends outer.Foo(outer) {
  def doExtract(): Unit = extract(unpack)
}

// same object Test

Then run:

> scalac Reproduction.scala
> scala Test

Output

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at Test$.main(hello.scala:32)
        at Test.main(hello.scala)

Expectation

The compiler should reject either of those.

Scala 2 reports:

tests/run/hello.scala:42: error: not found: value outer
class Evil(val outer: Outer) extends Foo[outer.type](outer)
                                         ^

and

tests/run/hello.scala:43: error: not found: value outer
class Evil(val outer: Outer) extends outer.Foo
                                     ^

Credits

Collaboration with @shardulc and @smarter

Metadata

Metadata

Assignees

Labels

area:typeritype:bugitype:soundnessSoundness bug (it lets us compile code that crashes at runtime with a ClassCastException)

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions