Skip to content

Commit e16dcde

Browse files
committed
Fix the hoisting of multiple-assignment targets
1 parent 6cf4e15 commit e16dcde

File tree

2 files changed

+72
-34
lines changed

2 files changed

+72
-34
lines changed

compiler/src/dotty/tools/dotc/typer/PartialAssignment.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ private[typer] final class PossiblyHoistedValue private (representation: tpd.Tre
5757
object PossiblyHoistedValue:
5858

5959
/** Creates a value representing the `e`'s evaluation. */
60-
def apply(e: tpd.Tree)(using Context): PossiblyHoistedValue =
61-
if tpd.exprPurity(e) >= TreeInfo.Pure then
60+
def apply(e: tpd.Tree, isSingleAssignment: Boolean)(using Context): PossiblyHoistedValue =
61+
if isSingleAssignment || (tpd.exprPurity(e) >= TreeInfo.Pure) then
6262
new PossiblyHoistedValue(e)
6363
else
6464
new PossiblyHoistedValue(tpd.SyntheticValDef(TempResultName.fresh(), e))
@@ -122,11 +122,17 @@ object ApplyLValue:
122122

123123
object Callee:
124124

125-
def apply(receiver: tpd.Tree)(using Context): Typed =
126-
Typed(PossiblyHoistedValue(receiver), None)
127-
128-
def apply(receiver: tpd.Tree, member: Name)(using Context): Typed =
129-
Typed(PossiblyHoistedValue(receiver), Some(member))
125+
/** Creates an instance from a function represented as a typed tree. */
126+
def apply(
127+
receiver: tpd.Tree, isSingleAssignment: Boolean
128+
)(using Context): Typed =
129+
Typed(PossiblyHoistedValue(receiver, isSingleAssignment), None)
130+
131+
/** Creates an instance denoting a selection on a receiver represented as a typed tree. */
132+
def apply(
133+
receiver: tpd.Tree, member: Name, isSingleAssignment: Boolean
134+
)(using Context): Typed =
135+
Typed(PossiblyHoistedValue(receiver, isSingleAssignment), Some(member))
130136

131137
/** A function representing a lvalue. */
132138
final case class Typed(receiver: PossiblyHoistedValue, member: Option[Name]) extends Callee:

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,39 +1298,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
12981298
}
12991299

13001300
/** Returns a builder for making trees representing assignments to `lhs`. */
1301-
def formPartialAssignmentTo(lhs: untpd.Tree)(using Context): PartialAssignment[LValue] =
1301+
def formPartialAssignmentTo(
1302+
lhs: untpd.Tree, isSingleAssignment: Boolean
1303+
)(using Context): PartialAssignment[LValue] =
13021304
lhs match
13031305
case lhs @ Apply(f, as) =>
13041306
// LHS is an application `f(a1, ..., an)` that desugars to `f.update(a1, ..., an, rhs)`.
1305-
val arguments = as.map((a) => PossiblyHoistedValue(typed(a)))
1306-
val lvalue = ApplyLValue(ApplyLValue.Callee(typed(f), nme.update), arguments)
1307+
val arguments = as.map((a) => PossiblyHoistedValue(typed(a), isSingleAssignment))
1308+
val callee = ApplyLValue.Callee(typed(f), nme.update, isSingleAssignment)
1309+
val lvalue = ApplyLValue(callee, arguments)
13071310
PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) }
13081311

13091312
case untpd.TypedSplice(Apply(MaybePoly(Select(fn, app), tas), as)) if app == nme.apply =>
13101313
if tas.isEmpty then
13111314
// No type arguments: fall back to a regular update.
1312-
val arguments = as.map(PossiblyHoistedValue.apply)
1313-
val lvalue = ApplyLValue(ApplyLValue.Callee(fn, nme.update), arguments)
1315+
val arguments = as.map((a) => PossiblyHoistedValue(a, isSingleAssignment))
1316+
val callee = ApplyLValue.Callee(fn, nme.update, isSingleAssignment)
1317+
val lvalue = ApplyLValue(callee, arguments)
13141318
PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) }
13151319
else
13161320
// Type arguments are present; the LHS requires a type application.
13171321
val s: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update)
13181322
val t = untpd.TypeApply(s, tas.map((ta) => untpd.TypedSplice(ta)))
1319-
val arguments = as.map(PossiblyHoistedValue.apply)
1320-
val lvalue = ApplyLValue(ApplyLValue.Callee(typed(t)), arguments)
1323+
val arguments = as.map((a) => PossiblyHoistedValue(a, isSingleAssignment))
1324+
val callee = ApplyLValue.Callee(typed(t), isSingleAssignment)
1325+
val lvalue = ApplyLValue(callee, arguments)
13211326
PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) }
13221327

