Skip to content

Commit 4149df7

Browse files
committed
Disallow constructor params from appearing in parent types for soundness
Fixes #16270.
1 parent 4fa0715 commit 4149df7

File tree

7 files changed

+56
-11
lines changed

7 files changed

+56
-11
lines changed

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
390390
case impl: Template =>
391391
for parent <- impl.parents do
392392
Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos)
393+
// Constructor parameters are in scope when typing a parent.
394+
// While they can safely appear in a parent tree, to preserve
395+
// soundness we need to ensure they don't appear in a parent
396+
// type (#16270).
397+
val illegalRefs = parent.tpe.namedPartsWith(p => p.symbol.is(ParamAccessor) && (p.symbol.owner eq sym))
398+
if illegalRefs.nonEmpty then
399+
report.error(
400+
em"The type of a class parent cannot refer to constructor parameters, but ${parent.tpe} refers to ${illegalRefs.map(_.name.show).mkString(",")}", parent.srcPos)
393401
// Add SourceFile annotation to top-level classes
394402
if sym.owner.is(Package) then
395403
if ctx.compilationUnit.source.exists && sym != defn.SourceFileAnnot then

tests/neg/i16270a.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class Outer {
2+
type Smuggler
3+
var smuggler: Option[Smuggler] = None
4+
}
5+
class Foo[T](var unpack: T)
6+
class Evil(val outer: Outer, extract: outer.type => Unit) extends Foo[outer.type](outer) { // error
7+
def doExtract(): Unit = extract(unpack)
8+
}
9+
10+
object Test {
11+
def main(args: Array[String]): Unit = {
12+
val outer1 = new Outer { type Smuggler = Int }
13+
outer1.smuggler = Some(5)
14+
val evil1 = new Evil(outer1, _ => ())
15+
16+
val outer2 = new Outer { type Smuggler = String }
17+
var extractedOuter2: Option[outer2.type] = None
18+
val evil2 = new Evil(outer2, x => extractedOuter2 = Some(x))
19+
20+
evil2.unpack = evil1.unpack
21+
evil2.doExtract()
22+
val smuggled: String = extractedOuter2.get.smuggler.get
23+
println(smuggled)
24+
}
25+
}

tests/neg/i16270b.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Outer {
2+
class Foo(var unpack: Outer.this.type)
3+
4+
type Smuggler
5+
var smuggler: Option[Smuggler] = None
6+
}
7+
class Evil(val outer: Outer, extract: outer.type => Unit) extends outer.Foo(outer) { // error
8+
def doExtract(): Unit = extract(unpack)
9+
}

tests/neg/i16270c.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Foo[T <: Singleton](x: T)
2+
class Outer
3+
class Evil(val outer: Outer) extends Foo(outer) // error (because outer.type appears in the inferred type)

tests/neg/i3935.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
enum Foo3[T](x: T) {
2+
case Bar[S, T](y: T) extends Foo3[y.type](y) // error
3+
}
4+
5+
// val foo: Foo3.Bar[Nothing, 3] = Foo3.Bar(3)
6+
// val bar = foo
7+
8+
// def baz[T](f: Foo3[T]): f.type = f
9+
10+
// val qux = baz(bar) // existentials are back in Dotty?

tests/pos/i5636.scala renamed to tests/neg/i5636.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ trait Bar[X] {
44
def foo: X = ???
55
}
66
// same for `class Foo(...)...`
7-
trait Foo(val a: A) extends Bar[a.type] {
7+
trait Foo(val a: A) extends Bar[a.type] { // error
88
val same: a.type = foo
99
}

tests/pos/i3935.scala

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)