Skip to content

Commit ca5c8ae

Browse files
committed
Allow normal parameter clauses after context parameters.
There are valid use cases, and syntactic awkwardness can be kept in check by the "one space following a with clause" rule.
1 parent 2ff0b4c commit ca5c8ae

File tree

8 files changed

+85
-28
lines changed

8 files changed

+85
-28
lines changed

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,17 +2917,23 @@ object Parsers {
29172917
}
29182918

29192919
/** ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’]
2920-
* | {ClsParamClause} {GivenClsParamClause}
2920+
* | ClsParamClause ClsParamClauses
2921+
* | ClsParamClauses1
2922+
* ClsParamClauses1 ::= WithClsParamClause ClsParamClauses
2923+
* | AnnotTypes ClsParamClauses1ClsParamClauses
29212924
* DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’]
2922-
* | {DefParamClause} {GivenParamClause}
2925+
* | DefParamClause DefParamClauses
2926+
* | DefParamClauses1
2927+
* DefParamClauses1 ::= WithCaramClause DefParamClauses
2928+
* | AnnotTypes DeParamClauses1
29232929
*
29242930
* @return The parameter definitions
29252931
*/
29262932
def paramClauses(ofClass: Boolean = false,
29272933
ofCaseClass: Boolean = false,
29282934
givenOnly: Boolean = false): List[List[ValDef]] =
29292935

2930-
def recur(firstClause: Boolean, nparams: Int, givenOnly: Boolean): List[List[ValDef]] =
2936+
def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] =
29312937
newLineOptWhenFollowedBy(LPAREN)
29322938
val prefixMods =
29332939
if in.token == WITH && in.ch != Chars.LF then // TODO: remove LF test
@@ -2949,14 +2955,17 @@ object Parsers {
29492955
|| params.nonEmpty && params.head.mods.flags.is(Given)
29502956
params :: (
29512957
if lastClause then Nil
2952-
else recur(firstClause = false, nparams + params.length, givenOnly | isGivenClause))
2958+
else recur(firstClause = false, nparams + params.length))
29532959
else if prefixMods.is(Given) then
29542960
val params = givenTypes(annotType, nparams, ofClass)
2955-
params :: recur(firstClause = false, nparams + params.length, true)
2961+
params :: (
2962+
if in.token == WITH then recur(firstClause = false, nparams + params.length)
2963+
else Nil
2964+
)
29562965
else Nil
29572966
end recur
29582967

2959-
recur(firstClause = true, 0, givenOnly)
2968+
recur(firstClause = true, 0)
29602969
end paramClauses
29612970

29622971
/* -------- DEFS ------------------------------------------- */
@@ -3448,11 +3457,12 @@ object Parsers {
34483457
syntaxError(i"extension clause can only define methods", stat.span)
34493458
}
34503459

3451-
/** GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
3452-
* | [GivenSig] ConstrApps [TemplateBody]
3453-
* GivenSig ::= [id] [DefTypeParamClause] GivenParamClauses ‘as’
3454-
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
3455-
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
3460+
/** GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
3461+
* | [GivenSig] ConstrApps [TemplateBody]
3462+
* GivenSig ::= [id] [DefTypeParamClause] {WithParamsOrTypes} ‘as’
3463+
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
3464+
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
3465+
* WithParamsOrTypes ::= WithParamClause | AnnotTypes
34563466
*/
34573467
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
34583468
var mods1 = addMod(mods, instanceMod)