13231328
case _ =>
1324-
formPartialAssignmentToNonApply(lhs)
1329+
formPartialAssignmentToNonApply(lhs, isSingleAssignment)
13251330

13261331
/** Returns a builder for making trees representing assignments to `lhs`, which isn't a term or
13271332
* type application.
13281333
*/
1329-
def formPartialAssignmentToNonApply(lhs: untpd.Tree)(using Context): PartialAssignment[LValue] =
1334+
def formPartialAssignmentToNonApply(
1335+
lhs: untpd.Tree, isSingleAssignment: Boolean
1336+
)(using Context): PartialAssignment[LValue] =
13301337
val locked = ctx.typerState.ownedVars
13311338
val core = typedUnadapted(lhs, LhsProto, locked)
13321339
def adapted = adapt(core, LhsProto, locked)
13331340

1341+
/** Returns a builder reporting that the left-hand side is not reassignable. */
13341342
def reassignmentToVal(): PartialAssignment[SimpleLValue] =
13351343
PartialAssignment(SimpleLValue(core)) { (l, r) =>
13361344
val target = l.expression
@@ -1357,10 +1365,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13571365

13581366
core match
13591367
case Apply(f, _) if f.symbol.is(ExtensionMethod) =>
1360-
formPartialAssignmentToExtensionApply(core).getOrElse(reassignmentToVal())
1368+
formPartialAssignmentToExtensionApply(core, isSingleAssignment)
1369+
.getOrElse(reassignmentToVal())
13611370

13621371
case _ => core.tpe match
1363-
case r: TermRef =>
1372+
case r: TermRef if !mustFormSetter(adapted, isSingleAssignment) =>
13641373
val v = core.denot.suchThat(!_.is(Method))
13651374
if canAssign(v.symbol) then
13661375
rememberNonLocalAssignToPrivate(v.symbol)
@@ -1387,6 +1396,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13871396
case _ =>
13881397
reassignmentToVal()
13891398

1399+
case r: TermRef =>
1400+
val (setter, locals) = formSetter(adapted, isExtensionReceiver=false, isSingleAssignment)
1401+
val lvalue = ApplyLValue(ApplyLValue.Callee.Untyped(setter, locals), List())
1402+
PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) }
1403+
13901404
case TryDynamicCallType =>
13911405
formPartialDynamicAssignment(lhs)
13921406

@@ -1397,37 +1411,45 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13971411
* defined in an extension.
13981412
*/
13991413
def formPartialAssignmentToExtensionApply(
1400-
lhs: Tree
1414+
lhs: Tree, isSingleAssignment: Boolean
14011415
)(using Context): Option[PartialAssignment[LValue]] =
1402-
/** Returns the setter corresponding to `lhs`, which is a getter, along hoisted definitions. */
1403-
def formSetter(lhs: Tree, locals: List[ValDef]): (untpd.Tree, List[ValDef]) =
1416+
val (setter, locals) = formSetter(lhs, isExtensionReceiver=true, isSingleAssignment)
1417+
if setter.isEmpty then None else
1418+
val lvalue = ApplyLValue(ApplyLValue.Callee.Untyped(setter, locals), List())
1419+
Some(PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) })
1420+
1421+
/** Returns the setter corresponding to `lhs`, which is a getter, along hoisted definitions. */
1422+
def formSetter(
1423+
lhs: Tree, isExtensionReceiver: Boolean, isSingleAssignment: Boolean
1424+
)(using Context): (untpd.Tree, List[ValDef]) =
1425+
def recurse(lhs: Tree, locals: List[ValDef]): (untpd.Tree, List[ValDef]) =
14041426
lhs match
14051427
case f @ Ident(name: TermName) =>
14061428
// We need to make sure that the prefix of this extension getter is retained when we
14071429
// transform it into a setter. Otherwise, we could end up resolving an unrelated setter
14081430
// from another extension. See tests/pos/i18713.scala for an example.
14091431
f.tpe match
14101432
case TermRef(q: TermRef, _) =>
1411-
formSetter(ref(q).select(f.symbol).withSpan(f.span), locals)
1433+
recurse(ref(q).select(f.symbol).withSpan(f.span), locals)
14121434
case TermRef(q: ThisType, _) =>
1413-
formSetter(This(q.cls).select(f.symbol).withSpan(f.span), locals)
1435+
recurse(This(q.cls).select(f.symbol).withSpan(f.span), locals)
14141436
case TermRef(NoPrefix, _) =>
14151437
(untpd.cpy.Ident(f)(name.setterName), locals)
14161438

