Skip to content

Commit dacfba8

Browse files
authored
Merge pull request #7363 from dotty-staging/change-indent-with
Tweaks to indent syntax
2 parents 210bdc6 + 75e85fc commit dacfba8

File tree

17 files changed

+475
-288
lines changed

17 files changed

+475
-288
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ object Config {
160160
/** Assume -indent by default */
161161
final val defaultIndent = true
162162

163+
/** Assume indentation is significant after a class, object, ... signature */
164+
final val silentTemplateIndent = true
165+
163166
/** If set, prints a trace of all symbol completions */
164167
final val showCompletions = false
165168

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -832,11 +832,12 @@ object SymDenotations {
832832
| Access to protected $this not permitted because enclosing ${ctx.owner.enclosingClass.showLocated}
833833
| is not a subclass of ${owner.showLocated} where target is defined""")
834834
else if
835-
(!( isType // allow accesses to types from arbitrary subclasses fixes #4737
835+
!( isType // allow accesses to types from arbitrary subclasses fixes #4737
836836
|| pre.derivesFrom(cls)
837837
|| isConstructor
838838
|| owner.is(ModuleClass) // don't perform this check for static members
839-
))
839+
)
840+
then
840841
fail(
841842
i"""
842843
| Access to protected ${symbol.show} not permitted because prefix type ${pre.widen.show}

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

Lines changed: 99 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Decorators._
2626
import scala.internal.Chars
2727
import scala.annotation.{tailrec, switch}
2828
import rewrites.Rewrites.{patch, overlapsPatch}
29+
import config.Config.silentTemplateIndent
2930

3031
object Parsers {
3132

@@ -124,13 +125,13 @@ object Parsers {
124125

125126
/* ------------- ERROR HANDLING ------------------------------------------- */
126127
/** The offset where the last syntax error was reported, or if a skip to a
127-
* safepoint occurred afterwards, the offset of the safe point.
128-
*/
128+
* safepoint occurred afterwards, the offset of the safe point.
129+
*/
129130
protected var lastErrorOffset : Int = -1
130131

131132
/** Issue an error at given offset if beyond last error offset
132-
* and update lastErrorOffset.
133-
*/
133+
* and update lastErrorOffset.
134+
*/
134135
def syntaxError(msg: => Message, offset: Int = in.offset): Unit =
135136
if (offset > lastErrorOffset) {
136137
val length = if (offset == in.offset && in.name != null) in.name.show.length else 0
@@ -337,6 +338,9 @@ object Parsers {
337338
offset
338339
}
339340

341+
def reportMissing(expected: Token): Unit =
342+
syntaxError(ExpectedTokenButFound(expected, in.token))
343+
340344
/** semi = nl {nl} | `;'
341345
* nl = `\n' // where allowed
342346
*/
@@ -356,6 +360,17 @@ object Parsers {
356360
accept(SEMI)
357361
}
358362

363+
/** Under -language:Scala2 or -old-syntax, flag
364+
*
365+
* extends p1 with new p1 with t1 with
366+
* p2 p2 t2
367+
*
368+
* as a migration warning or error since that means something else under significant indentation.
369+
*/
370+
def checkNotWithAtEOL(): Unit =
371+
if (in.isScala2Mode || in.oldSyntax) && in.isAfterLineEnd then
372+
in.errorOrMigrationWarning("`with` cannot be followed by new line, place at beginning of next line instead")
373+
359374
def rewriteNotice(additionalOption: String = "") = {
360375
val optionStr = if (additionalOption.isEmpty) "" else " " ++ additionalOption
361376
i"\nThis construct can be rewritten automatically under$optionStr -rewrite."
@@ -616,6 +631,7 @@ object Parsers {
616631

617632
/** If indentation is not significant, check that this is not the start of a
618633
* statement that's indented relative to the current region.
634+
* TODO: Drop if `with` is required before indented template definitions.
619635
*/
620636
def checkNextNotIndented(): Unit = in.currentRegion match
621637
case r: IndentSignificantRegion if in.isNewLine =>
@@ -1249,10 +1265,14 @@ object Parsers {
12491265
newLineOptWhenFollowedBy(LBRACE)
12501266
}
12511267

1252-
def possibleTemplateStart(): Unit = {
1253-
in.observeIndented()
1254-
newLineOptWhenFollowedBy(LBRACE)
1255-
}
1268+
def possibleTemplateStart(isNew: Boolean = false): Unit =
1269+
if in.token == WITH then
1270+
in.nextToken()
1271+
if in.token != LBRACE && in.token != INDENT then
1272+
syntaxError(i"indented definitions or `{' expected")
1273+
else
1274+
if silentTemplateIndent && !isNew then in.observeIndented()
1275+
newLineOptWhenFollowedBy(LBRACE)
12561276

12571277
def indentRegion[T](tag: EndMarkerTag)(op: => T): T = {
12581278
val iw = in.currentRegion.indentWidth
@@ -1396,7 +1416,7 @@ object Parsers {
13961416
makeParameter(name, typ(), mods | Param)
13971417
}
13981418

1399-
/** InfixType ::= RefinedType {id [nl] refinedType}
1419+
/** InfixType ::= RefinedType {id [nl] RefinedType}
14001420
*/
14011421
def infixType(): Tree = infixTypeRest(refinedType())
14021422

@@ -1407,7 +1427,7 @@ object Parsers {
14071427
def infixTypeRest(t: Tree): Tree =
14081428
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)
14091429

1410-
/** RefinedType ::= WithType {Annotation | [nl] Refinement}
1430+
/** RefinedType ::= WithType {[nl | `with'] Refinement}
14111431
*/
14121432
val refinedType: () => Tree = () => refinedTypeRest(withType())
14131433

@@ -1423,12 +1443,16 @@ object Parsers {
14231443
def withType(): Tree = withTypeRest(annotType())
14241444

14251445
def withTypeRest(t: Tree): Tree =
1426-
if (in.token == WITH) {
1427-
if (ctx.settings.strict.value)
1428-
deprecationWarning(DeprecatedWithOperator())
1446+
if in.token == WITH then
1447+
val withOffset = in.offset
14291448
in.nextToken()
1430-
makeAndType(t, withType())
1431-
}
1449+
if in.token == LBRACE || in.token == INDENT then
1450+
t
1451+
else
1452+
checkNotWithAtEOL()
1453+
if (ctx.settings.strict.value)
1454+
deprecationWarning(DeprecatedWithOperator(), withOffset)
1455+
makeAndType(t, withType())
14321456
else t
14331457

14341458
/** AnnotType ::= SimpleType {Annotation}
@@ -1676,7 +1700,7 @@ object Parsers {
16761700
else
16771701
if (altToken == THEN || enclosedInParens) && in.isNewLine then
16781702
in.observeIndented()
1679-
if !enclosedInParens && in.token != INDENT then accept(altToken)
1703+
if !enclosedInParens && in.token != INDENT then reportMissing(altToken)
16801704
if (rewriteToNewSyntax(t.span))
16811705
dropParensOrBraces(t.span.start, s"${tokenString(altToken)}")
16821706
t
@@ -2125,26 +2149,19 @@ object Parsers {
21252149
}
21262150
}
21272151

2128-
/** SimpleExpr ::= ‘new’ (ConstrApp {`with` ConstrApp} [TemplateBody] | TemplateBody)
2152+
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
2153+
* | ‘new’ TemplateBody
21292154
*/
21302155
def newExpr(): Tree =
21312156
indentRegion(NEW) {
21322157
val start = in.skipToken()
21332158
def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset))
21342159
possibleBracesStart()
21352160
val parents =
2136-
if (in.isNestedStart) Nil
2137-
else constrApp() :: {
2138-
if (in.token == WITH) {
2139-
// Enable this for 3.1, when we drop `with` for inheritance:
2140-
// in.errorUnlessInScala2Mode(
2141-
// "anonymous class with multiple parents is no longer supported; use a named class instead")
2142-
in.nextToken()
2143-
tokenSeparated(WITH, constrApp)
2144-
}
2145-
else Nil
2146-
}
2147-
possibleBracesStart()
2161+
if in.token == LBRACE || in.token == WITH then Nil
2162+
else constrApps(commaOK = false, templateCanFollow = true)
2163+
colonAtEOLOpt()
2164+
possibleTemplateStart(isNew = true)
21482165
parents match {
21492166
case parent :: Nil if !in.isNestedStart =>
21502167
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
@@ -3331,7 +3348,7 @@ object Parsers {
33313348
val parents =
33323349
if (in.token == EXTENDS) {
33333350
in.nextToken()
3334-
tokenSeparated(WITH, constrApp)
3351+
constrApps(commaOK = true, templateCanFollow = false)
33353352
}
33363353
else Nil
33373354
Template(constr, parents, Nil, EmptyValDef, Nil)
@@ -3347,12 +3364,20 @@ object Parsers {
33473364
case _ =>
33483365
syntaxError(em"extension clause must start with a single regular parameter", start)
33493366

3367+
def checkExtensionMethod(stat: Tree): Unit = stat match {
3368+
case stat: DefDef =>
3369+
if stat.mods.is(Extension) then
3370+
syntaxError(i"no extension method allowed here since leading parameter was already given", stat.span)
3371+
case _ =>
3372+
syntaxError(i"extension clause can only define methods", stat.span)
3373+
}
33503374

33513375
/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
33523376
* | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
3353-
* | [id ‘:’] [ExtParamClause] TemplateBody
3377+
* | [id ‘:’] ExtParamClause ExtMethods
33543378
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33553379
* ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause}
3380+
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
33563381
*/
33573382
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
33583383
var mods1 = addMod(mods, instanceMod)
@@ -3381,6 +3406,7 @@ object Parsers {
33813406
if in.token == COLON then
33823407
in.nextToken()
33833408
if in.token == LBRACE
3409+
|| in.token == WITH
33843410
|| in.token == LBRACKET
33853411
|| in.token == LPAREN && followingIsParamOrGivenType()
33863412
then
@@ -3393,8 +3419,10 @@ object Parsers {
33933419
syntaxError("`<:' is only allowed for given with `inline' modifier")
33943420
in.nextToken()
33953421
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3396-
else if name.isEmpty && in.token != LBRACE then
3397-
tokenSeparated(COMMA, constrApp)
3422+
else if name.isEmpty
3423+
&& in.token != LBRACE && in.token != WITH
3424+
&& !hasExtensionParams
3425+
then tokenSeparated(COMMA, constrApp)
33983426
else Nil
33993427

34003428
val gdef =
@@ -3407,12 +3435,17 @@ object Parsers {
34073435
case TypeBoundsTree(_, _) :: _ => syntaxError("`=' expected")
34083436
case _ =>
34093437
possibleTemplateStart()
3410-
if !hasExtensionParams then
3438+
if hasExtensionParams then
3439+
in.observeIndented()
3440+
else
34113441
tparams = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
34123442
vparamss = vparamss.map(_.map(vparam =>
34133443
vparam.withMods(vparam.mods &~ Param | ParamAccessor | PrivateLocal)))
34143444
val templ = templateBodyOpt(makeConstructor(tparams, vparamss), parents, Nil)
3415-
if tparams.isEmpty && vparamss.isEmpty || hasExtensionParams then ModuleDef(name, templ)
3445+
if hasExtensionParams then
3446+
templ.body.foreach(checkExtensionMethod)
3447+
ModuleDef(name, templ)
3448+
else if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
34163449
else TypeDef(name.toTypeName, templ)
34173450

34183451
finalizeDef(gdef, mods1, start)
@@ -3429,51 +3462,47 @@ object Parsers {
34293462
if in.token == LPAREN then parArgumentExprss(wrapNew(t)) else t
34303463
}
34313464

3432-
/** ConstrApps ::= ConstrApp {‘with’ ConstrApp} (to be deprecated in 3.1)
3433-
* | ConstrApp {‘,’ ConstrApp}
3465+
/** ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
34343466
*/
3435-
def constrApps(): List[Tree] = {
3467+
def constrApps(commaOK: Boolean, templateCanFollow: Boolean): List[Tree] =
34363468
val t = constrApp()
34373469
val ts =
3438-
if (in.token == WITH) {
3439-
in.nextToken()
3440-
tokenSeparated(WITH, constrApp)
3441-
}
3442-
else if (in.token == COMMA) {
3470+
if in.token == WITH then
3471+
val lookahead = in.LookaheadScanner(indent = true)
3472+
lookahead.nextToken()
3473+
if templateCanFollow && (lookahead.token == LBRACE || lookahead.token == INDENT) then
3474+
Nil
3475+
else
3476+
in.nextToken()
3477+
checkNotWithAtEOL()
3478+
constrApps(commaOK, templateCanFollow)
3479+
else if commaOK && in.token == COMMA then
34433480
in.nextToken()
3444-
tokenSeparated(COMMA, constrApp)
3445-
}
3481+
constrApps(commaOK, templateCanFollow)
34463482
else Nil
34473483
t :: ts
3448-
}
34493484

3450-
/** InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
3485+
/** Template ::= InheritClauses [TemplateBody]
3486+
* InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
34513487
*/
3452-
def inheritClauses(): (List[Tree], List[Tree]) = {
3453-
val extended =
3488+
def template(constr: DefDef, isEnum: Boolean = false): Template = {
3489+
val parents =
34543490
if (in.token == EXTENDS) {
34553491
in.nextToken()
34563492
if (in.token == LBRACE || in.token == COLONEOL) {
34573493
in.errorOrMigrationWarning("`extends' must be followed by at least one parent")
34583494
Nil
34593495
}
3460-
else constrApps()
3496+
else constrApps(commaOK = true, templateCanFollow = true)
34613497
}
34623498
else Nil
3499+
newLinesOptWhenFollowedBy(nme.derives)
34633500
val derived =
34643501
if (isIdent(nme.derives)) {
34653502
in.nextToken()
34663503
tokenSeparated(COMMA, () => convertToTypeId(qualId()))
34673504
}
34683505
else Nil
3469-
(extended, derived)
3470-
}
3471-
3472-
/** Template ::= InheritClauses [TemplateBody]
3473-
*/
3474-
def template(constr: DefDef, isEnum: Boolean = false): Template = {
3475-
newLinesOptWhenFollowedBy(nme.derives)
3476-
val (parents, derived) = inheritClauses()
34773506
possibleTemplateStart()
34783507
if (isEnum) {
34793508
val (self, stats) = withinEnum(templateBody())
@@ -3496,7 +3525,8 @@ object Parsers {
34963525
checkNextNotIndented()
34973526
Template(constr, Nil, Nil, EmptyValDef, Nil)
34983527

3499-
/** TemplateBody ::= [nl] `{' TemplateStatSeq `}'
3528+
/** TemplateBody ::= [nl | `with'] `{' TemplateStatSeq `}'
3529+
* EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
35003530
*/
35013531
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template =
35023532
val (self, stats) =
@@ -3524,7 +3554,7 @@ object Parsers {
35243554
case x: RefTree => atSpan(start, pointOffset(pkg))(PackageDef(x, stats))
35253555
}
35263556

3527-
/** Packaging ::= package QualId [nl] `{' TopStatSeq `}'
3557+
/** Packaging ::= package QualId [nl | `with'] `{' TopStatSeq `}'
35283558
*/
35293559
def packaging(start: Int): Tree = {
35303560
val pkg = qualId()
@@ -3713,23 +3743,23 @@ object Parsers {
37133743
ts ++= topStatSeq()
37143744
}
37153745
}
3716-
else {
3746+
else
37173747
val pkg = qualId()
3748+
var continue = false
37183749
indentRegion(pkg) {
37193750
possibleTemplateStart()
3720-
if (in.token == EOF)
3751+
if in.token == EOF then
37213752
ts += makePackaging(start, pkg, List())
3722-
else if (in.isNestedStart) {
3753+
else if in.isNestedStart then
37233754
ts += inDefScopeBraces(makePackaging(start, pkg, topStatSeq()))
3724-
acceptStatSepUnlessAtEnd()
3725-
ts ++= topStatSeq()
3726-
}
3727-
else {
3755+
continue = true
3756+
else
37283757
acceptStatSep()
37293758
ts += makePackaging(start, pkg, topstats())
3730-
}
37313759
}
3732-
}
3760+
if continue then
3761+
acceptStatSepUnlessAtEnd()
3762+
ts ++= topStatSeq()
37333763
}
37343764
else
37353765
ts ++= topStatSeq()

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,8 +869,8 @@ object Scanners {
869869

870870
// Lookahead ---------------------------------------------------------------
871871

872-
class LookaheadScanner extends Scanner(source, offset) {
873-
override val indentSyntax = false
872+
class LookaheadScanner(indent: Boolean = false) extends Scanner(source, offset) {
873+
override val indentSyntax = indent
874874
override protected def printState() = {
875875
print("la:")
876876
super.printState()

0 commit comments

Comments
 (0)