From 09086bf56e66740ace76a89b3e91ecc5aa55699a Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 15 Oct 2025 15:15:49 +0800 Subject: [PATCH] Better error message: reassignment to val --- .../dotty/tools/dotc/reporting/messages.scala | 28 ++++++---- .../src/dotty/tools/dotc/typer/Dynamic.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/reassignment.check | 53 +++++++++++++++++++ tests/neg/reassignment.scala | 10 ++++ 5 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 tests/neg/reassignment.check create mode 100644 tests/neg/reassignment.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index efbe9c405d26..923c09ed7d1c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1551,17 +1551,25 @@ class AmbiguousExtensionMethod(tree: untpd.Tree, expansion1: tpd.Tree, expansion |are possible expansions of $tree""" def explain(using Context) = "" -class ReassignmentToVal(name: Name)(using Context) +class ReassignmentToVal(name: Name, pt: Type)(using Context) extends TypeMsg(ReassignmentToValID) { - def msg(using Context) = i"""Reassignment to val $name""" - def explain(using Context) = - i"""|You can not assign a new value to $name as values can't be changed. - |Keep in mind that every statement has a value, so you may e.g. use - | ${hl("val")} $name ${hl("= if (condition) 2 else 5")} - |In case you need a reassignable name, you can declare it as - |variable - | ${hl("var")} $name ${hl("=")} ... - |""" + + def booleanExpected = pt.isRef(defn.BooleanClass) + def booleanNote = + if booleanExpected then + ". Maybe you meant to write an equality test using `==`?" + else "" + def msg(using Context) + = i"""Reassignment to val $name$booleanNote""" + + def explain(using Context) = + if booleanExpected then + i"""An equality test is written "x == y" using `==`. A single `=` stands + |for assigment, where a variable on the left gets a new value on the right. + |This is only permitted if the variable is declared with `var`.""" + else + i"""You can not assign a new value to $name as values can't be changed. + |Reassigment is only permitted if the variable is declared with `var`.""" } class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context) diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 14cc7bf963a6..8d48651ad6e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -137,7 +137,7 @@ trait Dynamic { case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, targs) case _ => - errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name)) + errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name, pt)) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e5841a3de768..fa952bae91da 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1429,7 +1429,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def lhs1 = adapt(lhsCore, LhsProto, locked) def reassignmentToVal = - report.error(ReassignmentToVal(lhsCore.symbol.name), tree.srcPos) + report.error(ReassignmentToVal(lhsCore.symbol.name, pt), tree.srcPos) cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) def canAssign(sym: Symbol) = diff --git a/tests/neg/reassignment.check b/tests/neg/reassignment.check new file mode 100644 index 000000000000..6b171d326b68 --- /dev/null +++ b/tests/neg/reassignment.check @@ -0,0 +1,53 @@ +-- [E052] Type Error: tests/neg/reassignment.scala:4:10 ---------------------------------------------------------------- +4 |val y = x = 0 // error + | ^^^^^ + | Reassignment to val x + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | You can not assign a new value to x as values can't be changed. + | Reassigment is only permitted if the variable is declared with `var`. + --------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/reassignment.scala:5:19 ---------------------------------------------------------------- +5 |val z: Boolean = x = 0 // error + | ^^^^^ + | Reassignment to val x. Maybe you meant to write an equality test using `==`? + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | An equality test is written "x == y" using `==`. A single `=` stands + | for assigment, where a variable on the left gets a new value on the right. + | This is only permitted if the variable is declared with `var`. + --------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/reassignment.scala:6:13 ---------------------------------------------------------------- +6 |def f = if x = 0 then 1 else 2 // error + | ^^^^^ + | Reassignment to val x. Maybe you meant to write an equality test using `==`? + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | An equality test is written "x == y" using `==`. A single `=` stands + | for assigment, where a variable on the left gets a new value on the right. + | This is only permitted if the variable is declared with `var`. + --------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/reassignment.scala:8:4 ----------------------------------------------------------------- +8 | x = 0 // error + | ^^^^^ + | Reassignment to val x + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | You can not assign a new value to x as values can't be changed. + | Reassigment is only permitted if the variable is declared with `var`. + --------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/reassignment.scala:9:4 ----------------------------------------------------------------- +9 | x = 2 // error + | ^^^^^ + | Reassignment to val x. Maybe you meant to write an equality test using `==`? + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | An equality test is written "x == y" using `==`. A single `=` stands + | for assigment, where a variable on the left gets a new value on the right. + | This is only permitted if the variable is declared with `var`. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/reassignment.scala b/tests/neg/reassignment.scala new file mode 100644 index 000000000000..c831c12dba5a --- /dev/null +++ b/tests/neg/reassignment.scala @@ -0,0 +1,10 @@ +//> using options -explain + +val x = 1 +val y = x = 0 // error +val z: Boolean = x = 0 // error +def f = if x = 0 then 1 else 2 // error +def g: Boolean = + x = 0 // error + x = 2 // error +