14171439
case f @ Select(q, name: TermName) =>
1418-
val (v, d) = PossiblyHoistedValue(q).valueAndDefinition
1440+
val (v, d) = PossiblyHoistedValue(q, isSingleAssignment).valueAndDefinition
14191441
(untpd.cpy.Select(f)(untpd.TypedSplice(v), name.setterName), locals ++ d)
14201442

14211443
case f @ TypeApply(g, ts) =>
1422-
val (s, cs) = formSetter(g, locals)
1444+
val (s, cs) = recurse(g, locals)
14231445
(untpd.cpy.TypeApply(f)(s, ts.map((t) => untpd.TypedSplice(t))), cs)
14241446

14251447
case f @ Apply(g, as) =>
1426-
var (s, newLocals) = formSetter(g, locals)
1448+
var (s, newLocals) = recurse(g, locals)
14271449
var arguments = List[untpd.Tree]()
14281450
for a <- as do
1429-
val (v, d) = PossiblyHoistedValue(a).valueAndDefinition
1430-
arguments = untpd.TypedSplice(v, isExtensionReceiver = true) +: arguments
1451+
val (v, d) = PossiblyHoistedValue(a, isSingleAssignment).valueAndDefinition
1452+
arguments = untpd.TypedSplice(v, isExtensionReceiver) +: arguments
14311453
newLocals = newLocals ++ d
14321454

14331455
val setter = untpd.cpy.Apply(f)(s, arguments)
@@ -1443,10 +1465,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
14431465
case _ =>
14441466
(EmptyTree, List())
14451467

1446-
val (setter, locals) = formSetter(lhs, List())
1447-
if setter.isEmpty then None else
1448-
val lvalue = ApplyLValue(ApplyLValue.Callee.Untyped(setter, locals), List())
1449-
Some(PartialAssignment(lvalue) { (l, r) => l.formAssignment(r) })
1468+
recurse(lhs, List())
1469+
1470+
/** Returns whether `t` should be desugared as a setter to form a partial assignment. */
1471+
def mustFormSetter(t: tpd.Tree, isSingleAssignment: Boolean)(using Context) =
1472+
!isSingleAssignment && (
1473+
t match
1474+
case f @ Ident(_) => f.tpe match
1475+
case TermRef(NoPrefix, _) => false
1476+
case _ => true
1477+
case f @ Select(q, _) =>
1478+
!(exprPurity(q) >= TreeInfo.Pure)
1479+
case _ =>
1480+
true
1481+
)
14501482

14511483
def typedAssign(tree: untpd.Assign, pt: Type)(using Context): Tree =
14521484
tree.lhs match
@@ -1455,7 +1487,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
14551487
typedMultipleAssign(lhs, tree.rhs)
14561488
case _ =>
14571489
// Simple assignment.
1458-
val assignmentBuilder = formPartialAssignmentTo(tree.lhs)
1490+
val assignmentBuilder = formPartialAssignmentTo(tree.lhs, isSingleAssignment=true)
14591491
val locals = assignmentBuilder.lhs.locals.map((d) => untpd.TypedSplice(d))
14601492
if locals.isEmpty then
14611493
typed(assignmentBuilder(tree.rhs))
@@ -1481,7 +1513,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
14811513
val s = PartialAssignment(SimpleLValue(e)) { (l, _) => l.expression }
14821514
assignmentBuilders.append(s)
14831515
case _ =>
1484-
val s = formPartialAssignmentTo(l)
1516+
val s = formPartialAssignmentTo(l, false)
14851517
statements.appendAll(s.lhs.locals.map((d) => untpd.TypedSplice(d)))
14861518
assignmentBuilders.append(s)
14871519

0 commit comments

Comments
 (0)