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 eb5e692b2915..7db9aeb03126 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1525,18 +1525,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 10a061ab8fc4..df03d7b37fa6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1400,9 +1400,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 @@ -1416,7 +1418,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) = @@ -1505,8 +1508,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