Skip to content

Commit 2631813

Browse files
Backport "Warn when named tuples resemble assignments" to 3.6 (#21965)
Backports #21823 to the 3.6.2. PR submitted by the release tooling. [skip ci]
2 parents 2482c5d + 10c13da commit 2631813

File tree

13 files changed

+89
-3
lines changed

13 files changed

+89
-3
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,9 +1605,10 @@ object desugar {
16051605

16061606
/** Translate tuple expressions
16071607
*
1608-
* () ==> ()
1609-
* (t) ==> t
1610-
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
1608+
* () ==> ()
1609+
* (t) ==> t
1610+
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
1611+
* (n1 = t1, ..., nN = tN) ==> NamedTuple.build[(n1, ..., nN)]()(TupleN(t1, ..., tN))
16111612
*/
16121613
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
16131614
var elems = checkWellFormedTupleElems(tree.trees)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
216216
case FinalLocalDefID // errorNumber: 200
217217
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
218218
case QuotedTypeMissingID // errorNumber: 202
219+
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
219220

220221
def errorNumber = ordinal - 1
221222

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,3 +3343,12 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q
33433343
|"""
33443344

33453345
end QuotedTypeMissing
3346+
3347+
final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(AmbiguousNamedTupleAssignmentID):
3348+
override protected def msg(using Context): String =
3349+
i"""Ambiguous syntax: this is interpreted as a named tuple with one element,
3350+
|not as an assignment.
3351+
|
3352+
|To assign a value, use curly braces: `{${key} = ${value}}`."""
3353+
3354+
override protected def explain(using Context): String = ""

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3395,6 +3395,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
33953395
/** Translate tuples of all arities */
33963396
def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree =
33973397
val tree1 = desugar.tuple(tree, pt)
3398+
checkAmbiguousNamedTupleAssignment(tree)
33983399
if tree1 ne tree then typed(tree1, pt)
33993400
else
34003401
val arity = tree.trees.length
@@ -3420,6 +3421,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34203421
val resTpe = TypeOps.nestedPairs(elemTpes)
34213422
app1.cast(resTpe)
34223423

3424+
/** Checks if `tree` is a named tuple with one element that could be
3425+
* interpreted as an assignment, such as `(x = 1)`. If so, issues a warning.
3426+
*/
3427+
def checkAmbiguousNamedTupleAssignment(tree: untpd.Tuple)(using Context): Unit =
3428+
tree.trees match
3429+
case List(NamedArg(name, value)) =>
3430+
val tmpCtx = ctx.fresh.setNewTyperState()
3431+
typedAssign(untpd.Assign(untpd.Ident(name), value), WildcardType)(using tmpCtx)
3432+
if !tmpCtx.reporter.hasErrors then
3433+
// If there are no errors typing the above, then the named tuple is
3434+
// ambiguous and we issue a warning.
3435+
report.migrationWarning(AmbiguousNamedTupleAssignment(name, value), tree.srcPos)
3436+
case _ => ()
3437+
34233438
/** Retrieve symbol attached to given tree */
34243439
protected def retrieveSym(tree: untpd.Tree)(using Context): Symbol = tree.removeAttachment(SymOfTree) match {
34253440
case Some(sym) =>

tests/pos/21681d.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def test1() =
2+
class Person:
3+
def age: Int = ???
4+
def age_=(x: Int): Unit = ???
5+
6+
val person = Person()
7+
8+
(person.age = 29) // no warn (interpreted as `person.age_=(29)`)
9+
10+
def test2() =
11+
class Person:
12+
var age: Int = 28
13+
14+
val person = Person()
15+
16+
(person.age = 29) // no warn (interpreted as `person.age_=(29)`)

tests/warn/21681.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E203] Syntax Migration Warning: tests/warn/21681.scala:3:2 ---------------------------------------------------------
2+
3 | (age = 29) // warn
3+
| ^^^^^^^^^^
4+
| Ambiguous syntax: this is interpreted as a named tuple with one element,
5+
| not as an assignment.
6+
|
7+
| To assign a value, use curly braces: `{age = 29}`.

tests/warn/21681.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def main() =
2+
var age: Int = 28
3+
(age = 29) // warn

tests/warn/21681b.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E203] Syntax Migration Warning: tests/warn/21681b.scala:3:2 --------------------------------------------------------
2+
3 | (age = 29) // warn
3+
| ^^^^^^^^^^
4+
| Ambiguous syntax: this is interpreted as a named tuple with one element,
5+
| not as an assignment.
6+
|
7+
| To assign a value, use curly braces: `{age = 29}`.

tests/warn/21681b.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test:
2+
var age: Int = 28
3+
(age = 29) // warn

tests/warn/21681c.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E203] Syntax Migration Warning: tests/warn/21681c.scala:5:2 --------------------------------------------------------
2+
5 | (age = 29) // warn
3+
| ^^^^^^^^^^
4+
| Ambiguous syntax: this is interpreted as a named tuple with one element,
5+
| not as an assignment.
6+
|
7+
| To assign a value, use curly braces: `{age = 29}`.

tests/warn/21681c.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test:
2+
def age: Int = ???
3+
def age_=(x: Int): Unit = ()
4+
age = 29
5+
(age = 29) // warn

tests/warn/21770.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E203] Syntax Migration Warning: tests/warn/21770.scala:5:9 ---------------------------------------------------------
2+
5 | f(i => (cache = Some(i))) // warn
3+
| ^^^^^^^^^^^^^^^^^
4+
| Ambiguous syntax: this is interpreted as a named tuple with one element,
5+
| not as an assignment.
6+
|
7+
| To assign a value, use curly braces: `{cache = Some(i)}`.

tests/warn/21770.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def f(g: Int => Unit) = g(0)
2+
3+
def test =
4+
var cache: Option[Int] = None
5+
f(i => (cache = Some(i))) // warn

0 commit comments

Comments
 (0)