Skip to content

Commit 07aaaf9

Browse files
authored
Merge pull request #5471 from dotty-staging/fix-#5145
Fix #5145: Improve treatment of soft modifiers
2 parents 2fadaed + 36e8fe1 commit 07aaaf9

File tree

11 files changed

+98
-37
lines changed

11 files changed

+98
-37
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ object StdNames {
7474
final val IFkw: N = kw("if")
7575
final val IMPLICITkw: N = kw("implicit")
7676
final val IMPORTkw: N = kw("import")
77-
final val INLINEkw: N = kw("inline")
7877
final val LAZYkw: N = kw("lazy")
7978
final val MACROkw: N = kw("macro")
8079
final val MATCHkw: N = kw("match")
@@ -436,6 +435,7 @@ object StdNames {
436435
val implicitConversions: N = "implicitConversions"
437436
val implicitly: N = "implicitly"
438437
val in: N = "in"
438+
val inline: N = "inline"
439439
val info: N = "info"
440440
val inlinedEquals: N = "inlinedEquals"
441441
val internal: N = "internal"
@@ -478,6 +478,7 @@ object StdNames {
478478
val notify_ : N = "notify"
479479
val null_ : N = "null"
480480
val ofDim: N = "ofDim"
481+
val opaque: N = "opaque"
481482
val origin: N = "origin"
482483
val prefix : N = "prefix"
483484
val productArity: N = "productArity"

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

+22-20
Original file line numberDiff line numberDiff line change
@@ -166,22 +166,27 @@ object Parsers {
166166
def isSimpleLiteral: Boolean = simpleLiteralTokens contains in.token
167167
def isLiteral: Boolean = literalTokens contains in.token
168168
def isNumericLit: Boolean = numericLitTokens contains in.token
169-
def isModifier: Boolean = modifierTokens.contains(in.token) || isIdent(nme.INLINEkw)
170-
def isBindingIntro: Boolean = canStartBindingTokens contains in.token
171169
def isTemplateIntro: Boolean = templateIntroTokens contains in.token
172170
def isDclIntro: Boolean = dclIntroTokens contains in.token
173171
def isStatSeqEnd: Boolean = in.token == RBRACE || in.token == EOF
174172
def mustStartStat: Boolean = mustStartStatTokens contains in.token
175173

174+
/** Is current token a hard or soft modifier (in modifier position or not)? */
175+
def isModifier: Boolean = modifierTokens.contains(in.token) || in.isSoftModifier
176+
177+
def isBindingIntro: Boolean =
178+
canStartBindingTokens.contains(in.token) &&
179+
!in.isSoftModifierInModifierPosition
180+
176181
def isExprIntro: Boolean =
177-
(canStartExpressionTokens `contains` in.token) &&
178-
(!isIdent(nme.INLINEkw) || lookaheadIn(canStartExpressionTokens))
182+
canStartExpressionTokens.contains(in.token) &&
183+
!in.isSoftModifierInModifierPosition
179184

180185
def isDefIntro(allowedMods: BitSet): Boolean =
181186
in.token == AT ||
182187
(defIntroTokens `contains` in.token) ||
183188
(allowedMods `contains` in.token) ||
184-
isIdent(nme.INLINEkw) && lookaheadIn(BitSet(AT) | defIntroTokens | allowedMods)
189+
in.isSoftModifierInModifierPosition
185190

186191
def isStatSep: Boolean =
187192
in.token == NEWLINE || in.token == NEWLINES || in.token == SEMI
@@ -455,14 +460,6 @@ object Parsers {
455460

456461
def commaSeparated[T](part: () => T): List[T] = tokenSeparated(COMMA, part)
457462

458-
/** Is the token following the current one in `tokens`? */
459-
def lookaheadIn(tokens: BitSet): Boolean = {
460-
val lookahead = in.lookaheadScanner
461-
do lookahead.nextToken()
462-
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES)
463-
tokens.contains(lookahead.token)
464-
}
465-
466463
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
467464

468465
var opStack: List[OpInfo] = Nil
@@ -841,7 +838,7 @@ object Parsers {
841838

842839
/** Is current ident a `*`, and is it followed by a `)` or `,`? */
843840
def isPostfixStar: Boolean =
844-
in.name == nme.raw.STAR && lookaheadIn(BitSet(RPAREN, COMMA))
841+
in.name == nme.raw.STAR && in.lookaheadIn(BitSet(RPAREN, COMMA))
845842

846843
def infixTypeRest(t: Tree): Tree =
847844
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)
@@ -899,7 +896,7 @@ object Parsers {
899896
val start = in.skipToken()
900897
typeBounds().withPos(Position(start, in.lastOffset, start))
901898
}
902-
else if (isIdent(nme.raw.TILDE) && lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
899+
else if (isIdent(nme.raw.TILDE) && in.lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
903900
atPos(in.offset) { PrefixOp(typeIdent(), path(thisOK = true)) }
904901
else path(thisOK = false, handleSingletonType) match {
905902
case r @ SingletonTypeTree(_) => r
@@ -1744,8 +1741,11 @@ object Parsers {
17441741
case PRIVATE => Mod.Private()
17451742
case PROTECTED => Mod.Protected()
17461743
case SEALED => Mod.Sealed()
1747-
case OPAQUE => Mod.Opaque()
1748-
case IDENTIFIER if name == nme.INLINEkw => Mod.Inline()
1744+
case IDENTIFIER =>
1745+
name match {
1746+
case nme.inline => Mod.Inline()
1747+
case nme.opaque => Mod.Opaque()
1748+
}
17491749
}
17501750

17511751
/** Drop `private' modifier when followed by a qualifier.
@@ -1816,7 +1816,8 @@ object Parsers {
18161816
@tailrec
18171817
def loop(mods: Modifiers): Modifiers = {
18181818
if (allowed.contains(in.token) ||
1819-
isIdent(nme.INLINEkw) && localModifierTokens.subsetOf(allowed)) {
1819+
in.isSoftModifier &&
1820+
localModifierTokens.subsetOf(allowed)) { // soft modifiers are admissible everywhere local modifiers are
18201821
val isAccessMod = accessModifierTokens contains in.token
18211822
val mods1 = addModifier(mods)
18221823
loop(if (isAccessMod) accessQualifierOpt(mods1) else mods1)
@@ -1957,7 +1958,8 @@ object Parsers {
19571958
}
19581959
}
19591960
else {
1960-
if (isIdent(nme.INLINEkw)) mods = addModifier(mods)
1961+
if (isIdent(nme.inline) && in.isSoftModifierInParamModifierPosition)
1962+
mods = addModifier(mods)
19611963
mods = atPos(start) { mods | Param }
19621964
}
19631965
atPos(start, nameStart) {
@@ -2616,7 +2618,7 @@ object Parsers {
26162618
if (in.token == IMPLICIT || in.token == ERASED) {
26172619
val start = in.offset
26182620
var imods = modifiers(funArgMods)
2619-
if (isBindingIntro && !isIdent(nme.INLINEkw))
2621+
if (isBindingIntro)
26202622
stats += implicitClosure(start, Location.InBlock, imods)
26212623
else
26222624
stats +++= localDef(start, imods)

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

+31-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import util.NameTransformer.avoidIllegalChars
1111
import Tokens._
1212
import scala.annotation.{ switch, tailrec }
1313
import scala.collection.mutable
14-
import scala.collection.immutable.SortedMap
14+
import scala.collection.immutable.{SortedMap, BitSet}
1515
import rewrites.Rewrites.patch
1616

1717
object Scanners {
@@ -301,9 +301,6 @@ object Scanners {
301301
case _ =>
302302
}
303303

304-
/** A new Scanner that starts at the current token offset */
305-
def lookaheadScanner: Scanner = new Scanner(source, offset)
306-
307304
/** Produce next token, filling TokenData fields of Scanner.
308305
*/
309306
def nextToken(): Unit = {
@@ -637,6 +634,28 @@ object Scanners {
637634
else false
638635
}
639636

637+
// Lookahead ---------------------------------------------------------------
638+
639+
/** A new Scanner that starts at the current token offset */
640+
def lookaheadScanner: Scanner = new Scanner(source, offset)
641+
642+
/** Is the token following the current one in `tokens`? */
643+
def lookaheadIn(tokens: BitSet): Boolean = {
644+
val lookahead = lookaheadScanner
645+
do lookahead.nextToken()
646+
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES)
647+
tokens.contains(lookahead.token)
648+
}
649+
650+
/** Is the current token in a position where a modifier is allowed? */
651+
def inModifierPosition(): Boolean = {
652+
val lookahead = lookaheadScanner
653+
do lookahead.nextToken()
654+
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES ||
655+
lookahead.isSoftModifier)
656+
modifierFollowers.contains(lookahead.token)
657+
}
658+
640659
// Identifiers ---------------------------------------------------------------
641660

642661
private def getBackquotedIdent(): Unit = {
@@ -717,6 +736,14 @@ object Scanners {
717736
}
718737
}
719738

739+
def isSoftModifier: Boolean =
740+
token == IDENTIFIER && softModifierNames.contains(name)
741+
742+
def isSoftModifierInModifierPosition: Boolean =
743+
isSoftModifier && inModifierPosition()
744+
745+
def isSoftModifierInParamModifierPosition: Boolean =
746+
isSoftModifier && !lookaheadIn(BitSet(COLON))
720747

721748
// Literals -----------------------------------------------------------------
722749

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package parsing
44

55
import collection.immutable.BitSet
66
import core.Decorators._
7+
import core.StdNames.nme
78

89
abstract class TokensCommon {
910
def maxToken: Int
@@ -93,7 +94,6 @@ abstract class TokensCommon {
9394
//final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate
9495
//final val ENUM = 62; enter(ENUM, "enum")
9596
//final val ERASED = 63; enter(ERASED, "erased")
96-
//final val OPAQUE = 64; enter(OPAQUE, "opaque")
9797

9898
/** special symbols */
9999
final val COMMA = 70; enter(COMMA, "','")
@@ -178,7 +178,6 @@ object Tokens extends TokensCommon {
178178
final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate
179179
final val ENUM = 62; enter(ENUM, "enum")
180180
final val ERASED = 63; enter(ERASED, "erased")
181-
final val OPAQUE = 64; enter(OPAQUE, "opaque")
182181

183182
/** special symbols */
184183
final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line")
@@ -199,7 +198,7 @@ object Tokens extends TokensCommon {
199198
/** XML mode */
200199
final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
201200

202-
final val alphaKeywords: TokenSet = tokenRange(IF, OPAQUE)
201+
final val alphaKeywords: TokenSet = tokenRange(IF, ERASED)
203202
final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND)
204203
final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND)
205204
final val keywords: TokenSet = alphaKeywords | symbolicKeywords
@@ -227,7 +226,7 @@ object Tokens extends TokensCommon {
227226
final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens
228227

229228
final val localModifierTokens: TokenSet = BitSet(
230-
ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED, OPAQUE)
229+
ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED)
231230

232231
final val accessModifierTokens: TokenSet = BitSet(
233232
PRIVATE, PROTECTED)
@@ -237,6 +236,8 @@ object Tokens extends TokensCommon {
237236

238237
final val modifierTokensOrCase: TokenSet = modifierTokens | BitSet(CASE)
239238

239+
final val modifierFollowers = modifierTokens | defIntroTokens
240+
240241
/** Is token only legal as start of statement (eof also included)? */
241242
final val mustStartStatTokens: TokenSet = defIntroTokens | modifierTokens | BitSet(IMPORT, PACKAGE)
242243

@@ -247,4 +248,6 @@ object Tokens extends TokensCommon {
247248
TYPE, RPAREN, RBRACE, RBRACKET)
248249

249250
final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT)
251+
252+
final val softModifierNames = Set(nme.`inline`, nme.`opaque`)
250253
}

compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ object SyntaxHighlighting {
5353
val start = scanner.offset
5454
val token = scanner.token
5555
val name = scanner.name
56+
val isSoftModifier = scanner.isSoftModifierInModifierPosition
5657
scanner.nextToken()
5758
val end = scanner.lastOffset
5859

@@ -67,11 +68,7 @@ object SyntaxHighlighting {
6768
// we don't highlight it, hence the `-1`
6869
highlightRange(start, end - 1, LiteralColor)
6970

70-
case _ if alphaKeywords.contains(token) =>
71-
highlightRange(start, end, KeywordColor)
72-
73-
case IDENTIFIER if name == nme.INLINEkw =>
74-
// `inline` is a "soft" keyword
71+
case _ if alphaKeywords.contains(token) || isSoftModifier =>
7572
highlightRange(start, end, KeywordColor)
7673

7774
case IDENTIFIER if name == nme.??? =>

compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala

+3
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,8 @@ class SyntaxHighlightingTests extends DottyTest {
125125
test("inline def foo = 1", "<K|inline> <K|def> <V|foo> = <L|1>")
126126
test("@inline def foo = 1", "<T|@inline> <K|def> <V|foo> = <L|1>")
127127
test("class inline", "<K|class> <T|inline>")
128+
test("val inline = 2", "<K|val> <V|inline> = <L|2>")
129+
test("def inline = 2", "<K|def> <V|inline> = <L|2>")
130+
test("def foo(inline: Int) = 2", "<K|def> <V|foo>(<V|inline>: <T|Int>) = <L|2>")
128131
}
129132
}

docs/docs/reference/inline.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ layout: doc-page
33
title: Inline
44
---
55

6-
`inline` is a new modifier that guarantees that a definition will be
7-
inline at the point of use. Example:
6+
`inline` is a new [soft modifier](./soft-modifier.html) that guarantees that a definition will be inline at the point of use. Example:
87

98
object Config {
109
inline val logging = false

docs/docs/reference/opaques.md

+2
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@ But the following operations would lead to type errors:
5353
l / l2 // error: `/` is not a member fo Logarithm
5454
```
5555

56+
`opaque` is a [soft modifier](./soft-modifier.html).
57+
5658
For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html).

docs/docs/reference/soft-modifier.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
layout: doc-page
3+
title: Soft Modifiers
4+
---
5+
6+
A soft modifier is one of the identifiers `opaque` and `inline`.
7+
8+
It is treated as a potential modifier of a definition, if it is followed by a hard modifier or a keyword combination starting a definition (`def`, `val`, `var`, `type`, `class`, `case class`, `trait`, `object`, `case object`, `enum`). Between the two words there may be a sequence of newline tokens and soft modifiers.
9+
10+
It is treated as a potential modifier of a parameter binding unless it is followed by `:`.
11+

tests/neg/inlinevals.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Test {
77
inline val N = 10
88
def X = 20
99

10-
inline inline val twice = 30 // error: repeated modifier // error: not found: inline
10+
inline inline val twice = 30 // error: repeated modifier
1111

1212
class C(inline x: Int, private inline val y: Int) { // error // error
1313
inline val foo: Int // error: abstract member may not be inline

tests/pos/i5145.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class Test {
2+
def foo(x: Int): Int = {
3+
val inline = 3
4+
def opaque(x: Int): Unit = ()
5+
opaque(3)
6+
inline
7+
}
8+
def bar(inline: Int => Int) = 3
9+
inline def baz(inline x: Int => Int) = 3
10+
11+
locally {
12+
bar(inline = identity)
13+
bar(inline => inline)
14+
bar(implicit inline => inline)
15+
}
16+
}

0 commit comments

Comments
 (0)