Skip to content

Commit bae7646

Browse files
committed
Issue Illegal Identifier for names with dollars
1 parent 68396ca commit bae7646

File tree

5 files changed

+210
-13
lines changed

5 files changed

+210
-13
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,9 +1286,7 @@ object Parsers {
12861286
if (isIdent) {
12871287
val name = in.name
12881288
if name == nme.CONSTRUCTOR || name == nme.STATIC_CONSTRUCTOR then
1289-
report.error(
1290-
em"""Illegal backquoted identifier: `<init>` and `<clinit>` are forbidden""",
1291-
in.sourcePos())
1289+
report.error(IllegalIdentifier(name), in.sourcePos())
12921290
in.nextToken()
12931291
name
12941292
}
@@ -1297,6 +1295,19 @@ object Parsers {
12971295
nme.ERROR
12981296
}
12991297

1298+
/** An ident that is checked to be clean for a definition. */
1299+
def defName(): TermName =
1300+
val checkable = in.token != BACKQUOTED_IDENT
1301+
val start = in.sourcePos()
1302+
ident().tap: name =>
1303+
if checkable && name.toSimpleName.contains('$') then
1304+
report.error(IllegalIdentifier(name), start)
1305+
1306+
extension (nameTree: NameTree) def checkName: nameTree.type =
1307+
if !isBackquoted(nameTree) && nameTree.name.toSimpleName.contains('$') then
1308+
report.error(IllegalIdentifier(nameTree.name), nameTree.srcPos)
1309+
nameTree
1310+
13001311
/** Accept identifier and return Ident with its name as a term name. */
13011312
def termIdent(): Ident =
13021313
makeIdent(in.token, in.offset, ident())
@@ -1309,7 +1320,7 @@ object Parsers {
13091320
val tree = Ident(name)
13101321
if (tok == BACKQUOTED_IDENT) tree.pushAttachment(Backquoted, ())
13111322

1312-
// Make sure that even trees with parsing errors have a offset that is within the offset
1323+
// Make sure that even trees with parsing errors have an offset that is within the offset
13131324
val errorOffset = offset min (in.lastOffset - 1)
13141325
if (tree.name == nme.ERROR && tree.span == NoSpan) tree.withSpan(Span(errorOffset, errorOffset))
13151326
else atSpan(offset)(tree)
@@ -1379,7 +1390,7 @@ object Parsers {
13791390

13801391
/** QualId ::= id {`.' id}
13811392
*/
1382-
def qualId(): Tree = dotSelectors(termIdent())
1393+
def qualId(): Tree = dotSelectors(termIdent().checkName)
13831394

13841395
/** SimpleLiteral ::= [‘-’] integerLiteral
13851396
* | [‘-’] floatingPointLiteral
@@ -3773,7 +3784,7 @@ object Parsers {
37733784
mods |= Param
37743785
}
37753786
atSpan(start, nameStart) {
3776-
val name = ident()
3787+
val name = defName()
37773788
acceptColon()
37783789
if (in.token == ARROW && paramOwner.isClass && !mods.is(Local))
37793790
syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable)))
@@ -4103,6 +4114,7 @@ object Parsers {
41034114
case IdPattern(id, t) :: Nil if t.isEmpty =>
41044115
val vdef = ValDef(id.name.asTermName, tpt, rhs)
41054116
if (isBackquoted(id)) vdef.pushAttachment(Backquoted, ())
4117+
else checkName(id)
41064118
finalizeDef(vdef, mods, start)
41074119
case _ =>
41084120
def isAllIds = lhs.forall {
@@ -4158,7 +4170,7 @@ object Parsers {
41584170
}
41594171
else {
41604172
val mods1 = addFlag(mods, Method)
4161-
val ident = termIdent()
4173+
val ident = termIdent().checkName
41624174
var name = ident.name.asTermName
41634175
val paramss =
41644176
if sourceVersion.enablesClauseInterleaving then
@@ -4229,7 +4241,7 @@ object Parsers {
42294241

42304242
newLinesOpt()
42314243
atSpan(start, nameStart) {
4232-
val nameIdent = typeIdent()
4244+
val nameIdent = typeIdent().checkName
42334245
val isCapDef = gobbleHat()
42344246
val tname = nameIdent.name.asTypeName
42354247
val tparams = typeParamClauseOpt(ParamOwner.Hk)
@@ -4321,7 +4333,7 @@ object Parsers {
43214333
/** ClassDef ::= id ClassConstr TemplateOpt
43224334
*/
43234335
def classDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
4324-
classDefRest(start, mods, ident().toTypeName)
4336+
classDefRest(start, mods, defName().toTypeName)
43254337
}
43264338

43274339
def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef =
@@ -4346,7 +4358,7 @@ object Parsers {
43464358
/** ObjectDef ::= id TemplateOpt
43474359
*/
43484360
def objectDef(start: Offset, mods: Modifiers): ModuleDef = atSpan(start, nameStart) {
4349-
val name = ident()
4361+
val name = defName()
43504362
val templ = templateOpt(emptyConstructor)
43514363
finalizeDef(ModuleDef(name, templ), mods, start)
43524364
}
@@ -4366,7 +4378,7 @@ object Parsers {
43664378
*/
43674379
def enumDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
43684380
val mods1 = checkAccessOnly(mods, "")
4369-
val modulName = ident()
4381+
val modulName = defName()
43704382
val clsName = modulName.toTypeName
43714383
val constr = classConstr(ParamOwner.Class)
43724384
val templ = template(constr, isEnum = true)
@@ -4380,10 +4392,10 @@ object Parsers {
43804392
accept(CASE)
43814393

43824394
atSpan(start, nameStart) {
4383-
val id = termIdent()
4395+
val id = termIdent().checkName
43844396
if (in.token == COMMA) {
43854397
in.nextToken()
4386-
val ids = commaSeparated(() => termIdent())
4398+
val ids = commaSeparated(() => termIdent().checkName)
43874399
if ctx.settings.Whas.enumCommentDiscard then
43884400
in.getDocComment(start).foreach: comm =>
43894401
warning(

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
236236
case DefaultShadowsGivenID // errorNumber: 220
237237
case RecurseWithDefaultID // errorNumber: 221
238238
case EncodedPackageNameID // errorNumber: 222
239+
case IllegalIdentifierID // errorNumber: 223
239240

240241
def errorNumber = ordinal - 1
241242

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3749,3 +3749,21 @@ final class EncodedPackageName(name: Name)(using Context) extends SyntaxMsg(Enco
37493749
|or `myfile-test.scala` can produce encoded names for the generated package objects.
37503750
|
37513751
|In this case, the name `$name` is encoded as `${name.encode}`."""
3752+
3753+
final class IllegalIdentifier(name: Name)(using Context) extends SyntaxMsg(IllegalIdentifierID):
3754+
override protected def msg(using Context): String =
3755+
name match
3756+
case nme.CONSTRUCTOR | nme.STATIC_CONSTRUCTOR =>
3757+
"Illegal backquoted identifier: `<init>` and `<clinit>` are forbidden"
3758+
case _ =>
3759+
i"The identifier `$name` should not contain `$$`, which is reserved for internal compiler use."
3760+
override protected def explain(using Context): String =
3761+
name match
3762+
case nme.CONSTRUCTOR | nme.STATIC_CONSTRUCTOR =>
3763+
"Names can include unusual characters when enclosed in backquotes, but `<init>` and `<clinit>` are reserved."
3764+
case _ =>
3765+
i"""User identifiers may be encoded with embedded `$$`, and other compiler artifacts
3766+
|may rely on using `$$` with specific meanings.
3767+
|
3768+
|The prohibition against explicit `$$` may be ignored by enclosing the identifier in backquotes
3769+
|at the definition site."""

tests/neg/i18234.scala

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/* vals */
2+
val goodVal = 1
3+
val $startVal = 1 // error
4+
val mid$dleVal = 1 // error
5+
val endVal$ = 1 // error
6+
7+
def testValUsage =
8+
$startVal + endVal$ // ok, should only warn on declaration not on usage
9+
10+
/* functions */
11+
def $funcStart() = 3 // error
12+
def func$Middle() = 3 // error
13+
def funcEnd$() = 3 // error
14+
15+
def func1badArg(goodArg: Int, bad$Arg: Int) = 5 // error
16+
def func1badArgAndUsageDoesNotThrowWarning(goodArg: Int, bad$Arg: Int) = bad$Arg // error
17+
def func2badArgs(goodArg: Int, bad$Arg: Int, bad$arg2: String) = 5 // error // error
18+
def multilineFunc(
19+
goodArg: Int,
20+
badAr$g: Int // error
21+
) = 1
22+
23+
def testFuncUsaage =
24+
$funcStart() + func1badArg(1, 2) // ok, should only warn on declaration not on usage
25+
26+
/* types */
27+
type GoodType = Int
28+
type $StartType = Int // error
29+
type Middle$Type = Int // error
30+
type EndType$ = Int // error
31+
32+
val typedVal: Middle$Type = 2 // ok, should only warn on declaration not on usage
33+
def funcWithDollarTypes(foo: $StartType): Middle$Type = 2 // ok, should only warn on declaration not on usage
34+
35+
/* enums */
36+
enum GoodEnum:
37+
case GoodCase
38+
case $BadCaseStart // error
39+
case BadCase$Middle // error
40+
case BadCaseEnd$ // error
41+
42+
enum $BadEnumStart: // error
43+
case GoodCase
44+
case $BadCase // error
45+
46+
enum BadEnum$Middle: // error
47+
case GoodCase
48+
case Bad$Case // error
49+
50+
enum BadEnumEnd$: // error
51+
case GoodCase
52+
case BadCase$ // error
53+
54+
enum E$numWithEndKeyword: // error
55+
case SomeCase
56+
end E$numWithEndKeyword // ok
57+
58+
def TestEnumUsage(a: $BadEnumStart): Int = // ok, should only warn on declaration not on usage
59+
a match
60+
case $BadEnumStart.GoodCase => 1
61+
case $BadEnumStart.$BadCase => 2 // ok, should only warn on declaration not on usage
62+
63+
/* objects */
64+
object $ObjectStart: // error
65+
val goodVal = 1
66+
val $badVal = 2 // error
67+
68+
object Object$Middle: // error
69+
val goodVal = 1
70+
val bad$Val = 2 // error
71+
72+
object ObjectEnd$: // error
73+
val goodVal = 1
74+
val badVal$ = 2 // error
75+
76+
object GoodObject:
77+
val goodVal = 1
78+
val b$adVal = 2 // error
79+
80+
object Ob$jectWithEndKeyword: // error
81+
val someVal = 1
82+
end Ob$jectWithEndKeyword // ok
83+
84+
val testObjectUsage = ObjectEnd$.badVal$ // ok, should only warn on declaration not on usage
85+
86+
/* case classes */
87+
case class $InlineCaseClassStart(someField: Int) // error
88+
case class InlineCaseClass$Middle(someField: Int) // error
89+
case class InlineCaseClassEnd$(someField: Int) // error
90+
91+
case class InlineCaseClass(goodField: Int, badFiel$d: Int) // error
92+
93+
case class $CaseClassStart( // error
94+
somefield: Int,
95+
b$adfield: Int // error
96+
)
97+
98+
case class CaseClass$Middle( // error
99+
somefield: Int,
100+
bad$Field: Int // error
101+
)
102+
103+
case class CaseClassEnd$( // error
104+
somefield: Int,
105+
badField$: Int // error
106+
)
107+
108+
// companion oject
109+
object CaseClassEnd$: // error
110+
val food = 1
111+
112+
val testCaseClassUsage = CaseClass$Middle(somefield = 1, bad$Field = 2) // ok, should only warn on declaration not on usage
113+
114+
/* classes */
115+
class GoodClass
116+
class $StartClass // error
117+
class Middle$Class // error
118+
class EndClass$ // error
119+
120+
class Cla$$( // error
121+
var goodMember: Int,
122+
var badM$ember: Int // error
123+
):
124+
def goodMethod(x: Int) = badM$ember // ok, only checking if the method name does not contain a dollar sign
125+
def bad$Method(y: Int) = goodMember // error
126+
def methodWithBadArgNames(b$ad$arg: Int) = goodMember // error
127+
def method$WithEndKeyword() = // error
128+
3
129+
end method$WithEndKeyword
130+
end Cla$$ // ok
131+
132+
def testUsage =
133+
val instatiation = new Cla$$(goodMember = 1, badM$ember = 2) // ok, should only warn on declaration not on usage
134+
instatiation.bad$Method(1) // ok, should only warn on declaration not on usage
135+
instatiation.methodWithBadArgNames(2) // ok, should only warn on declaration not on usage
136+
137+
138+
/* traits */
139+
trait GoodTrait
140+
trait $BadTraitStart // error
141+
trait BadTrait$Middle // error
142+
trait BadTraitEnd$ // error
143+
144+
class TestTraitUsage extends $BadTraitStart // ok, should only warn on declaration not on usage
145+
146+
package GoodPackage:
147+
val goodVal = 1
148+
val b$adVal = 2 // error
149+
150+
package $BadPackageStart: // error
151+
val goodVal = 1
152+
val $badVal = 2 // error
153+
154+
package BadPackage$Middle: // error
155+
val goodVal = 1
156+
val bad$Val = 2 // error
157+
158+
package BadPackageEnd$ : // error
159+
val goodVal = 1
160+
val badVal$ = 2 // error
161+
162+
class BadConstructor:
163+
def `<init>`() = () // error // error

tests/pos/i18234.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
class `C$Z`:
3+
override def toString = "C$Z"

0 commit comments

Comments
 (0)