Skip to content

Commit 26398f4

Browse files
authored
Merge pull request #11157 from dotty-staging/with-templates-3
Allow `with` after class
2 parents b3aab16 + 1173fcc commit 26398f4

File tree

341 files changed

+1122
-985
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

341 files changed

+1122
-985
lines changed

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

Lines changed: 144 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ object Parsers {
196196
def isTemplateIntro = templateIntroTokens contains in.token
197197
def isDclIntro = dclIntroTokens contains in.token
198198
def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN
199+
def isTemplateBodyStart = in.token == WITH || in.isNestedStart
199200
def mustStartStat = mustStartStatTokens contains in.token
200201

201202
/** Is current token a hard or soft modifier (in modifier position or not)? */
@@ -919,6 +920,53 @@ object Parsers {
919920
val next = in.lookahead.token
920921
next == LBRACKET || next == LPAREN
921922

923+
private def withEndMigrationWarning(): Boolean =
924+
migrateTo3
925+
&& {
926+
warning(
927+
em"""In Scala 3, `with` at the end of a line will start definitions,
928+
|so it cannot be used in front of a parent constructor anymore.
929+
|Place the `with` at the beginning of the next line instead.""")
930+
true
931+
}
932+
933+
/** Does a template start after `with`? This is the case if either
934+
* - the next token is `{`
935+
* - the `with` is at the end of a line
936+
* (except for source = 3.0-migration, when a warning is issued)
937+
* - the next tokens is `<ident>` or `this` and the one after it is `:` or `=>`
938+
* (i.e. we see the start of a self type)
939+
*/
940+
def followingIsTemplateStart() =
941+
val lookahead = in.LookaheadScanner()
942+
lookahead.nextToken()
943+
lookahead.token == LBRACE
944+
|| lookahead.isAfterLineEnd && !withEndMigrationWarning()
945+
|| (lookahead.isIdent || lookahead.token == THIS)
946+
&& {
947+
lookahead.nextToken()
948+
lookahead.token == COLON
949+
&& { // needed only as long as we support significant colon at eol
950+
lookahead.nextToken()
951+
!lookahead.isAfterLineEnd
952+
}
953+
|| lookahead.token == ARROW
954+
}
955+
956+
/** Does a refinement start after `with`? This is the case if either
957+
* - the next token is `{`
958+
* - the `with` is at the end of a line and is followed by a token that starts a declaration
959+
*/
960+
def followingIsRefinementStart() =
961+
val lookahead = in.LookaheadScanner()
962+
lookahead.nextToken()
963+
lookahead.token == LBRACE
964+
|| lookahead.isAfterLineEnd
965+
&& {
966+
if lookahead.token == INDENT then lookahead.nextToken()
967+
dclIntroTokens.contains(lookahead.token)
968+
}
969+
922970
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
923971

924972
var opStack: List[OpInfo] = Nil
@@ -1281,14 +1329,14 @@ object Parsers {
12811329
in.sourcePos())
12821330
patch(source, Span(in.offset), " ")
12831331

1284-
def possibleTemplateStart(isNew: Boolean = false): Unit =
1332+
def possibleTemplateStart(): Unit =
12851333
in.observeColonEOL()
1286-
if in.token == COLONEOL || in.token == WITH then
1334+
if in.token == COLONEOL then
12871335
if in.lookahead.isIdent(nme.end) then in.token = NEWLINE
12881336
else
12891337
in.nextToken()
12901338
if in.token != INDENT && in.token != LBRACE then
1291-
syntaxErrorOrIncomplete(i"indented definitions expected, ${in}")
1339+
syntaxErrorOrIncomplete(ExpectedTokenButFound(INDENT, in.token))
12921340
else
12931341
newLineOptWhenFollowedBy(LBRACE)
12941342

@@ -1532,33 +1580,29 @@ object Parsers {
15321580
def infixTypeRest(t: Tree): Tree =
15331581
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)
15341582

1535-
/** RefinedType ::= WithType {[nl] Refinement}
1583+
/** RefinedType ::= WithType {[nl | ‘with’] Refinement}
15361584
*/
15371585
val refinedType: () => Tree = () => refinedTypeRest(withType())
15381586

1539-
def refinedTypeRest(t: Tree): Tree = {
1587+
def refinedTypeRest(t: Tree): Tree =
15401588
argumentStart()
1541-
if (in.isNestedStart)
1589+
if isTemplateBodyStart then
1590+
if in.token == WITH then in.nextToken()
15421591
refinedTypeRest(atSpan(startOffset(t)) {
15431592
RefinedTypeTree(rejectWildcardType(t), refinement(indentOK = true))
15441593
})
15451594
else t
1546-
}
15471595

15481596
/** WithType ::= AnnotType {`with' AnnotType} (deprecated)
15491597
*/
15501598
def withType(): Tree = withTypeRest(annotType())
15511599

15521600
def withTypeRest(t: Tree): Tree =
1553-
if in.token == WITH then
1554-
val withOffset = in.offset
1555-
in.nextToken()
1556-
if in.token == LBRACE || in.token == INDENT then
1557-
t
1558-
else
1559-
if sourceVersion.isAtLeast(`3.1`) then
1560-
deprecationWarning(DeprecatedWithOperator(), withOffset)
1561-
atSpan(startOffset(t)) { makeAndType(t, withType()) }
1601+
if in.token == WITH && !followingIsRefinementStart() then
1602+
in.nextTokenNoIndent()
1603+
if sourceVersion.isAtLeast(`3.1`) then
1604+
deprecationWarning(DeprecatedWithOperator())
1605+
atSpan(startOffset(t)) { makeAndType(t, withType()) }
15621606
else t
15631607

15641608
/** AnnotType ::= SimpleType {Annotation}
@@ -2313,13 +2357,11 @@ object Parsers {
23132357
val start = in.skipToken()
23142358
def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset))
23152359
possibleTemplateStart()
2316-
val parents =
2317-
if in.isNestedStart then Nil
2318-
else constrApps(commaOK = false)
2360+
val parents = if isTemplateBodyStart then Nil else constrApp() :: withConstrApps()
23192361
colonAtEOLOpt()
2320-
possibleTemplateStart(isNew = true)
2362+
possibleTemplateStart()
23212363
parents match {
2322-
case parent :: Nil if !in.isNestedStart =>
2364+
case parent :: Nil if !isTemplateBodyStart =>
23232365
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
23242366
case _ =>
23252367
New(reposition(templateBodyOpt(emptyConstructor, parents, Nil)))
@@ -3494,7 +3536,7 @@ object Parsers {
34943536
val parents =
34953537
if (in.token == EXTENDS) {
34963538
in.nextToken()
3497-
constrApps(commaOK = true)
3539+
constrApps()
34983540
}
34993541
else Nil
35003542
Template(constr, parents, Nil, EmptyValDef, Nil)
@@ -3517,7 +3559,7 @@ object Parsers {
35173559
syntaxError(i"extension clause can only define methods", stat.span)
35183560
}
35193561

3520-
/** GivenDef ::= [GivenSig] (Type [‘=’ Expr] | StructuralInstance)
3562+
/** GivenDef ::= [GivenSig] (AnnotType [‘=’ Expr] | ConstrApps TemplateBody)
35213563
* GivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
35223564
*/
35233565
def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) {
@@ -3536,9 +3578,10 @@ object Parsers {
35363578
val noParams = tparams.isEmpty && vparamss.isEmpty
35373579
if !(name.isEmpty && noParams) then accept(COLON)
35383580
val parents =
3539-
if isSimpleLiteral then toplevelTyp() :: Nil
3540-
else constrApp() :: withConstrApps()
3581+
if isSimpleLiteral then rejectWildcardType(annotType()) :: Nil
3582+
else constrApps()
35413583
val parentsIsType = parents.length == 1 && parents.head.isType
3584+
newLineOptWhenFollowedBy(LBRACE)
35423585
if in.token == EQUALS && parentsIsType then
35433586
accept(EQUALS)
35443587
mods1 |= Final
@@ -3547,17 +3590,17 @@ object Parsers {
35473590
ValDef(name, parents.head, subExpr())
35483591
else
35493592
DefDef(name, joinParams(tparams, vparamss), parents.head, subExpr())
3550-
else if in.token != WITH && parentsIsType then
3551-
if name.isEmpty then
3552-
syntaxError(em"anonymous given cannot be abstract")
3553-
DefDef(name, joinParams(tparams, vparamss), parents.head, EmptyTree)
3554-
else
3593+
else if isTemplateBodyStart then
35553594
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
35563595
val vparamss1 = vparamss.map(_.map(vparam =>
35573596
vparam.withMods(vparam.mods &~ Param | ParamAccessor | Protected)))
3558-
val templ = withTemplate(makeConstructor(tparams1, vparamss1), parents)
3597+
val templ = templateBodyOpt(makeConstructor(tparams1, vparamss1), parents, Nil)
35593598
if noParams then ModuleDef(name, templ)
35603599
else TypeDef(name.toTypeName, templ)
3600+
else
3601+
if name.isEmpty then
3602+
syntaxError(em"anonymous given cannot be abstract")
3603+
DefDef(name, joinParams(tparams, vparamss), parents.head, EmptyTree)
35613604
end gdef
35623605
finalizeDef(gdef, mods1, start)
35633606
}
@@ -3576,8 +3619,11 @@ object Parsers {
35763619
isUsingClause(extParams)
35773620
do ()
35783621
leadParamss ++= paramClauses(givenOnly = true, numLeadParams = nparams)
3579-
if in.token == COLON then
3580-
syntaxError("no `:` expected here")
3622+
if in.token == WITH then
3623+
syntaxError(
3624+
i"""No `with` expected here.
3625+
|
3626+
|An extension clause is simply followed by one or more method definitions.""")
35813627
in.nextToken()
35823628
val methods =
35833629
if isDefIntro(modifierTokens) then
@@ -3626,25 +3672,24 @@ object Parsers {
36263672
// Using Ident(tpnme.ERROR) to avoid causing cascade errors on non-user-written code
36273673
if in.token == LPAREN then parArgumentExprss(wrapNew(t)) else t
36283674

3629-
/** ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
3675+
/** ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp})
36303676
*/
3631-
def constrApps(commaOK: Boolean): List[Tree] =
3677+
def constrApps(): List[Tree] =
36323678
val t = constrApp()
3633-
val ts =
3634-
if in.token == WITH || commaOK && in.token == COMMA then
3635-
in.nextToken()
3636-
constrApps(commaOK)
3637-
else Nil
3679+
val ts = if in.token == COMMA then commaConstrApps() else withConstrApps()
36383680
t :: ts
36393681

3682+
/** `{`,` ConstrApp} */
3683+
def commaConstrApps(): List[Tree] =
3684+
if in.token == COMMA then
3685+
in.nextToken()
3686+
constrApp() :: commaConstrApps()
3687+
else Nil
36403688

36413689
/** `{`with` ConstrApp} but no EOL allowed after `with`.
36423690
*/
36433691
def withConstrApps(): List[Tree] =
3644-
def isTemplateStart =
3645-
val la = in.lookahead
3646-
la.isAfterLineEnd || la.token == LBRACE
3647-
if in.token == WITH && !isTemplateStart then
3692+
if in.token == WITH && !followingIsTemplateStart() then
36483693
in.nextToken()
36493694
constrApp() :: withConstrApps()
36503695
else Nil
@@ -3662,7 +3707,7 @@ object Parsers {
36623707
in.sourcePos())
36633708
Nil
36643709
}
3665-
else constrApps(commaOK = true)
3710+
else constrApps()
36663711
}
36673712
else Nil
36683713
newLinesOptWhenFollowedBy(nme.derives)
@@ -3688,39 +3733,65 @@ object Parsers {
36883733
template(constr)
36893734
else
36903735
possibleTemplateStart()
3691-
if in.isNestedStart then
3736+
if isTemplateBodyStart then
36923737
template(constr)
36933738
else
36943739
checkNextNotIndented()
36953740
Template(constr, Nil, Nil, EmptyValDef, Nil)
36963741

3697-
/** TemplateBody ::= [nl] `{' TemplateStatSeq `}'
3698-
* EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
3742+
/** TemplateBody ::= [nl | ‘with’] `{' TemplateStatSeq `}'
3743+
* | ‘with’ [SelfType] indent TemplateStats outdent
3744+
* EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStats ‘}’
3745+
* | ‘with’ [SelfType] indent EnumStats outdent
36993746
*/
37003747
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template =
37013748
val (self, stats) =
3702-
if in.isNestedStart then
3749+
if isTemplateBodyStart then
37033750
templateBody()
37043751
else
37053752
checkNextNotIndented()
37063753
(EmptyValDef, Nil)
37073754
Template(constr, parents, derived, self, stats)
37083755

37093756
def templateBody(): (ValDef, List[Tree]) =
3710-
val r = inDefScopeBraces(templateStatSeq(), rewriteWithColon = true)
3757+
val givenSelf =
3758+
if in.token == WITH then
3759+
in.nextToken()
3760+
selfDefOpt()
3761+
else EmptyValDef
3762+
val r = inDefScopeBraces(templateStatSeq(givenSelf), rewriteWithColon = true)
37113763
if in.token == WITH then
37123764
syntaxError(EarlyDefinitionsNotSupported())
37133765
in.nextToken()
37143766
template(emptyConstructor)
37153767
r
37163768

3717-
/** with Template, with EOL <indent> interpreted */
3718-
def withTemplate(constr: DefDef, parents: List[Tree]): Template =
3719-
if in.token != WITH then syntaxError(em"`with` expected")
3720-
possibleTemplateStart() // consumes a WITH token
3721-
val (self, stats) = templateBody()
3722-
Template(constr, parents, Nil, self, stats)
3723-
.withSpan(Span(constr.span.orElse(parents.head.span).start, in.lastOffset))
3769+
/** SelfType ::= id [‘:’ InfixType] ‘=>’
3770+
* | ‘this’ ‘:’ InfixType ‘=>’
3771+
* Only called immediately after a `with`, in which case it must in turn
3772+
* be followed by `INDENT`.
3773+
*/
3774+
def selfDefOpt(): ValDef = atSpan(in.offset) {
3775+
val vd =
3776+
if in.isIdent then
3777+
val selfName = ident()
3778+
if in.token == COLON then
3779+
in.nextToken()
3780+
makeSelfDef(selfName, infixType())
3781+
else
3782+
makeSelfDef(selfName, TypeTree())
3783+
else if in.token == THIS then
3784+
in.nextToken()
3785+
accept(COLON)
3786+
makeSelfDef(nme.WILDCARD, infixType())
3787+
else
3788+
EmptyValDef
3789+
if !vd.isEmpty then
3790+
accept(ARROW)
3791+
if in.token != INDENT then
3792+
syntaxErrorOrIncomplete(ExpectedTokenButFound(INDENT, in.token))
3793+
vd
3794+
}
37243795

37253796
/* -------- STATSEQS ------------------------------------------- */
37263797

@@ -3787,10 +3858,10 @@ object Parsers {
37873858
* EnumStat ::= TemplateStat
37883859
* | Annotations Modifiers EnumCase
37893860
*/
3790-
def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
3791-
var self: ValDef = EmptyValDef
3861+
def templateStatSeq(givenSelf: ValDef = EmptyValDef): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
3862+
var self = givenSelf
37923863
val stats = new ListBuffer[Tree]
3793-
if (isExprIntro && !isDefIntro(modifierTokens)) {
3864+
if (self.isEmpty && isExprIntro && !isDefIntro(modifierTokens)) {
37943865
val first = expr1()
37953866
if (in.token == ARROW) {
37963867
first match {
@@ -3801,12 +3872,20 @@ object Parsers {
38013872
if (name != nme.ERROR)
38023873
self = makeSelfDef(name, tpt).withSpan(first.span)
38033874
}
3804-
in.token = SELFARROW // suppresses INDENT insertion after `=>`
3805-
in.nextToken()
3875+
in.nextTokenNoIndent()
38063876
}
38073877
else {
38083878
stats += first
3809-
acceptStatSepUnlessAtEnd(stats)
3879+
if in.token == WITH then
3880+
syntaxError(
3881+
i"""end of statement expected but ${showToken(WITH)} found
3882+
|
3883+
|Maybe you meant to write a mixin in an extends clause?
3884+
|Note that this requires the `with` to come first now.
3885+
|I.e.
3886+
|
3887+
| with $first""")
3888+
else acceptStatSepUnlessAtEnd(stats)
38103889
}
38113890
}
38123891
var exitOnError = false
@@ -3934,7 +4013,7 @@ object Parsers {
39344013
possibleTemplateStart()
39354014
if in.token == EOF then
39364015
ts += makePackaging(start, pkg, List())
3937-
else if in.isNestedStart then
4016+
else if isTemplateBodyStart then
39384017
ts += inDefScopeBraces(makePackaging(start, pkg, topStatSeq()), rewriteWithColon = true)
39394018
continue = true
39404019
else
@@ -3972,6 +4051,7 @@ object Parsers {
39724051
}
39734052

39744053
override def templateBody(): (ValDef, List[Thicket]) = {
4054+
if in.token == WITH then in.nextToken()
39754055
skipBraces()
39764056
(EmptyValDef, List(EmptyTree))
39774057
}

0 commit comments

Comments
 (0)