@@ -1298,39 +1298,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1298
1298
}
1299
1299
1300
1300
/** 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 ] =
1302
1304
lhs match
1303
1305
case lhs @ Apply (f, as) =>
1304
1306
// 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)
1307
1310
PartialAssignment (lvalue) { (l, r) => l.formAssignment(r) }
1308
1311
1309
1312
case untpd.TypedSplice (Apply (MaybePoly (Select (fn, app), tas), as)) if app == nme.apply =>
1310
1313
if tas.isEmpty then
1311
1314
// 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)
1314
1318
PartialAssignment (lvalue) { (l, r) => l.formAssignment(r) }
1315
1319
else
1316
1320
// Type arguments are present; the LHS requires a type application.
1317
1321
val s : untpd.Tree = untpd.Select (untpd.TypedSplice (fn), nme.update)
1318
1322
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)
1321
1326
PartialAssignment (lvalue) { (l, r) => l.formAssignment(r) }
1322
1327
1323
1328
case _ =>
1324
- formPartialAssignmentToNonApply(lhs)
1329
+ formPartialAssignmentToNonApply(lhs, isSingleAssignment )
1325
1330
1326
1331
/** Returns a builder for making trees representing assignments to `lhs`, which isn't a term or
1327
1332
* type application.
1328
1333
*/
1329
- def formPartialAssignmentToNonApply (lhs : untpd.Tree )(using Context ): PartialAssignment [LValue ] =
1334
+ def formPartialAssignmentToNonApply (
1335
+ lhs : untpd.Tree , isSingleAssignment : Boolean
1336
+ )(using Context ): PartialAssignment [LValue ] =
1330
1337
val locked = ctx.typerState.ownedVars
1331
1338
val core = typedUnadapted(lhs, LhsProto , locked)
1332
1339
def adapted = adapt(core, LhsProto , locked)
1333
1340
1341
+ /** Returns a builder reporting that the left-hand side is not reassignable. */
1334
1342
def reassignmentToVal (): PartialAssignment [SimpleLValue ] =
1335
1343
PartialAssignment (SimpleLValue (core)) { (l, r) =>
1336
1344
val target = l.expression
@@ -1357,10 +1365,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1357
1365
1358
1366
core match
1359
1367
case Apply (f, _) if f.symbol.is(ExtensionMethod ) =>
1360
- formPartialAssignmentToExtensionApply(core).getOrElse(reassignmentToVal())
1368
+ formPartialAssignmentToExtensionApply(core, isSingleAssignment)
1369
+ .getOrElse(reassignmentToVal())
1361
1370
1362
1371
case _ => core.tpe match
1363
- case r : TermRef =>
1372
+ case r : TermRef if ! mustFormSetter(adapted, isSingleAssignment) =>
1364
1373
val v = core.denot.suchThat(! _.is(Method ))
1365
1374
if canAssign(v.symbol) then
1366
1375
rememberNonLocalAssignToPrivate(v.symbol)
@@ -1387,6 +1396,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1387
1396
case _ =>
1388
1397
reassignmentToVal()
1389
1398
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
+
1390
1404
case TryDynamicCallType =>
1391
1405
formPartialDynamicAssignment(lhs)
1392
1406
@@ -1397,37 +1411,45 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1397
1411
* defined in an extension.
1398
1412
*/
1399
1413
def formPartialAssignmentToExtensionApply (
1400
- lhs : Tree
1414
+ lhs : Tree , isSingleAssignment : Boolean
1401
1415
)(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 ]) =
1404
1426
lhs match
1405
1427
case f @ Ident (name : TermName ) =>
1406
1428
// We need to make sure that the prefix of this extension getter is retained when we
1407
1429
// transform it into a setter. Otherwise, we could end up resolving an unrelated setter
1408
1430
// from another extension. See tests/pos/i18713.scala for an example.
1409
1431
f.tpe match
1410
1432
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)
1412
1434
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)
1414
1436
case TermRef (NoPrefix , _) =>
1415
1437
(untpd.cpy.Ident (f)(name.setterName), locals)
1416
1438
1417
1439
case f @ Select (q, name : TermName ) =>
1418
- val (v, d) = PossiblyHoistedValue (q).valueAndDefinition
1440
+ val (v, d) = PossiblyHoistedValue (q, isSingleAssignment ).valueAndDefinition
1419
1441
(untpd.cpy.Select (f)(untpd.TypedSplice (v), name.setterName), locals ++ d)
1420
1442
1421
1443
case f @ TypeApply (g, ts) =>
1422
- val (s, cs) = formSetter (g, locals)
1444
+ val (s, cs) = recurse (g, locals)
1423
1445
(untpd.cpy.TypeApply (f)(s, ts.map((t) => untpd.TypedSplice (t))), cs)
1424
1446
1425
1447
case f @ Apply (g, as) =>
1426
- var (s, newLocals) = formSetter (g, locals)
1448
+ var (s, newLocals) = recurse (g, locals)
1427
1449
var arguments = List [untpd.Tree ]()
1428
1450
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
1431
1453
newLocals = newLocals ++ d
1432
1454
1433
1455
val setter = untpd.cpy.Apply (f)(s, arguments)
@@ -1443,10 +1465,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1443
1465
case _ =>
1444
1466
(EmptyTree , List ())
1445
1467
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
+ )
1450
1482
1451
1483
def typedAssign (tree : untpd.Assign , pt : Type )(using Context ): Tree =
1452
1484
tree.lhs match
@@ -1455,7 +1487,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1455
1487
typedMultipleAssign(lhs, tree.rhs)
1456
1488
case _ =>
1457
1489
// Simple assignment.
1458
- val assignmentBuilder = formPartialAssignmentTo(tree.lhs)
1490
+ val assignmentBuilder = formPartialAssignmentTo(tree.lhs, isSingleAssignment = true )
1459
1491
val locals = assignmentBuilder.lhs.locals.map((d) => untpd.TypedSplice (d))
1460
1492
if locals.isEmpty then
1461
1493
typed(assignmentBuilder(tree.rhs))
@@ -1481,7 +1513,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
1481
1513
val s = PartialAssignment (SimpleLValue (e)) { (l, _) => l.expression }
1482
1514
assignmentBuilders.append(s)
1483
1515
case _ =>
1484
- val s = formPartialAssignmentTo(l)
1516
+ val s = formPartialAssignmentTo(l, false )
1485
1517
statements.appendAll(s.lhs.locals.map((d) => untpd.TypedSplice (d)))
1486
1518
assignmentBuilders.append(s)
1487
1519
0 commit comments