Skip to content

Fix #5145: Improve treatment of soft modifiers #5471

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

Merged
merged 6 commits into from
Nov 20, 2018
Merged
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
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ object StdNames {
final val IFkw: N = kw("if")
final val IMPLICITkw: N = kw("implicit")
final val IMPORTkw: N = kw("import")
final val INLINEkw: N = kw("inline")
final val LAZYkw: N = kw("lazy")
final val MACROkw: N = kw("macro")
final val MATCHkw: N = kw("match")
Expand Down Expand Up @@ -436,6 +435,7 @@ object StdNames {
val implicitConversions: N = "implicitConversions"
val implicitly: N = "implicitly"
val in: N = "in"
val inline: N = "inline"
val info: N = "info"
val inlinedEquals: N = "inlinedEquals"
val internal: N = "internal"
Expand Down Expand Up @@ -478,6 +478,7 @@ object StdNames {
val notify_ : N = "notify"
val null_ : N = "null"
val ofDim: N = "ofDim"
val opaque: N = "opaque"
val origin: N = "origin"
val prefix : N = "prefix"
val productArity: N = "productArity"
Expand Down
42 changes: 22 additions & 20 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,27 @@ object Parsers {
def isSimpleLiteral: Boolean = simpleLiteralTokens contains in.token
def isLiteral: Boolean = literalTokens contains in.token
def isNumericLit: Boolean = numericLitTokens contains in.token
def isModifier: Boolean = modifierTokens.contains(in.token) || isIdent(nme.INLINEkw)
def isBindingIntro: Boolean = canStartBindingTokens contains in.token
def isTemplateIntro: Boolean = templateIntroTokens contains in.token
def isDclIntro: Boolean = dclIntroTokens contains in.token
def isStatSeqEnd: Boolean = in.token == RBRACE || in.token == EOF
def mustStartStat: Boolean = mustStartStatTokens contains in.token

/** Is current token a hard or soft modifier (in modifier position or not)? */
def isModifier: Boolean = modifierTokens.contains(in.token) || in.isSoftModifier

def isBindingIntro: Boolean =
canStartBindingTokens.contains(in.token) &&
!in.isSoftModifierInModifierPosition

def isExprIntro: Boolean =
(canStartExpressionTokens `contains` in.token) &&
(!isIdent(nme.INLINEkw) || lookaheadIn(canStartExpressionTokens))
canStartExpressionTokens.contains(in.token) &&
!in.isSoftModifierInModifierPosition

def isDefIntro(allowedMods: BitSet): Boolean =
in.token == AT ||
(defIntroTokens `contains` in.token) ||
(allowedMods `contains` in.token) ||
isIdent(nme.INLINEkw) && lookaheadIn(BitSet(AT) | defIntroTokens | allowedMods)
in.isSoftModifierInModifierPosition

def isStatSep: Boolean =
in.token == NEWLINE || in.token == NEWLINES || in.token == SEMI
Expand Down Expand Up @@ -455,14 +460,6 @@ object Parsers {

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

/** Is the token following the current one in `tokens`? */
def lookaheadIn(tokens: BitSet): Boolean = {
val lookahead = in.lookaheadScanner
do lookahead.nextToken()
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES)
tokens.contains(lookahead.token)
}

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

var opStack: List[OpInfo] = Nil
Expand Down Expand Up @@ -841,7 +838,7 @@ object Parsers {

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

def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)
Expand Down Expand Up @@ -899,7 +896,7 @@ object Parsers {
val start = in.skipToken()
typeBounds().withPos(Position(start, in.lastOffset, start))
}
else if (isIdent(nme.raw.TILDE) && lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
else if (isIdent(nme.raw.TILDE) && in.lookaheadIn(BitSet(IDENTIFIER, BACKQUOTED_IDENT)))
atPos(in.offset) { PrefixOp(typeIdent(), path(thisOK = true)) }
else path(thisOK = false, handleSingletonType) match {
case r @ SingletonTypeTree(_) => r
Expand Down Expand Up @@ -1744,8 +1741,11 @@ object Parsers {
case PRIVATE => Mod.Private()
case PROTECTED => Mod.Protected()
case SEALED => Mod.Sealed()
case OPAQUE => Mod.Opaque()
case IDENTIFIER if name == nme.INLINEkw => Mod.Inline()
case IDENTIFIER =>
name match {
case nme.inline => Mod.Inline()
case nme.opaque => Mod.Opaque()
}
}

/** Drop `private' modifier when followed by a qualifier.
Expand Down Expand Up @@ -1816,7 +1816,8 @@ object Parsers {
@tailrec
def loop(mods: Modifiers): Modifiers = {
if (allowed.contains(in.token) ||
isIdent(nme.INLINEkw) && localModifierTokens.subsetOf(allowed)) {
in.isSoftModifier &&
localModifierTokens.subsetOf(allowed)) { // soft modifiers are admissible everywhere local modifiers are
val isAccessMod = accessModifierTokens contains in.token
val mods1 = addModifier(mods)
loop(if (isAccessMod) accessQualifierOpt(mods1) else mods1)
Expand Down Expand Up @@ -1957,7 +1958,8 @@ object Parsers {
}
}
else {
if (isIdent(nme.INLINEkw)) mods = addModifier(mods)
if (isIdent(nme.inline) && in.isSoftModifierInParamModifierPosition)
mods = addModifier(mods)
mods = atPos(start) { mods | Param }
}
atPos(start, nameStart) {
Expand Down Expand Up @@ -2616,7 +2618,7 @@ object Parsers {
if (in.token == IMPLICIT || in.token == ERASED) {
val start = in.offset
var imods = modifiers(funArgMods)
if (isBindingIntro && !isIdent(nme.INLINEkw))
if (isBindingIntro)
stats += implicitClosure(start, Location.InBlock, imods)
else
stats +++= localDef(start, imods)
Expand Down
35 changes: 31 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import util.NameTransformer.avoidIllegalChars
import Tokens._
import scala.annotation.{ switch, tailrec }
import scala.collection.mutable
import scala.collection.immutable.SortedMap
import scala.collection.immutable.{SortedMap, BitSet}
import rewrites.Rewrites.patch

object Scanners {
Expand Down Expand Up @@ -301,9 +301,6 @@ object Scanners {
case _ =>
}

/** A new Scanner that starts at the current token offset */
def lookaheadScanner: Scanner = new Scanner(source, offset)

/** Produce next token, filling TokenData fields of Scanner.
*/
def nextToken(): Unit = {
Expand Down Expand Up @@ -637,6 +634,28 @@ object Scanners {
else false
}

// Lookahead ---------------------------------------------------------------

/** A new Scanner that starts at the current token offset */
def lookaheadScanner: Scanner = new Scanner(source, offset)

/** Is the token following the current one in `tokens`? */
def lookaheadIn(tokens: BitSet): Boolean = {
val lookahead = lookaheadScanner
do lookahead.nextToken()
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES)
tokens.contains(lookahead.token)
}

/** Is the current token in a position where a modifier is allowed? */
def inModifierPosition(): Boolean = {
val lookahead = lookaheadScanner
do lookahead.nextToken()
while (lookahead.token == NEWLINE || lookahead.token == NEWLINES ||
lookahead.isSoftModifier)
modifierFollowers.contains(lookahead.token)
}

// Identifiers ---------------------------------------------------------------

private def getBackquotedIdent(): Unit = {
Expand Down Expand Up @@ -717,6 +736,14 @@ object Scanners {
}
}

def isSoftModifier: Boolean =
token == IDENTIFIER && softModifierNames.contains(name)

def isSoftModifierInModifierPosition: Boolean =
isSoftModifier && inModifierPosition()

def isSoftModifierInParamModifierPosition: Boolean =
isSoftModifier && !lookaheadIn(BitSet(COLON))

// Literals -----------------------------------------------------------------

Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package parsing

import collection.immutable.BitSet
import core.Decorators._
import core.StdNames.nme

abstract class TokensCommon {
def maxToken: Int
Expand Down Expand Up @@ -93,7 +94,6 @@ abstract class TokensCommon {
//final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate
//final val ENUM = 62; enter(ENUM, "enum")
//final val ERASED = 63; enter(ERASED, "erased")
//final val OPAQUE = 64; enter(OPAQUE, "opaque")

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

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

final val alphaKeywords: TokenSet = tokenRange(IF, OPAQUE)
final val alphaKeywords: TokenSet = tokenRange(IF, ERASED)
final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND)
final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND)
final val keywords: TokenSet = alphaKeywords | symbolicKeywords
Expand Down Expand Up @@ -227,7 +226,7 @@ object Tokens extends TokensCommon {
final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens

final val localModifierTokens: TokenSet = BitSet(
ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED, OPAQUE)
ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED)

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

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

final val modifierFollowers = modifierTokens | defIntroTokens

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

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

final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT)

final val softModifierNames = Set(nme.`inline`, nme.`opaque`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ object SyntaxHighlighting {
val start = scanner.offset
val token = scanner.token
val name = scanner.name
val isSoftModifier = scanner.isSoftModifierInModifierPosition
scanner.nextToken()
val end = scanner.lastOffset

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

case _ if alphaKeywords.contains(token) =>
highlightRange(start, end, KeywordColor)

case IDENTIFIER if name == nme.INLINEkw =>
// `inline` is a "soft" keyword
case _ if alphaKeywords.contains(token) || isSoftModifier =>
highlightRange(start, end, KeywordColor)

case IDENTIFIER if name == nme.??? =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,8 @@ class SyntaxHighlightingTests extends DottyTest {
test("inline def foo = 1", "<K|inline> <K|def> <V|foo> = <L|1>")
test("@inline def foo = 1", "<T|@inline> <K|def> <V|foo> = <L|1>")
test("class inline", "<K|class> <T|inline>")
test("val inline = 2", "<K|val> <V|inline> = <L|2>")
test("def inline = 2", "<K|def> <V|inline> = <L|2>")
test("def foo(inline: Int) = 2", "<K|def> <V|foo>(<V|inline>: <T|Int>) = <L|2>")
}
}
3 changes: 1 addition & 2 deletions docs/docs/reference/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ layout: doc-page
title: Inline
---

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

object Config {
inline val logging = false
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/reference/opaques.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ But the following operations would lead to type errors:
l / l2 // error: `/` is not a member fo Logarithm
```

`opaque` is a [soft modifier](./soft-modifier.html).

For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html).
11 changes: 11 additions & 0 deletions docs/docs/reference/soft-modifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
layout: doc-page
title: Soft Modifiers
---

A soft modifier is one of the identifiers `opaque` and `inline`.

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.

It is treated as a potential modifier of a parameter binding unless it is followed by `:`.

2 changes: 1 addition & 1 deletion tests/neg/inlinevals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object Test {
inline val N = 10
def X = 20

inline inline val twice = 30 // error: repeated modifier // error: not found: inline
inline inline val twice = 30 // error: repeated modifier

class C(inline x: Int, private inline val y: Int) { // error // error
inline val foo: Int // error: abstract member may not be inline
Expand Down
16 changes: 16 additions & 0 deletions tests/pos/i5145.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Test {
def foo(x: Int): Int = {
val inline = 3
def opaque(x: Int): Unit = ()
opaque(3)
inline
}
def bar(inline: Int => Int) = 3
inline def baz(inline x: Int => Int) = 3

locally {
bar(inline = identity)
bar(inline => inline)
bar(implicit inline => inline)
}
}