scala3
scala3 copied to clipboard
Surprising parsing behaviour for objects ending with `_`
Objects ending with _
that have a body are parsed in a surprising way.
I only narrowed it down accidentally, but the symptoms were lots of compiler errors because of the defs I expected to be in the companion actually being at top level.
Compiler version
3.2.0
Minimized code
You can see how Hello_:
is defined instead, and x
is treated as a top-level definition
object Hello_:
def x = 1
object `Byte_`:
def x = 1
@main def hello =
println(Byte_)
println(Hello_:)
println(x)
Output
This parsing warning is the only indication that helped me figure out that something was off:
[warn] ./test.scala:2:3: Line is indented too far to the right, or a `{` or `:` is missing
[warn] def x = 1
[warn] ^
Expectation
The code either parses correctly, or fails to parse, or gives a meaningful warning
That's a pretty good warning. Luckily everyone always compiles with -Werror
. But this is a longstanding issue. It leads to folks always adding a space before their punctuation.
scala> def _greeting_: String = "hello"
-- [E040] Syntax Error: ------------------------------------------------------------------------------------------------
1 |def _greeting_: String = "hello"
| ^^^^^^
| '=' expected, but identifier found
Here, we want to write _greeting_
for emphasis! greeting, Earthling!
I think the only thing we could do here is to improve the warning/error message. According to the language specification a sequence of alphanumeric characters directly followed by an underscore and a sequence of operator-like characters is treated as one identifier, so
object Hello_:
is equivalent to
object `Hello_:`
and not
object `Hello_`:
It feels like the warning is a warning because we thought that indenting too much is harmless, but here the indentation is evidently misleading. One of the basic reasons we went for indentation-based syntax is to prevent misleading indentation, so I think we should emit an error here. @odersky what do you think about this?
There are other situations where you might want this. Example:
val mainDef = ...
val mainDef_subSef1 = ...
...
val mainDef_subDefN = ...
(this pattern is used a lot in Definitions.scala
).
Or:
def foo =
val x = 2
x
+ y
+ z
Without a clear idea how this should be improved there's nothing to do. So far I have not seen anything.
Relatedly, https://github.com/lampepfl/dotty/issues/10671
I tried just checking ahead from objectDef or classDef when name.endsWith(":")
, as the suspicious case is easy to test for. I'll see if the carets line up.
Maybe it's worth the effort because "colon fusion" f_:
is a language characteristic.
I see on my local branch the carets did not line up. (Trailing comments, etc.)
On discord, the problem of "accidental fusion of colon" came up again, in the context that it is not only accidental but surprising, the way a banana peel is not surprising when someone slips on it, unless one has no experience with them.
Has no one ever proposed allowing (some subset of) trailing colon opchars only for operators, which are encouraged for infix? That sounds draconian. Perhaps a deprecation under -source:future
if lacking infix
.
Ok. So this issue happens because the users end the object's name with an underscore and they accidentally "fuse" the colon. How about we just refuse to compile objects with such names, unless they are quoted? We could emit an error early during the parsing stage.
Example of the problematic scenario:
object Hello_:
def x = 1
The user wanted to name the object Hello_
and accidentally created an object named Hello_:
instead. So: we refuse to compile this code (b/c the object's name ends in _:
without being quoted) and emit an error together with an explanation.
Example of fixed code:
object `Hello_:`
def x = 1
Now it should be clear to the user what the object's name actually is.
Thoughts?
it's not more restrictive than
➜ ~ scala -deprecation
Welcome to Scala 3.2.2 (19, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> {
| object :: :
| def f = 42
| }
1 warning found
-- Deprecation Warning: ------------------------------------------------------------------------------------------------
2 |object :: :
| ^
| `:` after symbolic operator is deprecated; use backticks around operator instead
// defined object ::