Skip to content

Use other tree for actual symbol of Assign #22869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
40 changes: 28 additions & 12 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Dynamic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
14 changes: 9 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) =
Expand Down Expand Up @@ -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)
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
|
| 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).<=?
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) 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 = ???
--------------------------------------------------------------------------------------------------------------------
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