From f55f055b4ea7a2801cb7e58983c694702e34a9b0 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 24 Mar 2025 18:29:47 -0700 Subject: [PATCH 1/2] Use other tree for actual symbol of Assign Co-authored-by: Jan-Pieter van den Heuvel Co-authored-by: Seth Tisue Co-authored-by: Lucas Nouguier Co-authored-by: Prince --- .../tools/dotc/printing/Formatting.scala | 5 +- .../dotty/tools/dotc/reporting/messages.scala | 40 +++++--- .../src/dotty/tools/dotc/typer/Dynamic.scala | 5 +- .../src/dotty/tools/dotc/typer/Typer.scala | 14 ++- tests/neg/assignments.check | 22 +++++ tests/neg/assignments.scala | 4 +- tests/neg/i11561.check | 2 +- tests/neg/i16655.check | 2 +- tests/neg/i20338c.check | 2 +- tests/neg/i22671.check | 61 ++++++++++++ tests/neg/i22671.explain.check | 94 +++++++++++++++++++ tests/neg/i22671.explain.scala | 32 +++++++ tests/neg/i22671.scala | 49 ++++++++++ 13 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 tests/neg/assignments.check create mode 100644 tests/neg/i22671.check create mode 100644 tests/neg/i22671.explain.check create mode 100644 tests/neg/i22671.explain.scala create mode 100644 tests/neg/i22671.scala diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index daf4e70ad25a..cb37a5ea8a00 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -8,7 +8,7 @@ import core.* import Texts.*, Types.*, Flags.*, Symbols.*, Contexts.* import Decorators.* import reporting.Message -import util.{DiffUtil, SimpleIdentitySet} +import util.{Chars, DiffUtil, SimpleIdentitySet} import Highlighting.* object Formatting { @@ -184,7 +184,8 @@ object Formatting { } def assemble(args: Seq[Shown])(using Context): String = { - def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak + // compatible with CharArrayReader (not StringOps) + inline def isLineBreak(c: Char) = c == Chars.LF || c == Chars.FF def stripTrailingPart(s: String) = { val (pre, post) = s.span(c => !isLineBreak(c)) pre ++ post.stripMargin diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 2bad86f8967b..e5ec16d3df3e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1551,18 +1551,34 @@ class AmbiguousExtensionMethod(tree: untpd.Tree, expansion1: tpd.Tree, expansion |are possible expansions of $tree""" def explain(using Context) = "" -class ReassignmentToVal(name: Name)(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("=")} ... - |""" -} +class ReassignmentToVal(sym: Symbol, usage: Name, rhs: untpd.Tree)(using Context) extends TypeMsg(ReassignmentToValID): + val isSetter = usage.isSetterName && sym.info.firstParamTypes.nonEmpty + def msg(using Context) = + if isSetter then i"Bad assignment to setter should use $usage($rhs)" + else if sym.exists then i"Assignment to $sym" + else i"Bad assignment to $usage" + def explain(using Context) = + val name = + if isSetter then usage.asSimpleName.dropRight(2) + else if sym.exists then sym.name + else usage + if isSetter then + i"""|$usage is a setter name and can be used with assignment syntax: + | $name = $rhs + |""" + else + val addendum = if !sym.exists || !sym.owner.isClass || sym.isSetter then "" else + i"""| + |Assignment syntax can be used if there is a corresponding setter of the form: + | ${hl("def")} ${name}${hl(i"_=(x: ${sym.info.resultType}): Unit = ???")} + |""" + i"""|Members defined using `val` or `def` can't be assigned to. + |If you need to change the value of $name, use `var` instead: + | ${hl("var")} $name ${hl("=")} ??? + |However, it's more common to initialize a variable just once + |with a complex expression or even a block with many statements: + | ${hl("val")} $name ${hl("= if (condition) 1 else -1")}$addendum + |""" class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context) extends TypeMsg(TypeDoesNotTakeParametersID) { diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 14cc7bf963a6..193eb411a885 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -136,8 +136,9 @@ trait Dynamic { typedDynamicAssign(qual, name, sel.span, Nil) case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, targs) - case _ => - errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name)) + case lhs => + val name = lhs match { case nt: NameTree => nt.name case _ => nme.NO_NAME } + errorTree(tree, ReassignmentToVal(lhs.symbol, name, untpd.EmptyTree)) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c6cb85ea43b6..0d84fa19e110 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1412,9 +1412,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedAssign(tree: untpd.Assign, pt: Type)(using Context): Tree = tree.lhs match { - case lhs @ Apply(fn, args) => - typed(untpd.Apply(untpd.Select(fn, nme.update), args :+ tree.rhs), pt) - case untpd.TypedSplice(Apply(MaybePoly(Select(fn, app), targs), args)) if app == nme.apply => + case Apply(fn, args) => + val appliedUpdate = + untpd.Apply(untpd.Select(fn, nme.update), args :+ tree.rhs) + typed(appliedUpdate, pt) + case untpd.TypedSplice(Apply(MaybePoly(Select(fn, nme.apply), targs), args)) => val rawUpdate: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update) val wrappedUpdate = if (targs.isEmpty) rawUpdate @@ -1428,7 +1430,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def lhs1 = adapt(lhsCore, LhsProto, locked) def reassignmentToVal = - report.error(ReassignmentToVal(lhsCore.symbol.name), tree.srcPos) + val name = lhs match { case nt: NameTree => nt.name case _ => nme.NO_NAME } + report.error(ReassignmentToVal(lhs1.symbol `orElse` lhsCore.symbol, name, tree.rhs), tree.srcPos) cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) def canAssign(sym: Symbol) = @@ -1517,8 +1520,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedDynamicAssign(tree, pt) case tpe => reassignmentToVal - } + } } + end typedAssign def typedBlockStats(stats: List[untpd.Tree])(using Context): (List[tpd.Tree], Context) = index(stats) diff --git a/tests/neg/assignments.check b/tests/neg/assignments.check new file mode 100644 index 000000000000..b10a3205efff --- /dev/null +++ b/tests/neg/assignments.check @@ -0,0 +1,22 @@ +-- [E052] Type Error: tests/neg/assignments.scala:17:8 ----------------------------------------------------------------- +17 | x_= = 2 // error should give missing arguments, was: Reassignment to val x_= + | ^^^^^^^ + | Bad assignment to setter should use x_=(2) + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | x_= is a setter name and can be used with assignment syntax: + | x = 2 + -------------------------------------------------------------------------------------------------------------------- +-- [E083] Type Error: tests/neg/assignments.scala:20:9 ----------------------------------------------------------------- +20 | import c._ // error should give: prefix is not stable + | ^ + | (assignments.c : assignments.C) is not a valid import prefix, since it is not an immutable path + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | An immutable path is + | - a reference to an immutable value, or + | - a reference to `this`, or + | - a selection of an immutable path with an immutable value. + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/assignments.scala b/tests/neg/assignments.scala index 273419cb50ba..9e03cf482c43 100644 --- a/tests/neg/assignments.scala +++ b/tests/neg/assignments.scala @@ -1,3 +1,4 @@ +//> using options -explain object assignments { var a = Array(1, 2, 3) @@ -13,9 +14,8 @@ object assignments { x = x + 1 x *= 2 - x_= = 2 // error should give missing arguments + x_= = 2 // error should give missing arguments, was: Reassignment to val x_= } - var c = new C import c._ // error should give: prefix is not stable x = x + 1 diff --git a/tests/neg/i11561.check b/tests/neg/i11561.check index 28d7e355c499..c9d11dbc68fc 100644 --- a/tests/neg/i11561.check +++ b/tests/neg/i11561.check @@ -11,6 +11,6 @@ -- [E052] Type Error: tests/neg/i11561.scala:3:30 ---------------------------------------------------------------------- 3 | val updateText2 = copy(text = (_: String)) // error | ^^^^^^^^^^^^^^^^^^ - | Reassignment to val text + | Assignment to value text | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i16655.check b/tests/neg/i16655.check index e1335b624244..e9fbeb56adb8 100644 --- a/tests/neg/i16655.check +++ b/tests/neg/i16655.check @@ -1,6 +1,6 @@ -- [E052] Type Error: tests/neg/i16655.scala:3:4 ----------------------------------------------------------------------- 3 | x = 5 // error | ^^^^^ - | Reassignment to val x + | Assignment to value x | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20338c.check b/tests/neg/i20338c.check index 1d19ec0b3042..fa8e05de4641 100644 --- a/tests/neg/i20338c.check +++ b/tests/neg/i20338c.check @@ -1,6 +1,6 @@ -- [E052] Type Error: tests/neg/i20338c.scala:9:6 ---------------------------------------------------------------------- 9 | f.x = 42 // error | ^^^^^^^^ - | Reassignment to val x + | Assignment to value x | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i22671.check b/tests/neg/i22671.check new file mode 100644 index 000000000000..0ea9768a0bb8 --- /dev/null +++ b/tests/neg/i22671.check @@ -0,0 +1,61 @@ +-- [E007] Type Mismatch Error: tests/neg/i22671.scala:41:22 ------------------------------------------------------------ +41 | names_times(fields(0)) += fields(1).toLong // error + | ^^^^^^^^^ + | Found: Char + | Required: String + | + | longer explanation available when compiling with `-explain` +-- [E008] Not Found Error: tests/neg/i22671.scala:45:6 ----------------------------------------------------------------- +45 | x() += "42" // error + | ^^^^^^ + | value += is not a member of Int - did you mean Int.!=? or perhaps Int.<=? +-- [E052] Type Error: tests/neg/i22671.scala:49:6 ---------------------------------------------------------------------- +49 | c = 42 // error + | ^^^^^^ + | Assignment to value c + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:9:6 ----------------------------------------------------------------------- +9 | X.w = 27 // error + | ^^^^^^^^ + | Assignment to value w + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:12:6 ---------------------------------------------------------------------- +12 | X.x = 27 // error + | ^^^^^^^^ + | Assignment to method x + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:16:4 ---------------------------------------------------------------------- +16 | x = 27 // error + | ^^^^^^ + | Assignment to method x + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:20:4 ---------------------------------------------------------------------- +20 | y = 27 // error + | ^^^^^^ + | Assignment to method x + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:24:4 ---------------------------------------------------------------------- +24 | y = 27 // error + | ^^^^^^ + | Assignment to value z + | + | longer explanation available when compiling with `-explain` +-- [E052] Type Error: tests/neg/i22671.scala:28:4 ---------------------------------------------------------------------- +28 | x = 27 // error + | ^^^^^^ + | Assignment to value x + | + | longer explanation available when compiling with `-explain` +-- [E008] Not Found Error: tests/neg/i22671.scala:31:6 ----------------------------------------------------------------- +31 | X.x += 27 // error + | ^^^^^^ + | value += is not a member of Int - did you mean Int.!=? or perhaps Int.<=? +-- [E008] Not Found Error: tests/neg/i22671.scala:32:4 ----------------------------------------------------------------- +32 | 1 += 1 // error + | ^^^^ + | value += is not a member of Int - did you mean (1 : Int).!=? or perhaps (1 : Int).<=? diff --git a/tests/neg/i22671.explain.check b/tests/neg/i22671.explain.check new file mode 100644 index 000000000000..6375e265be1d --- /dev/null +++ b/tests/neg/i22671.explain.check @@ -0,0 +1,94 @@ +-- [E052] Type Error: tests/neg/i22671.explain.scala:14:6 -------------------------------------------------------------- +14 | X.w = 27 // error + | ^^^^^^^^ + | Assignment to value w + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of w, use `var` instead: + | var w = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val w = if (condition) 1 else -1 + | Assignment syntax can be used if there is a corresponding setter of the form: + | def w_=(x: Int): Unit = ??? + -------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/i22671.explain.scala:17:6 -------------------------------------------------------------- +17 | X.x = 27 // error + | ^^^^^^^^ + | Assignment to method x + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of x, use `var` instead: + | var x = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val x = if (condition) 1 else -1 + | Assignment syntax can be used if there is a corresponding setter of the form: + | def x_=(x: Int): Unit = ??? + -------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/i22671.explain.scala:21:4 -------------------------------------------------------------- +21 | y = 27 // error overload renamed + | ^^^^^^ + | Assignment to method x + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of x, use `var` instead: + | var x = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val x = if (condition) 1 else -1 + | Assignment syntax can be used if there is a corresponding setter of the form: + | def x_=(x: Int): Unit = ??? + -------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/i22671.explain.scala:25:4 -------------------------------------------------------------- +25 | y = 27 // error val renamed + | ^^^^^^ + | Assignment to value z + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of z, use `var` instead: + | var z = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val z = if (condition) 1 else -1 + | Assignment syntax can be used if there is a corresponding setter of the form: + | def z_=(x: Int): Unit = ??? + -------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/i22671.explain.scala:29:4 -------------------------------------------------------------- +29 | x = 27 // error local + | ^^^^^^ + | Assignment to value x + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of x, use `var` instead: + | var x = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val x = if (condition) 1 else -1 + -------------------------------------------------------------------------------------------------------------------- +-- [E052] Type Error: tests/neg/i22671.explain.scala:32:6 -------------------------------------------------------------- +32 | t.t = t // error + | ^^^^^^^ + | Assignment to method t + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Members defined using `val` or `def` can't be assigned to. + | If you need to change the value of t, use `var` instead: + | var t = ??? + | However, it's more common to initialize a variable just once + | with a complex expression or even a block with many statements: + | val t = if (condition) 1 else -1 + | Assignment syntax can be used if there is a corresponding setter of the form: + | def t_=(x: Int): Unit = ??? + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i22671.explain.scala b/tests/neg/i22671.explain.scala new file mode 100644 index 000000000000..1b78caf76326 --- /dev/null +++ b/tests/neg/i22671.explain.scala @@ -0,0 +1,32 @@ +//> using options -explain + +object X: + val w: Int = 42 + def w(y: Int): Int = x + y + def x: Int = 42 + def x(y: Int): Int = x + y + val z = 26 + +trait T: + def t = 42 + +def w = + X.w = 27 // error + +def f = + X.x = 27 // error + +def h = + import X.x as y + y = 27 // error overload renamed + +def i = + import X.z as y + y = 27 // error val renamed + +def j = + val x = 42 + x = 27 // error local + +def t(t: T) = + t.t = t // error diff --git a/tests/neg/i22671.scala b/tests/neg/i22671.scala new file mode 100644 index 000000000000..afb04f4cea73 --- /dev/null +++ b/tests/neg/i22671.scala @@ -0,0 +1,49 @@ +object X: + val w: Int = 42 + def w(y: Int): Int = x + y + def x: Int = 42 + def x(y: Int): Int = x + y + val z = 26 + +def w = + X.w = 27 // error + +def f = + X.x = 27 // error + +def g = + import X.x + x = 27 // error + +def h = + import X.x as y + y = 27 // error + +def i = + import X.z as y + y = 27 // error + +def j = + val x = 42 + x = 27 // error + +def k = + X.x += 27 // error + 1 += 1 // error + + +object t8763: + import collection.mutable + def bar(): Unit = + val names_times = mutable.Map.empty[String, mutable.Set[Long]] + val line = "" + val Array(fields) = line.split("\t") + names_times(fields(0)) += fields(1).toLong // error + +object t9834: + object x { def apply() = 42 ; def update(i: Int) = () } + x() += "42" // error + +class C(c: Int): + def test(): Unit = + c = 42 // error From a9a08b478c0f7b7c44b8de5d361f0525c0204cfb Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 22 Sep 2025 15:03:25 -0700 Subject: [PATCH 2/2] Note if name used differs from symbol in assign It must have been imported; say so for clarity. Use current syntax in explanatory examples; don't use very fake code but prefer /* code */. Some LOC are made shorter for reading. --- .../dotty/tools/dotc/reporting/messages.scala | 8 ++++++-- .../src/dotty/tools/dotc/typer/Dynamic.scala | 7 +++++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 ++++++++-- tests/neg/i22671.check | 4 ++-- tests/neg/i22671.explain.check | 16 ++++++++-------- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index e5ec16d3df3e..f9b4b3ac33fb 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1555,7 +1555,11 @@ class ReassignmentToVal(sym: Symbol, usage: Name, rhs: untpd.Tree)(using Context val isSetter = usage.isSetterName && sym.info.firstParamTypes.nonEmpty def msg(using Context) = if isSetter then i"Bad assignment to setter should use $usage($rhs)" - else if sym.exists then i"Assignment to $sym" + else if sym.exists then + if sym.name != usage && usage != nme.NO_NAME then + i"Assignment to $sym imported as $usage" + else + i"Assignment to $sym" else i"Bad assignment to $usage" def explain(using Context) = val name = @@ -1577,7 +1581,7 @@ class ReassignmentToVal(sym: Symbol, usage: Name, rhs: untpd.Tree)(using Context | ${hl("var")} $name ${hl("=")} ??? |However, it's more common to initialize a variable just once |with a complex expression or even a block with many statements: - | ${hl("val")} $name ${hl("= if (condition) 1 else -1")}$addendum + | ${hl("val")} $name ${hl("= if condition then /* code */ else /* other code */")}$addendum |""" 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 193eb411a885..6d314490ef85 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -131,14 +131,17 @@ trait Dynamic { def typedDynamicAssign(tree: untpd.Assign, pt: Type)(using Context): Tree = { def typedDynamicAssign(qual: untpd.Tree, name: Name, selSpan: Span, targs: List[untpd.Tree]): Tree = typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, selSpan, targs), tree.rhs), pt) + def reassignmentToVal(sym: Symbol, name: Name) = + errorTree(tree, ReassignmentToVal(sym, name, untpd.EmptyTree)) tree.lhs match { case sel @ Select(qual, name) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, Nil) case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, sel.span, targs) + case lhs: NameTree => + reassignmentToVal(lhs.symbol, lhs.name) case lhs => - val name = lhs match { case nt: NameTree => nt.name case _ => nme.NO_NAME } - errorTree(tree, ReassignmentToVal(lhs.symbol, name, untpd.EmptyTree)) + reassignmentToVal(lhs.symbol, nme.NO_NAME) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0d84fa19e110..1806c133dd7d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1430,8 +1430,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def lhs1 = adapt(lhsCore, LhsProto, locked) def reassignmentToVal = - val name = lhs match { case nt: NameTree => nt.name case _ => nme.NO_NAME } - report.error(ReassignmentToVal(lhs1.symbol `orElse` lhsCore.symbol, name, tree.rhs), tree.srcPos) + def reassignmentError() = + val name = + lhs match + case lhs: NameTree => lhs.name + case _ => nme.NO_NAME + val msg = ReassignmentToVal(lhs1.symbol `orElse` lhsCore.symbol, name, tree.rhs) + report.error(msg, tree.srcPos) + reassignmentError() cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) def canAssign(sym: Symbol) = diff --git a/tests/neg/i22671.check b/tests/neg/i22671.check index 0ea9768a0bb8..94294a7bbeb5 100644 --- a/tests/neg/i22671.check +++ b/tests/neg/i22671.check @@ -36,13 +36,13 @@ -- [E052] Type Error: tests/neg/i22671.scala:20:4 ---------------------------------------------------------------------- 20 | y = 27 // error | ^^^^^^ - | Assignment to method x + | Assignment to method x imported as y | | longer explanation available when compiling with `-explain` -- [E052] Type Error: tests/neg/i22671.scala:24:4 ---------------------------------------------------------------------- 24 | y = 27 // error | ^^^^^^ - | Assignment to value z + | Assignment to value z imported as y | | longer explanation available when compiling with `-explain` -- [E052] Type Error: tests/neg/i22671.scala:28:4 ---------------------------------------------------------------------- diff --git a/tests/neg/i22671.explain.check b/tests/neg/i22671.explain.check index 6375e265be1d..af78e8a53574 100644 --- a/tests/neg/i22671.explain.check +++ b/tests/neg/i22671.explain.check @@ -10,7 +10,7 @@ | var w = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val w = if (condition) 1 else -1 + | val w = if condition then /* code */ else /* other code */ | Assignment syntax can be used if there is a corresponding setter of the form: | def w_=(x: Int): Unit = ??? -------------------------------------------------------------------------------------------------------------------- @@ -26,14 +26,14 @@ | var x = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val x = if (condition) 1 else -1 + | val x = if condition then /* code */ else /* other code */ | Assignment syntax can be used if there is a corresponding setter of the form: | def x_=(x: Int): Unit = ??? -------------------------------------------------------------------------------------------------------------------- -- [E052] Type Error: tests/neg/i22671.explain.scala:21:4 -------------------------------------------------------------- 21 | y = 27 // error overload renamed | ^^^^^^ - | Assignment to method x + | Assignment to method x imported as y |-------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -42,14 +42,14 @@ | var x = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val x = if (condition) 1 else -1 + | val x = if condition then /* code */ else /* other code */ | Assignment syntax can be used if there is a corresponding setter of the form: | def x_=(x: Int): Unit = ??? -------------------------------------------------------------------------------------------------------------------- -- [E052] Type Error: tests/neg/i22671.explain.scala:25:4 -------------------------------------------------------------- 25 | y = 27 // error val renamed | ^^^^^^ - | Assignment to value z + | Assignment to value z imported as y |-------------------------------------------------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -58,7 +58,7 @@ | var z = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val z = if (condition) 1 else -1 + | val z = if condition then /* code */ else /* other code */ | Assignment syntax can be used if there is a corresponding setter of the form: | def z_=(x: Int): Unit = ??? -------------------------------------------------------------------------------------------------------------------- @@ -74,7 +74,7 @@ | var x = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val x = if (condition) 1 else -1 + | val x = if condition then /* code */ else /* other code */ -------------------------------------------------------------------------------------------------------------------- -- [E052] Type Error: tests/neg/i22671.explain.scala:32:6 -------------------------------------------------------------- 32 | t.t = t // error @@ -88,7 +88,7 @@ | var t = ??? | However, it's more common to initialize a variable just once | with a complex expression or even a block with many statements: - | val t = if (condition) 1 else -1 + | val t = if condition then /* code */ else /* other code */ | Assignment syntax can be used if there is a corresponding setter of the form: | def t_=(x: Int): Unit = ??? --------------------------------------------------------------------------------------------------------------------