Skip to content

Commit 030107c

Browse files
committed
Refactor 'typedAssign' to ensure the right evaluation order
1 parent 6b3ea8e commit 030107c

File tree

8 files changed

+398
-183
lines changed

8 files changed

+398
-183
lines changed

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
208208
case UnstableInlineAccessorID // errorNumber: 192
209209
case VolatileOnValID // errorNumber: 193
210210
case InvalidMultipleAssignmentSourceID // errorNumber: 194
211-
case MultipleAssignmentShapeMismatchID // errorNumber: 195
211+
case InvalidMultipleAssignmentTargetID // errorNumber: 195
212+
case MultipleAssignmentShapeMismatchID // errorNumber: 196
212213

213214
def errorNumber = ordinal - 1
214215

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,6 +3164,14 @@ class InvalidMultipleAssignmentSource(found: Type)(using Context)
31643164
def explain(using Context) = ""
31653165
}
31663166

3167+
class InvalidMultipleAssignmentTarget()(using Context)
3168+
extends TypeMsg(InvalidMultipleAssignmentTargetID) {
3169+
def msg(using Context) =
3170+
i"""invalid target of multiple assignment.
3171+
|Multiple assignments admit only one level of nesting."""
3172+
def explain(using Context) = ""
3173+
}
3174+
31673175
class MultipleAssignmentShapeMismatch(found: Int, required: Int)(using Context)
31683176
extends TypeMsg(MultipleAssignmentShapeMismatchID) {
31693177
def msg(using Context) =

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,33 @@ trait Dynamic {
141141
}
142142
}
143143

