Skip to content

Commit 0229d05

Browse files
committed
Allow self types after with
1 parent 13e75d8 commit 0229d05

File tree

7 files changed

+131
-40
lines changed

7 files changed

+131
-40
lines changed

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

Lines changed: 85 additions & 32 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,39 @@ object Parsers {
919920
val next = in.lookahead.token
920921
next == LBRACKET || next == LPAREN
921922

923+
/** Does a template start after `with`? This is the case if either
924+
* - the next token is `{`
925+
* - the `with` is at the end of a line
926+
* (except for source = 3.0-migration, when a warning is issued)
927+
* - the next tokens is `<ident>` or `this` and the one after it is `:` or `=>`
928+
* (i.e. we see the start of a self type)
929+
*/
930+
def followingIsTemplateStart() =
931+
val lookahead = in.LookaheadScanner()
932+
lookahead.nextToken()
933+
lookahead.token == LBRACE
934+
|| lookahead.isAfterLineEnd
935+
&& {
936+
if migrateTo3 then
937+
warning(
938+
em"""In Scala 3, `with` at the end of a line will start definitions,
939+
|so it cannot be used in front of a parent constructor anymore.
940+
|Place the `with` at the beginning of the next line instead.""")
941+
false
942+
else
943+
true
944+
}
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+
922956
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
923957

924958
var opStack: List[OpInfo] = Nil
@@ -1281,14 +1315,14 @@ object Parsers {
12811315
in.sourcePos())
12821316
patch(source, Span(in.offset), " ")
12831317

1284-
def possibleTemplateStart(isNew: Boolean = false): Unit =
1318+
def possibleTemplateStart(): Unit =
12851319
in.observeColonEOL()
1286-
if in.token == COLONEOL || in.token == WITH then
1320+
if in.token == COLONEOL then
12871321
if in.lookahead.isIdent(nme.end) then in.token = NEWLINE
12881322
else
12891323
in.nextToken()
12901324
if in.token != INDENT && in.token != LBRACE then
1291-
syntaxErrorOrIncomplete(i"indented definitions expected, ${in} found")
1325+
syntaxErrorOrIncomplete(ExpectedTokenButFound(INDENT, in.token))
12921326
else
12931327
newLineOptWhenFollowedBy(LBRACE)
12941328

@@ -2313,13 +2347,11 @@ object Parsers {
23132347
val start = in.skipToken()
23142348
def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset))
23152349
possibleTemplateStart()
2316-
val parents =
2317-
if in.isNestedStart then Nil
2318-
else constrApp() :: withConstrApps()
2350+
val parents = if isTemplateBodyStart then Nil else constrApp() :: withConstrApps()
23192351
colonAtEOLOpt()
2320-
possibleTemplateStart(isNew = true)
2352+
possibleTemplateStart()
23212353
parents match {
2322-
case parent :: Nil if !in.isNestedStart =>
2354+
case parent :: Nil if !isTemplateBodyStart =>
23232355
reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent)
23242356
case _ =>
23252357
New(reposition(templateBodyOpt(emptyConstructor, parents, Nil)))
@@ -3643,21 +3675,7 @@ object Parsers {
36433675
/** `{`with` ConstrApp} but no EOL allowed after `with`.
36443676
*/
36453677
def withConstrApps(): List[Tree] =
3646-
def isTemplateStart =
3647-
val la = in.lookahead
3648-
la.token == LBRACE
3649-
|| la.isAfterLineEnd
3650-
&& {
3651-
if migrateTo3 then
3652-
warning(
3653-
em"""In Scala 3, `with` at the end of a line will start definitions,
3654-
|so it cannot be used in front of a parent constructor anymore.
3655-
|Place the `with` at the beginning of the next line instead.""")
3656-
false
3657-
else
3658-
true
3659-
}
3660-
if in.token == WITH && !isTemplateStart then
3678+
if in.token == WITH && !followingIsTemplateStart() then
36613679
in.nextToken()
36623680
constrApp() :: withConstrApps()
36633681
else Nil
@@ -3701,32 +3719,66 @@ object Parsers {
37013719
template(constr)
37023720
else
37033721
possibleTemplateStart()
3704-
if in.isNestedStart then
3722+
if isTemplateBodyStart then
37053723
template(constr)
37063724
else
37073725
checkNextNotIndented()
37083726
Template(constr, Nil, Nil, EmptyValDef, Nil)
37093727

3710-
/** TemplateBody ::= [nl] `{' TemplateStatSeq `}'
3711-
* EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
3728+
/** TemplateBody ::= [nl | ‘with’] `{' TemplateStatSeq `}'
3729+
* | ‘with’ [SelfType] indent TemplateStats outdent
3730+
* EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStats ‘}’
3731+
* | ‘with’ [SelfType] indent EnumStats outdent
37123732
*/
37133733
def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template =
37143734
val (self, stats) =
3715-
if in.isNestedStart then
3735+
if isTemplateBodyStart then
37163736
templateBody()
37173737
else
37183738
checkNextNotIndented()
37193739
(EmptyValDef, Nil)
37203740
Template(constr, parents, derived, self, stats)
37213741

37223742
def templateBody(): (ValDef, List[Tree]) =
3723-
val r = inDefScopeBraces(templateStatSeq(), rewriteWithColon = true)
3743+
val givenSelf =
3744+
if in.token == WITH then
3745+
in.nextToken()
3746+
selfDefOpt()
3747+
else EmptyValDef
3748+
val r = inDefScopeBraces(templateStatSeq(givenSelf), rewriteWithColon = true)
37243749
if in.token == WITH then
37253750
syntaxError(EarlyDefinitionsNotSupported())
37263751
in.nextToken()
37273752
template(emptyConstructor)
37283753
r
37293754

3755+
/** SelfType ::= id [‘:’ InfixType] ‘=>’
3756+
* | ‘this’ ‘:’ InfixType ‘=>’
3757+
* Only called immediately after a `with`, in which case it must in turn
3758+
* be followed by `INDENT`.
3759+
*/
3760+
def selfDefOpt(): ValDef = atSpan(in.offset) {
3761+
val vd =
3762+
if in.isIdent then
3763+
val selfName = ident()
3764+
if in.token == COLON then
3765+
in.nextToken()
3766+
makeSelfDef(selfName, infixType())
3767+
else
3768+
makeSelfDef(selfName, TypeTree())
3769+
else if in.token == THIS then
3770+
in.nextToken()
3771+
accept(COLON)
3772+
makeSelfDef(nme.WILDCARD, infixType())
3773+
else
3774+
EmptyValDef
3775+
if !vd.isEmpty then
3776+
accept(ARROW)
3777+
if in.token != INDENT then
3778+
syntaxErrorOrIncomplete(ExpectedTokenButFound(INDENT, in.token))
3779+
vd
3780+
}
3781+
37303782
/** with Template, with EOL <indent> interpreted */
37313783
def withTemplate(constr: DefDef, parents: List[Tree]): Template =
37323784
if in.token != WITH then syntaxError(em"`with` expected")
@@ -3800,10 +3852,10 @@ object Parsers {
38003852
* EnumStat ::= TemplateStat
38013853
* | Annotations Modifiers EnumCase
38023854
*/
3803-
def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
3804-
var self: ValDef = EmptyValDef
3855+
def templateStatSeq(givenSelf: ValDef = EmptyValDef): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
3856+
var self = givenSelf
38053857
val stats = new ListBuffer[Tree]
3806-
if (isExprIntro && !isDefIntro(modifierTokens)) {
3858+
if (self.isEmpty && isExprIntro && !isDefIntro(modifierTokens)) {
38073859
val first = expr1()
38083860
if (in.token == ARROW) {
38093861
first match {
@@ -3956,7 +4008,7 @@ object Parsers {
39564008
possibleTemplateStart()
39574009
if in.token == EOF then
39584010
ts += makePackaging(start, pkg, List())
3959-
else if in.isNestedStart then
4011+
else if isTemplateBodyStart then
39604012
ts += inDefScopeBraces(makePackaging(start, pkg, topStatSeq()), rewriteWithColon = true)
39614013
continue = true
39624014
else
@@ -3994,6 +4046,7 @@ object Parsers {
39944046
}
39954047

39964048
override def templateBody(): (ValDef, List[Thicket]) = {
4049+
if in.token == WITH then in.nextToken()
39974050
skipBraces()
39984051
(EmptyValDef, List(EmptyTree))
39994052
}

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,7 @@ import transform.SymUtils._
11261126
def msg =
11271127
val expectedText =
11281128
if (Tokens.isIdentifier(expected)) "an identifier"
1129+
else if expected == Tokens.INDENT then "indented definitions"
11291130
else Tokens.showToken(expected)
11301131
em"""${expectedText} expected, but ${foundText} found"""
11311132

docs/docs/internals/syntax.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’
148148
TypedFunParam ::= id ‘:’ Type
149149
MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’
150150
InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2)
151-
RefinedType ::= WithType {[nl] Refinement} RefinedTypeTree(t, ds)
151+
RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds)
152152
WithType ::= AnnotType {‘with’ AnnotType} (deprecated)
153153
AnnotType ::= SimpleType {Annotation} Annotated(t, annot)
154154
@@ -401,6 +401,7 @@ ConstrExpr ::= SelfInvocation
401401
SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs}
402402
403403
TemplateBody ::= [nl | ‘with’] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’
404+
| ‘with’ [SelfType] indent TemplateStats outdent
404405
TemplateStat ::= Import
405406
| Export
406407
| {Annotation [nl]} {Modifier} Def
@@ -412,7 +413,9 @@ TemplateStat ::= Import
412413
SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _)
413414
| ‘this’ ‘:’ InfixType ‘=>’
414415
415-
EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
416+
EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStats ‘}’
417+
| ‘with’ [SelfType] indent EnumStats outdent
418+
EnumStats ::= EnumStat {semi EnumStat}
416419
EnumStat ::= TemplateStat
417420
| {Annotation [nl]} {Modifier} EnumCase
418421
EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids)

tests/neg/with-template.check

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- Error: tests/neg/with-template.scala:6:5 ----------------------------------------------------------------------------
2+
6 | B with // error // error this one allows an informative hint
3+
| ^^^^
4+
| end of statement expected but 'with' found
5+
|
6+
| Maybe you meant to write a mixin in an extends clause?
7+
| Note that this requires the `with` to come first now.
8+
| I.e.
9+
|
10+
| with B
11+
-- [E006] Not Found Error: tests/neg/with-template.scala:6:2 -----------------------------------------------------------
12+
6 | B with // error // error this one allows an informative hint
13+
| ^
14+
| Not found: B
15+
16+
longer explanation available when compiling with `-explain`
17+
-- [E006] Not Found Error: tests/neg/with-template.scala:10:2 ----------------------------------------------------------
18+
10 | B { // error this one is harder since it is syntactically correct
19+
| ^
20+
| Not found: B
21+
22+
longer explanation available when compiling with `-explain`

tests/neg/with-template.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class A
2+
trait B
3+
trait C
4+
5+
object Test extends A with
6+
B with // error // error this one allows an informative hint
7+
C
8+
9+
object Test2 extends A with
10+
B { // error this one is harder since it is syntactically correct
11+
println("foo")
12+
}
13+
14+

tests/pos/i10080.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
trait Foo with
2-
end Foo
3-
41
trait Bar
52
end Bar

tests/pos/indent.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ object Test with
8282
x < 10
8383
do ()
8484

85-
class Test2 with
86-
self =>
85+
class Test2 with self: Test2 =>
8786
def foo(x: Int) =
8887
if x < 0 then throw
8988
val ex = new AssertionError()
@@ -98,9 +97,11 @@ class Test2 with
9897
end Test2
9998

10099
class Test3 with
101-
self =>
100+
self =>
102101
def foo = 1
103102

103+
class Test4 with { val x = 5 }
104+
104105
import collection.mutable.HashMap
105106

106107
class Coder(words: List[String]) with

0 commit comments

Comments
 (0)