Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/Formatting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
44 changes: 32 additions & 12 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1551,18 +1551,38 @@ 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
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 =
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 then /* code */ else /* other code */")}$addendum
|"""

class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Context)
extends TypeMsg(TypeDoesNotTakeParametersID) {
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +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 _ =>
errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name))
case lhs: NameTree =>
reassignmentToVal(lhs.symbol, lhs.name)
case lhs =>
reassignmentToVal(lhs.symbol, nme.NO_NAME)
}
}

Expand Down
20 changes: 15 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -1428,7 +1430,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def lhs1 = adapt(lhsCore, LhsProto, locked)

def reassignmentToVal =
report.error(ReassignmentToVal(lhsCore.symbol.name), 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) =
Expand Down Expand Up @@ -1517,8 +1526,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)
Expand Down
22 changes: 22 additions & 0 deletions tests/neg/assignments.check
Original file line number Diff line number Diff line change
@@ -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.
--------------------------------------------------------------------------------------------------------------------
4 changes: 2 additions & 2 deletions tests/neg/assignments.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//> using options -explain
object assignments {

var a = Array(1, 2, 3)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i11561.check
Original file line number Diff line number Diff line change
Expand Up @@ -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`
2 changes: 1 addition & 1 deletion tests/neg/i16655.check
Original file line number Diff line number Diff line change
@@ -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`
2 changes: 1 addition & 1 deletion tests/neg/i20338c.check
Original file line number Diff line number Diff line change
@@ -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`
61 changes: 61 additions & 0 deletions tests/neg/i22671.check
Original file line number Diff line number Diff line change
@@ -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 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 imported as y
|
| 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).<=?
94 changes: 94 additions & 0 deletions tests/neg/i22671.explain.check
Original file line number Diff line number Diff line change
@@ -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 then /* code */ else /* other code */
| 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 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 imported as y
|--------------------------------------------------------------------------------------------------------------------
| 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 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 imported as y
|--------------------------------------------------------------------------------------------------------------------
| 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 then /* code */ else /* other code */
| 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 then /* code */ else /* other code */
--------------------------------------------------------------------------------------------------------------------
-- [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 then /* code */ else /* other code */
| Assignment syntax can be used if there is a corresponding setter of the form:
| def t_=(x: Int): Unit = ???
--------------------------------------------------------------------------------------------------------------------
32 changes: 32 additions & 0 deletions tests/neg/i22671.explain.scala
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading