Skip to content
Draft
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
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
extends MemberDef {
type ThisTree[+T <: Untyped] <: Trees.NameTree[T] & Trees.MemberDef[T] & ModuleDef
def withName(name: Name)(using Context): ModuleDef = cpy.ModuleDef(this)(name.toTermName, impl)
def isBackquoted: Boolean = hasAttachment(Backquoted)
}

/** An untyped template with a derives clause. Derived parents are added to the end
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ object NameOps {
// Ends with operator characters
while i >= 0 && isOperatorPart(name(i)) do i -= 1
if i == -1 then return true
// Optionnally prefixed with alpha-numeric characters followed by `_`
// Optionally prefixed with alpha-numeric characters followed by `_`
if name(i) != '_' then return false
while i >= 0 && isIdentifierPart(name(i)) do i -= 1
i == -1
Expand Down
56 changes: 42 additions & 14 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,7 @@ object Parsers {
if MigrationVersion.Scala2to3.needsPatch then
patch(source, Span(in.offset), " ")

def possibleTemplateStart(isNew: Boolean = false): Unit =
def possibleTemplateStart(): Unit =
in.observeColonEOL(inTemplate = true)
if in.token == COLONeol then
if in.lookahead.token == END then in.token = NEWLINE
Expand Down Expand Up @@ -2866,7 +2866,7 @@ object Parsers {
val parents =
if in.isNestedStart then Nil
else constrApps(exclude = COMMA)
possibleTemplateStart(isNew = true)
possibleTemplateStart()
parents match {
case parent :: Nil if !in.isNestedStart =>
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
Expand Down Expand Up @@ -3784,6 +3784,18 @@ object Parsers {
/* -------- DEFS ------------------------------------------- */

def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] =
def checkName(): Unit =
def checkName(name: Name): Unit =
if !name.isEmpty
&& !Chars.isOperatorPart(name.firstCodePoint) // warn a_: not ::
&& name.endsWith(":")
then
report.warning(AmbiguousTemplateName(md), md.namePos)
md match
case md @ TypeDef(name, impl: Template) if impl.body.isEmpty && !md.isBackquoted => checkName(name)
case md @ ModuleDef(name, impl) if impl.body.isEmpty && !md.isBackquoted => checkName(name)
case _ =>
checkName()
md.withMods(mods).setComment(in.getDocComment(start))

type ImportConstr = (Tree, List[ImportSelector]) => Tree
Expand Down Expand Up @@ -3996,7 +4008,14 @@ object Parsers {
val tpt = typedOpt()
val rhs =
if tpt.isEmpty || in.token == EQUALS then
accept(EQUALS)
if tpt.isEmpty && in.token != EQUALS then
lhs match
case Ident(name) :: Nil if name.endsWith(":") =>
val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?"
syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help))
case _ => accept(EQUALS)
else
accept(EQUALS)
val rhsOffset = in.offset
subExpr() match
case rhs0 @ Ident(name) if placeholderParams.nonEmpty && name == placeholderParams.head.name
Expand Down Expand Up @@ -4094,6 +4113,10 @@ object Parsers {
tpt = scalaUnit
if (in.token == LBRACE) expr()
else EmptyTree
else if in.token == IDENTIFIER && paramss.isEmpty && name.endsWith(":") then
val help = i"; identifier ends in colon, did you mean `${name.toSimpleName.dropRight(1)}`: in backticks?"
syntaxErrorOrIncomplete(ExpectedTokenButFound(EQUALS, in.token, suffix = help))
EmptyTree
else
if (!isExprIntro) syntaxError(MissingReturnType(), in.lastOffset)
accept(EQUALS)
Expand Down Expand Up @@ -4233,14 +4256,15 @@ object Parsers {

/** ClassDef ::= id ClassConstr TemplateOpt
*/
def classDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
classDefRest(start, mods, ident().toTypeName)
}
def classDef(start: Offset, mods: Modifiers): TypeDef =
val td = atSpan(start, nameStart):
classDefRest(mods, ident().toTypeName)
finalizeDef(td, mods, start)

def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef =
def classDefRest(mods: Modifiers, name: TypeName): TypeDef =
val constr = classConstr(if mods.is(Case) then ParamOwner.CaseClass else ParamOwner.Class)
val templ = templateOpt(constr)
finalizeDef(TypeDef(name, templ), mods, start)
TypeDef(name, templ)

/** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsTermParamClauses
*/
Expand All @@ -4258,11 +4282,15 @@ object Parsers {

/** ObjectDef ::= id TemplateOpt
*/
def objectDef(start: Offset, mods: Modifiers): ModuleDef = atSpan(start, nameStart) {
val name = ident()
val templ = templateOpt(emptyConstructor)
finalizeDef(ModuleDef(name, templ), mods, start)
}
def objectDef(start: Offset, mods: Modifiers): ModuleDef =
val md = atSpan(start, nameStart):
val nameIdent = termIdent()
val templ = templateOpt(emptyConstructor)
ModuleDef(nameIdent.name.asTermName, templ)
.tap: md =>
if nameIdent.isBackquoted then
md.pushAttachment(Backquoted, ())
finalizeDef(md, mods, start)

private def checkAccessOnly(mods: Modifiers, caseStr: String): Modifiers =
// We allow `infix` and `into` on `enum` definitions.
Expand Down Expand Up @@ -4494,7 +4522,7 @@ object Parsers {
Template(constr, parents, Nil, EmptyValDef, Nil)
else if !newSyntaxAllowed
|| in.token == WITH && tparams.isEmpty && vparamss.isEmpty
// if new syntax is still allowed and there are parameters, they mist be new style conditions,
// if new syntax is still allowed and there are parameters, they must be new style conditions,
// so old with-style syntax would not be allowed.
then
withTemplate(constr, parents)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case CannotInstantiateQuotedTypeVarID // errorNumber: 219
case DefaultShadowsGivenID // errorNumber: 220
case RecurseWithDefaultID // errorNumber: 221
case AmbiguousTemplateNameID // errorNumber: 222

def errorNumber = ordinal - 1

Expand Down
14 changes: 9 additions & 5 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import parsing.Tokens
import printing.Highlighting.*
import printing.Formatting
import ErrorMessageID.*
import ast.Trees
import ast.Trees.*
import ast.desugar
import ast.tpd
import ast.untpd
import config.{Feature, MigrationVersion, ScalaVersion}
import transform.patmat.Space
import transform.patmat.SpaceEngine
Expand All @@ -25,9 +27,6 @@ import typer.Inferencing
import scala.util.control.NonFatal
import StdNames.nme
import Formatting.{hl, delay}
import ast.Trees.*
import ast.untpd
import ast.tpd
import scala.util.matching.Regex
import java.util.regex.Matcher.quoteReplacement
import cc.CaptureSet.IdentityCaptRefMap
Expand Down Expand Up @@ -3109,7 +3108,7 @@ class MissingImplicitArgument(
def msg(using Context): String =

def formatMsg(shortForm: String)(headline: String = shortForm) = arg match
case arg: Trees.SearchFailureIdent[?] =>
case arg: SearchFailureIdent[?] =>
arg.tpe match
case _: NoMatchingImplicits => headline
case tpe: SearchFailureType =>
Expand Down Expand Up @@ -3741,3 +3740,8 @@ final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(Recurs
i"Recursive call used a default argument for parameter $name."
override protected def explain(using Context): String =
"It's more explicit to pass current or modified arguments in a recursion."

class AmbiguousTemplateName(tree: NamedDefTree[?])(using Context) extends SyntaxMsg(AmbiguousTemplateNameID):
override protected def msg(using Context) = i"name `${tree.name}` should be enclosed in backticks"
override protected def explain(using Context): String =
"Names with trailing operator characters may fuse with a subsequent colon if not set off by backquotes or spaces."
3 changes: 3 additions & 0 deletions tests/neg/i16072.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

enum Oops_:
case Z // error // error expected { and }
1 change: 1 addition & 0 deletions tests/neg/i18020.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import _root_.scala.StringContext // ok

class Test :
Expand Down
26 changes: 26 additions & 0 deletions tests/neg/i18020b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- [E040] Syntax Error: tests/neg/i18020b.scala:2:17 -------------------------------------------------------------------
2 |class i18020(a_: Int): // error
| ^^^
| ':' expected, but identifier found
-- [E040] Syntax Error: tests/neg/i18020b.scala:3:12 -------------------------------------------------------------------
3 | def f(b_: Int) = 42 // error
| ^^^
| ':' expected, but identifier found
-- [E040] Syntax Error: tests/neg/i18020b.scala:4:10 -------------------------------------------------------------------
4 | def g_: Int = 27 // error
| ^^^
| '=' expected, but identifier found; identifier ends in colon, did you mean `g_`: in backticks?
-- [E040] Syntax Error: tests/neg/i18020b.scala:6:12 -------------------------------------------------------------------
6 | val x_: Int = 1 // error
| ^^^
| '=' expected, but identifier found; identifier ends in colon, did you mean `x_`: in backticks?
-- [E040] Syntax Error: tests/neg/i18020b.scala:7:12 -------------------------------------------------------------------
7 | val y_: Int = 2 // error
| ^^^
| '=' expected, but identifier found; identifier ends in colon, did you mean `y_`: in backticks?
-- [E006] Not Found Error: tests/neg/i18020b.scala:8:4 -----------------------------------------------------------------
8 | x_ + y_ // error
| ^^
| Not found: x_ - did you mean x_:?
|
| longer explanation available when compiling with `-explain`
8 changes: 8 additions & 0 deletions tests/neg/i18020b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// problems with colon fusion, a harder challenge than cold fusion
class i18020(a_: Int): // error
def f(b_: Int) = 42 // error
def g_: Int = 27 // error
def k =
val x_: Int = 1 // error
val y_: Int = 2 // error
x_ + y_ // error
24 changes: 24 additions & 0 deletions tests/warn/i16072.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Warning: tests/warn/i16072.scala:4:2 --------------------------------------------------------------------------------
4 | def x = 1 // warn too far right
| ^
| Line is indented too far to the right, or a `{` or `:` is missing
-- [E222] Syntax Warning: tests/warn/i16072.scala:3:7 ------------------------------------------------------------------
3 |object Hello_: // warn colon in name without backticks because the body is empty
| ^^^^^^^
| name `Hello_:` should be enclosed in backticks
|
| longer explanation available when compiling with `-explain`
-- Deprecation Warning: tests/warn/i16072.scala:12:10 ------------------------------------------------------------------
12 |object :: : // warn deprecated colon without backticks for operator name
| ^
| `:` after symbolic operator is deprecated; use backticks around operator instead
-- Warning: tests/warn/i16072.scala:21:2 -------------------------------------------------------------------------------
21 | def y = 1 // warn
| ^
| Line is indented too far to the right, or a `{` or `:` is missing
-- [E222] Syntax Warning: tests/warn/i16072.scala:20:6 -----------------------------------------------------------------
20 |class Uhoh_: // warn
| ^^^^^^
| name `Uhoh_:` should be enclosed in backticks
|
| longer explanation available when compiling with `-explain`
26 changes: 26 additions & 0 deletions tests/warn/i16072.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//> using options -deprecation

object Hello_: // warn colon in name without backticks because the body is empty
def x = 1 // warn too far right

object Goodbye_: : // nowarn if non-empty body without nit-picking about backticks
def x = 2

object `Byte_`:
def x = 3

object :: : // warn deprecated colon without backticks for operator name
def x = 42

object ::: // nowarn

object Braces_: { // nowarn because body is non-empty with an EmptyTree
}

class Uhoh_: // warn
def y = 1 // warn

@main def hello =
println(Byte_)
println(Hello_:) // apparently user did forget a colon, see https://youforgotapercentagesignoracolon.com/
println(x)
Loading