144+
/** Returns the partial application of a dynamic assignment translating selection that does not
145+
* typecheck according to the normal rules into a `updateDynamic`.
146+
*
147+
* For example: `foo.bar = baz ~~> foo.updateDynamic(bar)(baz)`
148+
*
149+
* @param lhs The target of the assignment.
150+
*/
151+
def formPartialDynamicAssignment(
152+
lhs: untpd.Tree
153+
)(using Context): PartialAssignment[SimpleLValue] =
154+
lhs match
155+
case s @ Select(q, n) if !isDynamicMethod(n) =>
156+
formPartialDynamicAssignment(q, n, s.span, Nil)
157+
case TypeApply(s @ Select(q, n), targs) if !isDynamicMethod(n) =>
158+
formPartialDynamicAssignment(q, n, s.span, Nil)
159+
case _ =>
160+
val e = errorTree(lhs, ReassignmentToVal(lhs.symbol.name))
161+
PartialAssignment(SimpleLValue(e)) { (l, _) => l.expression }
162+
163+
def formPartialDynamicAssignment(
164+
q: untpd.Tree, n: Name, s: Span, targs: List[untpd.Tree]
165+
)(using Context): PartialAssignment[SimpleLValue] =
166+
val v = typed(coreDynamic(q, nme.updateDynamic, n, s, targs))
167+
PartialAssignment(SimpleLValue(v)) { (l, r) =>
168+
untpd.Apply(untpd.TypedSplice(l.expression), List(r))
169+
}
170+
144171
private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, selSpan: Span, targs: List[untpd.Tree])(using Context): untpd.Apply = {
145172
val select = untpd.Select(qual, dynName).withSpan(selSpan)
146173
val selectWithTypes =
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package dotty.tools
2+
package dotc
3+
package typer
4+
5+
import dotty.tools.dotc.ast.Trees.ApplyKind
6+
import dotty.tools.dotc.ast.tpd
7+
import dotty.tools.dotc.ast.untpd
8+
import dotty.tools.dotc.core.Contexts.Context
9+
import dotty.tools.dotc.core.Names.Name
10+
import core.Symbols.defn
11+
12+
/** A function computing the assignment of a lvalue.
13+
*
14+
* @param lhs The target of the assignment.
15+
* @param perform: A closure that accepts `lhs` and an untyped tree `rhs`, and returns a tree
16+
* representing the assignment of `rhs` ro `lhs`.
17+
*/
18+
private[typer] final class PartialAssignment[+T <: LValue](val lhs: T)(
19+
perform: (T, untpd.Tree) => untpd.Tree
20+
):
21+
22+
/** Returns a tree computing the assignment of `rhs` to `lhs`. */
23+
def apply(rhs: untpd.Tree): untpd.Tree =
24+
perform(lhs, rhs)
25+
26+
end PartialAssignment
27+
28+
/** The left-hand side of an assignment. */
29+
private[typer] sealed abstract class LValue:
30+
31+
/** Returns the local `val` definitions composing this lvalue. */
32+
def locals: List[tpd.ValDef]
33+
34+
/** Returns this lvalue converted to a rvalue. */
35+
def toRValue(using Context): untpd.Tree
36+
37+
/** Returns a tree computing the assignment of `rhs` to this lvalue. */
38+
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree
39+
40+
/** Returns the value of `t`, which may be an expression or a local `val` definition. */
41+
protected final def read(t: tpd.Tree)(using Context): tpd.Tree =
42+
t match
43+
case d: tpd.ValDef => tpd.Ident(d.namedType)
44+
case e => e
45+
46+
end LValue
47+
48+
/** A simple expression, typically valid on left-hand side of an `Assign` tree.
49+
*
50+
* Use this class to represent an assignment that translates to an `Assign` tree or to wrap an
51+
* error whose diagnostic can be delayed until the right-hand side is known.
52+
*
53+
* @param expression The expression of the lvalue.
54+
*/
55+
private[typer] final case class SimpleLValue(expression: tpd.Tree) extends LValue:
56+
57+
def locals: List[tpd.ValDef] =
58+
List()
59+
60+
def toRValue(using Context): untpd.Tree =
61+
untpd.TypedSplice(expression)
62+
63+
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
64+
val s = untpd.Assign(untpd.TypedSplice(expression), rhs)
65+
untpd.TypedSplice(s.withType(defn.UnitType))
66+
67+
end SimpleLValue
68+
69+
/** A lvalue represeted by the partial application a function.
70+
*
71+
* @param function The partially applied function.
72+
* @param arguments The arguments of the partial application.
73+
* @param kind The way in which the function is applied.
74+
*/
75+
private[typer] final case class ApplyLValue(
76+
function: tpd.Tree,
77+
arguments: List[tpd.Tree],
78+
kind: ApplyKind = ApplyKind.Regular
79+
) extends LValue:
80+
81+
val locals: List[tpd.ValDef] =
82+
(function +: arguments).collect { case d: tpd.ValDef => d }
83+
84+
def toRValue(using Context): untpd.Tree =
85+
untpd.Apply(function, arguments)
86+
87+
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
88+
val s = untpd.TypedSplice(read(function))
89+
val t = arguments.map((a) => untpd.TypedSplice(read(a))) :+ rhs
90+
untpd.Apply(s, t)
91+
92+
end ApplyLValue
93+
94+
/** A lvalue represeted by the application of a partially applied method.
95+
*
96+
* @param receiver The receiver of the partially applied method.
97+
* @param member The name of the partially applied method.
98+
* @param arguments The arguments of the partial application.
99+
*/
100+
private[typer] final case class SelectLValue(
101+
receiver: tpd.Tree,
102+
member: Name,
103+
arguments: List[tpd.Tree] = List()
104+
) extends LValue:
105+
106+
def expandReceiver()(using Context): tpd.Tree =
107+
receiver match
108+
case d: tpd.ValDef => d.rhs
109+
case r => r
110+
111+
val locals: List[tpd.ValDef] =
112+
(receiver +: arguments).collect { case d: tpd.ValDef => d }
113+
114+
def toRValue(using Context): untpd.Tree =
115+
require(arguments.isEmpty)
116+
untpd.Select(untpd.TypedSplice(expandReceiver()), member)
117+
118+
def formAssignment(rhs: untpd.Tree)(using Context): untpd.Tree =
119+
val s = untpd.Select(untpd.TypedSplice(read(receiver)), member)
120+
val t = arguments.map((a) => untpd.TypedSplice(read(a))) :+ rhs
121+
untpd.Apply(s, t)
122+
123+
end SelectLValue

0 commit comments

Comments
 (0)