docs/docs/internals/syntax.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,19 +299,26 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] |
299299
SubtypeBounds
300300
301301
ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’]
302-
| {ClsParamClause} {WithClsParamClause}
302+
| ClsParamClause ClsParamClauses
303+
| ClsParamClauses1
304+
ClsParamClauses1 ::= WithClsParamClause ClsParamClauses
305+
| AnnotTypes ClsParamClauses1
303306
ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’
304-
WithClsParamClause::= ‘with’ (‘(’ (ClsParams | Types) ‘)’ | AnnotTypes)
307+
WithClsParamClause::= ‘with’ ‘(’ (ClsParams | Types) ‘)’
305308
ClsParams ::= ClsParam {‘,’ ClsParam}
306309
ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var
307310
[{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
308311
Param ::= id ‘:’ ParamType [‘=’ Expr]
309312
| INT
310313
311314
DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’]
312-
| {DefParamClause} {WithParamClause}
315+
| DefParamClause DefParamClauses
316+
| DefParamClauses1
317+
DefParamClauses1 ::= WithParamClause DefParamClauses
318+
| AnnotTypes DefParamClauses1
313319
DefParamClause ::= [nl] ‘(’ DefParams ‘)’
314-
WithParamClause ::= ‘with’ (‘(’ (DefParams | Types) ‘)’ | AnnotTypes)
320+
WithParamClause ::= ‘with’ (‘(’ (DefParams | Types) ‘)’
321+
WithParamsOrTypes ::= WithParamClause | AnnotTypes
315322
DefParams ::= DefParam {‘,’ DefParam}
316323
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.
317324
ClosureMods ::= { ‘implicit’ | ‘given’}
@@ -390,8 +397,8 @@ ObjectDef ::= id [Template]
390397
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
391398
GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
392399
| [GivenSig] ConstrApps [TemplateBody]
393-
GivenSig ::= [id] [DefTypeParamClause] {WithParamClause} ‘as’
394-
ExtensionDef ::= [id] ‘on’ ExtParamClause {WithParamClause} ExtMethods
400+
GivenSig ::= [id] [DefTypeParamClause] {WithParamsOrTypes} ‘as’
401+
ExtensionDef ::= [id] ‘on’ ExtParamClause {WithParamsOrTypes} ExtMethods
395402
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
396403
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
397404
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)

docs/docs/reference/contextual/context-parameters.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ maximum(xs).with(descending.with(listOrd.with(intOrd)))
6969

7070
## Multiple With Clauses
7171

72-
There can be several with clauses in a definition and with clauses can be freely interspersed with normal parameters. Example:
72+
There can be several with clauses in a definition. Example:
7373
```scala
7474
def f(u: Universe) with (ctx: u.Context) with (s: ctx.Symbol, k: ctx.Kind) = ...
7575
```
@@ -88,6 +88,17 @@ f(global).with(ctx).with(sym, kind)
8888
```
8989
But `f(global).with(sym, kind)` would give a type error.
9090

91+
With clauses can be freely interspersed with normal parameters, but a normal parameter clause cannot
92+
directly follow a with parameter clause consisting only of types outside parentheses. So the following is illegal:
93+
```scala
94+
def f with A, B (x: C) = ...
95+
```
96+
But the following variants are valid:
97+
```scala
98+
def g with A, B with (x: C) = ...
99+
def h with (A, B) (x: C) = ...
100+
```
101+
91102
## Summoning Instances
92103

93104
The method `summon` in `Predef` returns the given of a specific type. For example,
@@ -105,9 +116,15 @@ def summon[T] with (x: T): x.type = x
105116
Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../../internals/syntax.md).
106117
```
107118
ClsParamClauses ::= ...
108-
| {ClsParamClause} {WithClsParamClause}
119+
| ClsParamClause ClsParamClauses
120+
| ClsParamClauses1
121+
ClsParamClauses1 ::= WithClsParamClause ClsParamClauses
122+
| AnnotTypes ClsParamClauses1
109123
DefParamClauses ::= ...
110-
| {DefParamClause} {WithParamClause}
124+
| DefParamClause DefParamClauses
125+
| DefParamClauses1
126+
DefParamClauses1 ::= WithParamClause DefParamClauses
127+
| AnnotTypes DefParamClauses1
111128
WithClsParamClause ::= ‘with’ (‘(’ (ClsParams | Types) ‘)’ | AnnotTypes)
112129
WithParamClause ::= ‘with’ (‘(’ (DefParams | Types) ‘)’ | AnnotTypes)
113130
Types ::= Type {‘,’ Type}

docs/docs/reference/contextual/givens.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ parameters, which are explained in the [next section](./context-parameters.md).
4242
The name of a given can be left out. So the definitions
4343
of the last section can also be expressed like this:
4444
```scala
45-
given as Ord[Int] { ... }
45+
given Ord[Int] { ... }
4646
given [T] with Ord[T] as Ord[List[T]] { ... }
4747
```
4848
If the name of a given is missing, the compiler will synthesize a name from
@@ -90,9 +90,10 @@ is created for each reference.
9090
Here is the new syntax for givens, seen as a delta from the [standard context free syntax of Scala 3](../../internals/syntax.md).
9191

9292
```
93-
TmplDef ::= ...
94-
| ‘given’ GivenDef
95-
GivenDef ::= GivenSig [‘_’ ‘<:’] Type ‘=’ Expr
96-
| GivenSig ConstrApp {‘,’ ConstrApp } [TemplateBody]
97-
GivenSig ::= [id] [DefTypeParamClause] {WithParamClause} ‘of’
93+
TmplDef ::= ...
94+
| ‘given’ GivenDef
95+
GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
96+
| [GivenSig] ConstrApp {‘,’ ConstrApp } [TemplateBody]
97+
GivenSig ::= [id] [DefTypeParamClause] {WithParamsOrTypes} ‘as’
98+
WithParamsOrTypes ::= WithParamClause | AnnotTypes
9899
```

tests/neg/given-eta.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ trait D
33
type T
44
def trans(other: T): T
55

6-
def h(d: D) with (x: d.T) (y: d.T) = (d.trans(x), d.trans(y)) // error: normal parameter cannot follow context parameter
6+
def h(d: D) with (x: d.T) (y: d.T) = (d.trans(x), d.trans(y))
77

88
val z = h // error: no implicit argument of type d.T was found for parameter x of method h
99

10+
def f with (D) (x: Int) = x // OK
11+
def g with D (x: Int) = x // error // error

tests/neg/implicit-params.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object Test {
55

66
def f(x: Int) with (c: C) = x + c.x
77

8-
def g0(x: Int) with (c: C) (y: Int) = x + c.x + y // error: normal parameter cannot follow context parameter
8+
def g0(x: Int) with (c: C) (y: Int) = x + c.x + y
99

1010
def g(x: Int) with (c: C) with D = x + c.x + summon[D].x // OK
1111

tests/pos/reference/delegates.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Common:
2222
def [A, B](x: F[A]) map (f: A => B) = x.flatMap(f `andThen` pure)
2323

2424
def pure[A](x: A): F[A]
25+
end Common
2526

2627
object Instances extends Common:
2728

tests/run/given-eta.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class C(val x: Int)
2+
3+
trait D
4+
type T
5+
def trans(other: T): T
6+
7+
def f(x: Int) with (c: C) (y: Int) = x + c.x + y
8+
def g(x: Int) with (d: D) (y: d.T): d.T = d.trans(y)
9+
10+
@main def Test =
11+
given C(1)
12+
val x = f
13+
assert(x(2)(3) == 6)
14+
15+
given D
16+
type T = Int
17+
def trans(other: T) = 2 * other
18+
val y = g
19+
assert(y(2)(3) == 6)

0 commit comments

Comments
 (0)