Skip to content

Commit 9c8f7f6

Browse files
committed
Fix the computation of partial assignments to setter in extensions
1 parent 9d96531 commit 9c8f7f6

File tree

2 files changed

+66
-63
lines changed

2 files changed

+66
-63
lines changed

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

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ private[typer] sealed abstract class LValue:
3131
/** Returns the local `val` definitions composing this lvalue. */
3232
def locals: List[tpd.ValDef]
3333

34-
/** Returns this lvalue converted to a rvalue. */
35-
def toRValue(using Context): untpd.Tree
36-
3734
/** Returns a tree computing the assignment of `rhs` to this lvalue. */
3835
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree
3936

@@ -45,6 +42,15 @@ private[typer] sealed abstract class LValue:
4542

4643
end LValue
4744

45+
private[typer] final case class UnappliedSetter(
46+
expression: untpd.Tree, locals: List[tpd.ValDef]
47+
) extends LValue:
48+
49+
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
50+
untpd.Apply(expression, List(rhs))
51+
52+
end UnappliedSetter
53+
4854
/** A simple expression, typically valid on left-hand side of an `Assign` tree.
4955
*
5056
* Use this class to represent an assignment that translates to an `Assign` tree or to wrap an
@@ -57,9 +63,6 @@ private[typer] final case class SimpleLValue(expression: tpd.Tree) extends LValu
5763
def locals: List[tpd.ValDef] =
5864
List()
5965

60-
def toRValue(using Context): untpd.Tree =
61-
untpd.TypedSplice(expression)
62-
6366
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
6467
val s = untpd.Assign(untpd.TypedSplice(expression), rhs)
6568
untpd.TypedSplice(s.withType(defn.UnitType))
@@ -81,9 +84,6 @@ private[typer] final case class ApplyLValue(
8184
val locals: List[tpd.ValDef] =
8285
(function +: arguments).collect { case d: tpd.ValDef => d }
8386

84-
def toRValue(using Context): untpd.Tree =
85-
untpd.Apply(function, arguments)
86-
8787
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
8888
val s = untpd.TypedSplice(read(function))
8989
val t = arguments.map((a) => untpd.TypedSplice(read(a))) :+ rhs
@@ -111,10 +111,6 @@ private[typer] final case class SelectLValue(
111111
val locals: List[tpd.ValDef] =
112112
(receiver +: arguments).collect { case d: tpd.ValDef => d }
113113

114-
def toRValue(using Context): untpd.Tree =
115-
require(arguments.isEmpty)
116-
untpd.Select(untpd.TypedSplice(expandReceiver()), member)
117-
118114
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
119115
val s = untpd.Select(untpd.TypedSplice(read(receiver)), member)
120116
val t = arguments.map((a) => untpd.TypedSplice(read(a))) :+ rhs

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

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13061306
else
13071307
tpd.SyntheticValDef(TempResultName.fresh(), e)
13081308

1309+
/** Returns `(n, Some(d))` where `n` is the name of a synthetic val `d` that binds the result of
1310+
* `e` if evaluating `e` is impure. Otherwise, returns `(e, None)`.
1311+
*/
1312+
def hoisted(e: tpd.Tree)(using Context): (tpd.Tree, Option[tpd.ValDef]) =
1313+
if exprPurity(e) >= TreeInfo.Pure then
1314+
(e, None)
1315+
else
1316+
val d = tpd.SyntheticValDef(TempResultName.fresh(), e)
1317+
(tpd.Ident(d.namedType), Some(d))
1318+
13091319
/** Returns a builder for computing trees representing assignments to `lhs`. */
13101320
def formPartialAssignmentTo(lhs: untpd.Tree)(using Context): PartialAssignment[LValue] =
13111321
lhs match
@@ -1363,7 +1373,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13631373

13641374
core match
13651375
case Apply(f, _) if f.symbol.is(ExtensionMethod) =>
1366-
formPartialAssignmentToExtensionApply(f, reassignmentToVal).getOrElse(reassignmentToVal())
1376+
formPartialAssignmentToExtensionApply(core).getOrElse(reassignmentToVal())
13671377

13681378
case _ => core.tpe match
13691379
case r: TermRef =>
@@ -1399,63 +1409,60 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
13991409
case _ =>
14001410
reassignmentToVal()
14011411

