Skip to content

Commit 1f377c1

Browse files
committed
Allow with at end of class, object, ... headers
... and make indentation significant after it.
1 parent 69750c8 commit 1f377c1

File tree

5 files changed

+108
-137
lines changed

5 files changed

+108
-137
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 silentTemplateIdent = true
165+
163166
/** If set, prints a trace of all symbol completions */
164167
final val showCompletions = false
165168

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

Lines changed: 39 additions & 30 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.silentTemplateIdent
2930

3031
object Parsers {
3132

@@ -616,6 +617,7 @@ object Parsers {
616617

617618
/** If indentation is not significant, check that this is not the start of a
618619
* statement that's indented relative to the current region.
620+
* TODO: Drop if `with` is required before indented template definitions.
619621
*/
620622
def checkNextNotIndented(): Unit = in.currentRegion match
621623
case r: IndentSignificantRegion if in.isNewLine =>
@@ -1249,10 +1251,14 @@ object Parsers {
12491251
newLineOptWhenFollowedBy(LBRACE)
12501252
}
12511253

1252-
def possibleTemplateStart(): Unit = {
1253-
in.observeIndented()
1254+
def possibleTemplateStart(): Unit =
1255+
if in.token == WITH then
1256+
in.nextToken()
1257+
if in.token != LBRACE && in.token != INDENT then
1258+
syntaxError(i"indented definitions or `{' expected")
1259+
else if silentTemplateIdent then
1260+
in.observeIndented()
12541261
newLineOptWhenFollowedBy(LBRACE)
1255-
}
12561262

12571263
def indentRegion[T](tag: EndMarkerTag)(op: => T): T = {
12581264
val iw = in.currentRegion.indentWidth
@@ -2125,25 +2131,20 @@ object Parsers {
21252131
}
21262132
}
21272133

2128-
/** SimpleExpr ::= ‘new’ (ConstrApp {`with` ConstrApp} [TemplateBody] | TemplateBody)
2134+
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
2135+
* | ‘new’ TemplateBody
21292136
*/
21302137
def newExpr(): Tree =
21312138
indentRegion(NEW) {
21322139
val start = in.skipToken()
21332140
def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset))
21342141
possibleBracesStart()
21352142
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-
}
2143+
if in.token == WITH then
2144+
possibleTemplateStart()
2145+
Nil
2146+
else if in.token == LBRACE then Nil
2147+
else constrApps(commaOK = false, templateCanFollow = true)
21472148
possibleBracesStart()
21482149
parents match {
21492150
case parent :: Nil if !in.isNestedStart =>
@@ -3325,7 +3326,7 @@ object Parsers {
33253326
val parents =
33263327
if (in.token == EXTENDS) {
33273328
in.nextToken()
3328-
tokenSeparated(WITH, constrApp)
3329+
constrApps(commaOK = true, templateCanFollow = false)
33293330
}
33303331
else Nil
33313332
Template(constr, parents, Nil, EmptyValDef, Nil)
@@ -3375,6 +3376,7 @@ object Parsers {
33753376
if in.token == COLON then
33763377
in.nextToken()
33773378
if in.token == LBRACE
3379+
|| in.token == WITH
33783380
|| in.token == LBRACKET
33793381
|| in.token == LPAREN && followingIsParamOrGivenType()
33803382
then
@@ -3387,7 +3389,7 @@ object Parsers {
33873389
syntaxError("`<:' is only allowed for given with `inline' modifier")
33883390
in.nextToken()
33893391
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3390-
else if name.isEmpty && in.token != LBRACE then
3392+
else if name.isEmpty && in.token != LBRACE && in.token != WITH then
33913393
tokenSeparated(COMMA, constrApp)
33923394
else Nil
33933395

@@ -3423,23 +3425,29 @@ object Parsers {
34233425
if in.token == LPAREN then parArgumentExprss(wrapNew(t)) else t
34243426
}
34253427

3426-
/** ConstrApps ::= ConstrApp {‘with’ ConstrApp} (to be deprecated in 3.1)
3427-
* | ConstrApp {‘,’ ConstrApp}
3428+
/** ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
34283429
*/
3429-
def constrApps(): List[Tree] = {
3430+
def constrApps(commaOK: Boolean, templateCanFollow: Boolean): List[Tree] =
34303431
val t = constrApp()
34313432
val ts =
3432-
if (in.token == WITH) {
3433+
if in.token == WITH then
34333434
in.nextToken()
3434-
tokenSeparated(WITH, constrApp)
3435-
}
3436-
else if (in.token == COMMA) {
3435+
if templateCanFollow && (in.token == LBRACE || in.token == INDENT) then Nil
3436+
else
3437+
if (in.isScala2Mode || in.oldSyntax) && in.isAfterLineEnd then
3438+
// Disallow
3439+
//
3440+
// extends p1 with
3441+
// p2
3442+
//
3443+
// since that means something else under significant indentation
3444+
in.errorOrMigrationWarning("`with` cannot be followed by new line, place at beginning of next line instead")
3445+
constrApps(commaOK, templateCanFollow)
3446+
else if commaOK && in.token == COMMA then
34373447
in.nextToken()
3438-
tokenSeparated(COMMA, constrApp)
3439-
}
3448+
constrApps(commaOK, templateCanFollow)
34403449
else Nil
34413450
t :: ts
3442-
}
34433451

34443452
/** InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
34453453
*/
@@ -3451,7 +3459,7 @@ object Parsers {
34513459
in.errorOrMigrationWarning("`extends' must be followed by at least one parent")
34523460
Nil
34533461
}
3454-
else constrApps()
3462+
else constrApps(commaOK = true, templateCanFollow = true)
34553463
}
34563464
else Nil
34573465
val derived =
@@ -3490,7 +3498,8 @@ object Parsers {
34903498
checkNextNotIndented()
34913499
Template(constr, Nil, Nil, EmptyValDef, Nil)
34923500

3493-
/** TemplateBody ::= [nl] `{' TemplateStatSeq `}'
3501+
/** TemplateBody ::= [nl | `with'] `{' TemplateStatSeq `}'
3502+
* EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
34943503
*/
34953504
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template =
34963505
val (self, stats) =
@@ -3518,7 +3527,7 @@ object Parsers {
35183527
case x: RefTree => atSpan(start, pointOffset(pkg))(PackageDef(x, stats))
35193528
}
35203529

3521-
/** Packaging ::= package QualId [nl] `{' TopStatSeq `}'
3530+
/** Packaging ::= package QualId [nl | `with'] `{' TopStatSeq `}'
35223531
*/
35233532
def packaging(start: Int): Tree = {
35243533
val pkg = qualId()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ object Tokens extends TokensCommon {
270270
final val closingRegionTokens = BitSet(RBRACE, CASE) | statCtdTokens
271271

272272
final val canStartIndentTokens: BitSet =
273-
statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR, RETURN, IF)
273+
statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR, RETURN, IF, WITH)
274274
// `if` is excluded because it often comes after `else` which makes for awkward indentation rules TODO: try to do without the exception
275275

276276
/** Faced with the choice between a type and a formal parameter, the following

docs/docs/internals/syntax.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ SimpleExpr ::= Path
219219
| ‘$’ ‘{’ Block ‘}’
220220
| Quoted
221221
| quoteId // only inside splices
222-
| ‘new’ (ConstrApp [TemplateBody] | TemplateBody) New(constr | templ)
222+
| ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody] New(constr | templ)
223+
| ‘new’ TemplateBody
223224
| ‘(’ ExprsInParens ‘)’ Parens(exprs)
224225
| SimpleExpr ‘.’ id Select(expr, id)
225226
| SimpleExpr TypeArgs TypeApply(expr, args)
@@ -393,14 +394,14 @@ GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
393394
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
394395
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
395396
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
396-
ConstrApps ::= ConstrApp {‘with’ ConstrApp}
397-
| ConstrApp {‘,’ ConstrApp}
397+
ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
398398
ConstrApp ::= AnnotType {ParArgumentExprs} Apply(tp, args)
399399
ConstrExpr ::= SelfInvocation
400400
| ‘{’ SelfInvocation {semi BlockStat} ‘}’
401401
SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs}
402402
403-
TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats)
403+
TemplateBody ::= [nl | ‘with’]
404+
‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats)
404405
TemplateStat ::= Import
405406
| Export
406407
| {Annotation [nl]} {Modifier} Def
@@ -410,7 +411,7 @@ TemplateStat ::= Import
410411
SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _)
411412
| ‘this’ ‘:’ InfixType ‘=>’
412413
413-
EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
414+
EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
414415
EnumStat ::= TemplateStat
415416
| {Annotation [nl]} {Modifier} EnumCase
416417
EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids)
@@ -422,7 +423,7 @@ TopStat ::= Import
422423
| Packaging
423424
| PackageObject
424425
|
425-
Packaging ::= ‘package’ QualId [nl] ‘{’ TopStatSeq ‘}’ Package(qid, stats)
426+
Packaging ::= ‘package’ QualId [nl | ‘with’] ‘{’ TopStatSeq ‘}’ Package(qid, stats)
426427
PackageObject ::= ‘package’ ‘object’ ObjectDef object with package in mods.
427428
428429
CompilationUnit ::= {‘package’ QualId (semi | cnl)} TopStatSeq Package(qid, stats)

0 commit comments

Comments
 (0)