diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fdefc14aadd6..561411857688 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1821,16 +1821,15 @@ object Trees { } } - def rename(tree: NameTree, newName: Name)(using Context): tree.ThisTree[T] = { - tree match { + def rename(tree: NameTree, newName: Name)(using Context): tree.ThisTree[T] = + tree.match case tree: Ident => cpy.Ident(tree)(newName) case tree: Select => cpy.Select(tree)(tree.qualifier, newName) case tree: Bind => cpy.Bind(tree)(newName, tree.body) case tree: ValDef => cpy.ValDef(tree)(name = newName.asTermName) case tree: DefDef => cpy.DefDef(tree)(name = newName.asTermName) case tree: TypeDef => cpy.TypeDef(tree)(name = newName.asTypeName) - } - }.asInstanceOf[tree.ThisTree[T]] + .asInstanceOf[tree.ThisTree[T]] object TypeDefs: def unapply(xs: List[Tree]): Option[List[TypeDef]] = xs match diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 741b997d9926..5b05982ccbaa 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 { @@ -169,7 +169,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 dcd7ed10987b..79d64c0d1cef 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1526,18 +1526,24 @@ 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 +class ReassignmentToVal(sym: Symbol, usage: Name)(using Context) extends TypeMsg(ReassignmentToValID): + private def name = if sym.exists then sym.name else usage + private def addendum = if !sym.exists || !sym.owner.isClass then "" else + i"""| + |Also, assignment syntax can be used if there is a corresponding setter: + | ${hl("def")} ${name}${hl("_=(x: Int): Unit = _v = x")} + |""" + def msg(using Context) = + if sym.exists then i"""Assignment to $sym""" + else i"""Bad assignment to $usage""" + def explain(using Context) = + 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..d660fd3c41ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -128,18 +128,17 @@ trait Dynamic { /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) */ - def typedDynamicAssign(tree: untpd.Assign, pt: Type)(using Context): Tree = { + 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) - tree.lhs match { + 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 _ => - 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)) private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, selSpan: Span, targs: List[untpd.Tree])(using Context): untpd.Apply = { val select = untpd.Select(qual, dynName).withSpan(selSpan) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6b7b840e7606..cbbf9a8c3de0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1373,9 +1373,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 @@ -1389,7 +1391,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, name), tree.srcPos) cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)).withType(defn.UnitType) def canAssign(sym: Symbol) = @@ -1478,8 +1481,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..e42b50ccc8d9 --- /dev/null +++ b/tests/neg/assignments.check @@ -0,0 +1,12 @@ +-- [E052] Type Error: tests/neg/assignments.scala:16:8 ----------------------------------------------------------------- +16 | x_= = 2 // error should give missing arguments + | ^^^^^^^ + | Bad assignment to x_= + | + | longer explanation available when compiling with `-explain` +-- [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 + | + | longer explanation available when compiling with `-explain` 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..72ea32eddcf2 --- /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 + | Also, assignment syntax can be used if there is a corresponding setter: + | def w_=(x: Int): Unit = _v = x + -------------------------------------------------------------------------------------------------------------------- +-- [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 + | Also, assignment syntax can be used if there is a corresponding setter: + | def x_=(x: Int): Unit = _v = x + -------------------------------------------------------------------------------------------------------------------- +-- [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 + | Also, assignment syntax can be used if there is a corresponding setter: + | def x_=(x: Int): Unit = _v = x + -------------------------------------------------------------------------------------------------------------------- +-- [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 + | Also, assignment syntax can be used if there is a corresponding setter: + | def z_=(x: Int): Unit = _v = x + -------------------------------------------------------------------------------------------------------------------- +-- [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 + | Also, assignment syntax can be used if there is a corresponding setter: + | def t_=(x: Int): Unit = _v = x + -------------------------------------------------------------------------------------------------------------------- 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