1402-
/** Returns a builder for computing trees representing assignments to `lhs`, which denotes the
1403-
* use of a setter defined in an extension.
1404-
*/
1412+
/** Returns a builder for making trees representing assignments to `lhs`, which denotes a setter
1413+
* defined in an extension.
1414+
*/
14051415
def formPartialAssignmentToExtensionApply(
1406-
lhs: Tree, reassignmentToVal: () => PartialAssignment[LValue]
1416+
lhs: Tree
14071417
)(using Context): Option[PartialAssignment[LValue]] =
1408-
def formAssignment(v: LValue): PartialAssignment[LValue] =
1409-
PartialAssignment(v) { (l, r) =>
1410-
// QUESTION: Why do we need `IgnoredProto(pt)`=
1411-
// typed(l.formAssignment(r), IgnoredProto(e))
1412-
l.formAssignment(r)
1413-
}
1414-
1415-
lhs match
1416-
case f @ Ident(name: TermName) =>
1417-
// We need to make sure that the prefix of this extension getter is retained when we
1418-
// transform it into a setter. Otherwise, we could end up resolving an unrelated setter
1419-
// from another extension. See tests/pos/i18713.scala for an example.
1420-
val v: SelectLValue = f.tpe match
1421-
case TermRef(q: TermRef, _) =>
1422-
SelectLValue(temporary(ref(q)), f.symbol.name)
1423-
case TermRef(q: ThisType, _) =>
1424-
SelectLValue(temporary(This(q.cls)), f.symbol.name)
1425-
case TermRef(NoPrefix, _) =>
1426-
SelectLValue(f, name.setterName)
1427-
Some(formAssignment(v))
1428-
1429-
case f @ Select(q, name: TermName) =>
1430-
Some(formAssignment(SelectLValue(temporary(q), name.setterName)))
1431-
1432-
case f @ TypeApply(f1, tas) =>
1433-
formPartialAssignmentToExtensionApply(f1, reassignmentToVal).map { (partialAssignment) =>
1434-
val s = partialAssignment.lhs.toRValue
1435-
val t = untpd.cpy.TypeApply(f)(s, tas.map((ta) => untpd.TypedSplice(ta)))
1436-
formAssignment(ApplyLValue(temporary(typed(t)), List()))
1437-
}
1438-
1439-
case f @ Apply(f1, as) =>
1440-
formPartialAssignmentToExtensionApply(f1, reassignmentToVal).map { (partialAssignment) =>
1441-
val applyKind = f1 match
1418+
/** Returns the setter corresponding to `lhs`, which is a getter, along hoisted definitions. */
1419+
def formSetter(lhs: Tree, captures: List[Tree]): (untpd.Tree, List[Tree]) =
1420+
lhs match
1421+
case f @ Ident(name: TermName) =>
1422+
// We need to make sure that the prefix of this extension getter is retained when we
1423+
// transform it into a setter. Otherwise, we could end up resolving an unrelated setter
1424+
// from another extension. See tests/pos/i18713.scala for an example.
1425+
f.tpe match
1426+
case TermRef(q: TermRef, _) =>
1427+
formSetter(ref(q).select(f.symbol).withSpan(f.span), captures)
1428+
case TermRef(q: ThisType, _) =>
1429+
formSetter(This(q.cls).select(f.symbol).withSpan(f.span), captures)
1430+
case TermRef(NoPrefix, _) =>
1431+
(untpd.cpy.Ident(f)(name.setterName), captures)
1432+
1433+
case f @ Select(q, name: TermName) =>
1434+
val (v, d) = hoisted(q)
1435+
(untpd.cpy.Select(f)(untpd.TypedSplice(v), name.setterName), captures ++ d)
1436+
1437+
case f @ TypeApply(g, ts) =>
1438+
val (s, cs) = formSetter(g, captures)
1439+
(untpd.cpy.TypeApply(f)(s, ts.map((t) => untpd.TypedSplice(t))), cs)
1440+
1441+
case f @ Apply(g, as) =>
1442+
var (s, newCaptures) = formSetter(g, captures)
1443+
var arguments = List[untpd.Tree]()
1444+
for a <- as do
1445+
val (v, d) = hoisted(a)
1446+
arguments = untpd.TypedSplice(v, isExtensionReceiver = true) +: arguments
1447+
newCaptures = newCaptures ++ d
1448+
1449+
val setter = untpd.cpy.Apply(f)(s, arguments)
1450+
1451+
g match
14421452
case _: Apply =>
14431453
// Current apply is to implicit arguments. Note that we cannot copy the apply kind
14441454
// of `f` since `f` is a typed tree and apply kinds are not preserved for those.
1445-
ApplyKind.Using
1455+
(setter.setApplyKind(ApplyKind.Using), newCaptures)
14461456
case _ =>
1447-
ApplyKind.Regular
1457+
(setter, newCaptures)
14481458

1449-
val arguments = as.map { (a) =>
1450-
val x = untpd.TypedSplice(a, isExtensionReceiver = true)
1451-
temporary(typed(x))
1452-
}
1453-
val s = partialAssignment.lhs.toRValue
1454-
formAssignment(ApplyLValue(temporary(typed(s)), arguments, applyKind))
1455-
}
1459+
case _ =>
1460+
(EmptyTree, List())
14561461

1457-
case _ =>
1458-
None
1462+
val (setter, captures) = formSetter(lhs, List())
1463+
if setter.isEmpty then None else
1464+
val cs = captures.collect { case d: tpd.ValDef => d }
1465+
Some(PartialAssignment(UnappliedSetter(setter, cs)) { (l, r) => l.formAssignment(r) })
14591466

14601467
def typedAssign(tree: untpd.Assign, pt: Type)(using Context): Tree =
14611468
tree.lhs match

0 commit comments

Comments
 (0)