Skip to content

Commit

Permalink
Merge pull request #15737 from dotty-staging/fix-15723
Browse files Browse the repository at this point in the history
Fix treatment of parameter selections via this in constructors.
  • Loading branch information
odersky authored Jul 23, 2022
2 parents 837b1cc + 4710941 commit 1b299b3
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 10 deletions.
25 changes: 17 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,26 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
// (2) If the parameter accessor reference was to an alias getter,
// drop the () when replacing by the parameter.
object intoConstr extends TreeMap {
private var isSuperCall = false
private var inSuperCall = false
override def transform(tree: Tree)(using Context): Tree = tree match {
case Ident(_) | Select(This(_), _) =>
var sym = tree.symbol
if sym.is(ParamAccessor) && (!sym.is(Mutable) || isSuperCall)
// Variables need to go through the getter since they might have been updated,
// except if we are in a super call, since then the virtual getter call would
// be illegal.
then
def isOverridableSelect = tree.isInstanceOf[Select] && !sym.isEffectivelyFinal
def switchOutsideSupercall = !sym.is(Mutable) && !isOverridableSelect
// If true, switch to constructor parameters also in the constructor body
// that follows the super call.
// Variables need to go through the getter since they might have been updated.
// References via this need to use the getter as well as long as that getter
// can be overridden. This is needed to handle overrides correctly. See run/i15723.scala.
// Note that in a supercall we need to switch to parameters in any case since then
// calling the virtual getter call would be illegal.
//
// Note: We intentionally treat references via this and identifiers differently
// here. Identifiers in a constructor always bind to the parameter. This is
// done for backwards compatbility.
if sym.is(ParamAccessor) && (switchOutsideSupercall || inSuperCall) then
sym = sym.subst(accessors, paramSyms)
if (sym.maybeOwner.isConstructor) ref(sym).withSpan(tree.span) else tree
if sym.maybeOwner.isConstructor then ref(sym).withSpan(tree.span) else tree
case Apply(fn, Nil) =>
val fn1 = transform(fn)
if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
Expand All @@ -170,7 +179,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
}

def apply(tree: Tree, prevOwner: Symbol)(using Context): Tree =
isSuperCall = isSuperConstrCall(tree)
inSuperCall = isSuperConstrCall(tree)
transform(tree).changeOwnerAfter(prevOwner, constr.symbol, thisPhase)
}

Expand Down
12 changes: 10 additions & 2 deletions tests/pos/fewer-braces.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import language.experimental.fewerBraces

object Test:
def Test =

val xo: Option[Int] = Some(1)

val y =
xo.fold:
22
.apply: x =>
x + 1
println(y)

assert((new Object: Any).isInstanceOf[Object])
2 changes: 2 additions & 0 deletions tests/run/i15723.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
20
20
10 changes: 10 additions & 0 deletions tests/run/i15723.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class B(val y: Int) {
println(this.y)
foo()
def foo() = println(this.y)
}
class C(override val y: Int) extends B(10)

object Test extends App {
new C(20)
}
4 changes: 4 additions & 0 deletions tests/run/patmat.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Bob is 22 years old and lives in Paris
Hello Peter
Bob is 22 years old and lives in Paris
Hello PersonExtractor(Peter)
45 changes: 45 additions & 0 deletions tests/run/patmat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
object Test1:
class User(val name: String, val age: Int, val city: String)
object User:
def unapply(user: User) = UserExtractor(user.name, user.age, user.city)

case class UserExtractor(name: String, age: Int, city: String)

class Person(val name: String)
object Person:
def unapply(person: Person) = PersonExtractor(person.name)

case class PersonExtractor(name: String)

def test =
val user = User("Bob", 22, "Paris")
(user: Any) match
case User(name, age, city) => println(s"$name is $age years old and lives in $city")
val p = Person("Peter")
(p: Any) match
case Person(n) => println(s"Hello $n")

object Test2:
class User(val name: String, val age: Int, val city: String)
object User:
def unapply(user: User): Option[UserExtractor] = Some(UserExtractor(user.name, user.age, user.city))

case class UserExtractor(name: String, age: Int, city: String)

class Person(val name: String)
object Person:
def unapply(person: Person): Some[PersonExtractor] = Some(PersonExtractor(person.name))

case class PersonExtractor(name: String)

def test =
val user = User("Bob", 22, "Paris")
(user: Any) match
case User(name, age, city) => println(s"$name is $age years old and lives in $city")
val p = Person("Peter")
(p: Any) match
case Person(n) => println(s"Hello $n")

@main def Test =
Test1.test
Test2.test

0 comments on commit 1b299b3

Please sign in to comment.