Skip to content

Commit 86cd4e1

Browse files
committed
Change extension method syntax
1 parent ca6b1c6 commit 86cd4e1

File tree

3 files changed

+166
-94
lines changed

3 files changed

+166
-94
lines changed

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

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,7 +2733,7 @@ object Parsers {
27332733
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
27342734
* DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds
27352735
*
2736-
* TypTypeParamCaluse::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
2736+
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
27372737
* TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds
27382738
*
27392739
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
@@ -2799,6 +2799,7 @@ object Parsers {
27992799
ofClass: Boolean = false, // owner is a class
28002800
ofCaseClass: Boolean = false, // owner is a case class
28012801
prefix: Boolean = false, // clause precedes name of an extension method
2802+
givenOnly: Boolean = false, // only given parameters allowed
28022803
firstClause: Boolean = false // clause is the first in regular list of clauses
28032804
): List[ValDef] = {
28042805
var impliedMods: Modifiers = EmptyModifiers
@@ -2870,6 +2871,8 @@ object Parsers {
28702871
if !impliedModOpt(IMPLICIT, () => Mod.Implicit()) then
28712872
impliedModOpt(GIVEN, () => Mod.Given())
28722873
impliedModOpt(ERASED, () => Mod.Erased())
2874+
if givenOnly && !impliedMods.is(Given) then
2875+
syntaxError(ExpectedTokenButFound(GIVEN, in.token))
28732876
val isParams =
28742877
!impliedMods.is(Given)
28752878
|| startParamTokens.contains(in.token)
@@ -2890,7 +2893,7 @@ object Parsers {
28902893
*/
28912894
def paramClauses(ofClass: Boolean = false,
28922895
ofCaseClass: Boolean = false,
2893-
ofInstance: Boolean = false): List[List[ValDef]] =
2896+
givenOnly: Boolean = false): List[List[ValDef]] =
28942897

28952898
def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] =
28962899
newLineOptWhenFollowedBy(LPAREN)
@@ -2900,6 +2903,7 @@ object Parsers {
29002903
nparams,
29012904
ofClass = ofClass,
29022905
ofCaseClass = ofCaseClass,
2906+
givenOnly = givenOnly,
29032907
firstClause = firstClause)
29042908
val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit)
29052909
params :: (
@@ -3394,58 +3398,70 @@ object Parsers {
33943398

33953399
/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
33963400
* | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
3397-
* | [id ‘:’] ExtParamClause ExtMethods
3401+
* | [[id] ‘extends’ ExtParamClause {GivenParamClause} ExtMethods
33983402
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33993403
* ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause}
34003404
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
34013405
*/
34023406
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
34033407
var mods1 = addMod(mods, instanceMod)
34043408
val hasGivenSig = followingIsGivenSig()
3405-
val name = if isIdent && hasGivenSig then ident() else EmptyTermName
3406-
indentRegion(name) {
3407-
var tparams: List[TypeDef] = Nil
3408-
var vparamss: List[List[ValDef]] = Nil
3409-
var hasExtensionParams = false
3410-
3411-
def parseParams(isExtension: Boolean): Unit =
3412-
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
3413-
hasExtensionParams = true
3414-
if tparams.nonEmpty || vparamss.nonEmpty then
3415-
syntaxError(i"cannot have parameters before and after `:` in extension")
3416-
if in.token == LBRACKET then
3417-
tparams = typeParamClause(ParamOwner.Def)
3418-
if in.token == LPAREN && followingIsParamOrGivenType() then
3419-
val paramsStart = in.offset
3420-
vparamss = paramClauses(ofInstance = !isExtension)
3421-
if isExtension then
3422-
checkExtensionParams(paramsStart, vparamss)
3423-
3424-
parseParams(isExtension = !hasGivenSig)
3425-
val parents =
3426-
if in.token == COLON then
3427-
in.nextToken()
3428-
if in.token == LBRACE
3429-
|| in.token == WITH
3430-
|| in.token == LBRACKET
3431-
|| in.token == LPAREN && followingIsParamOrGivenType()
3432-
then
3433-
parseParams(isExtension = true)
3434-
Nil
3435-
else
3436-
tokenSeparated(COMMA, constrApp)
3437-
else if in.token == SUBTYPE then
3438-
if !mods.is(Inline) then
3439-
syntaxError("`<:' is only allowed for given with `inline' modifier")
3440-
in.nextToken()
3441-
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3442-
else if name.isEmpty
3443-
&& in.token != LBRACE && in.token != WITH
3444-
&& !hasExtensionParams
3445-
then tokenSeparated(COMMA, constrApp)
3446-
else Nil
3409+
val name =
3410+
if isIdent && (hasGivenSig || in.lookaheadIn(BitSet(EXTENDS))) then ident()
3411+
else EmptyTermName
3412+
val gdef = indentRegion(name) {
3413+
if in.token == EXTENDS then
3414+
in.nextToken()
3415+
val tparams = typeParamClauseOpt(ParamOwner.Def)
3416+
val extParams = paramClause(0, prefix = true)
3417+
val givenParamss = paramClauses(givenOnly = true)
3418+
possibleTemplateStart()
3419+
val extMethods = templateBodyOpt(
3420+
makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
3421+
extMethods.body.foreach(checkExtensionMethod)
3422+
ModuleDef(name, extMethods)
3423+
else
3424+
var tparams: List[TypeDef] = Nil
3425+
var vparamss: List[List[ValDef]] = Nil
3426+
var hasExtensionParams = false
3427+
3428+
def parseParams(isExtension: Boolean): Unit =
3429+
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
3430+
hasExtensionParams = true
3431+
if tparams.nonEmpty || vparamss.nonEmpty then
3432+
syntaxError(i"cannot have parameters before and after `:` in extension")
3433+
if in.token == LBRACKET then
3434+
tparams = typeParamClause(ParamOwner.Def)
3435+
if in.token == LPAREN && followingIsParamOrGivenType() then
3436+
val paramsStart = in.offset
3437+
vparamss = paramClauses(givenOnly = !isExtension)
3438+
if isExtension then
3439+
checkExtensionParams(paramsStart, vparamss)
3440+
3441+
parseParams(isExtension = !hasGivenSig)
3442+
val parents =
3443+
if in.token == COLON then
3444+
in.nextToken()
3445+
if in.token == LBRACE
3446+
|| in.token == WITH
3447+
|| in.token == LBRACKET
3448+
|| in.token == LPAREN && followingIsParamOrGivenType()
3449+
then
3450+
parseParams(isExtension = true)
3451+
Nil
3452+
else
3453+
tokenSeparated(COMMA, constrApp)
3454+
else if in.token == SUBTYPE then
3455+
if !mods.is(Inline) then
3456+
syntaxError("`<:' is only allowed for given with `inline' modifier")
3457+
in.nextToken()
3458+
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3459+
else if name.isEmpty
3460+
&& in.token != LBRACE && in.token != WITH
3461+
&& !hasExtensionParams
3462+
then tokenSeparated(COMMA, constrApp)
3463+
else Nil
34473464

3448-
val gdef =
34493465
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
34503466
in.nextToken()
34513467
mods1 |= Final
@@ -3467,9 +3483,8 @@ object Parsers {
34673483
ModuleDef(name, templ)
34683484
else if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
34693485
else TypeDef(name.toTypeName, templ)
3470-
3471-
finalizeDef(gdef, mods1, start)
34723486
}
3487+
finalizeDef(gdef, mods1, start)
34733488
}
34743489

34753490
/* -------- TEMPLATES ------------------------------------------- */

docs/docs/reference/contextual/extension-methods.md

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -81,50 +81,6 @@ and where `T` is the expected type. The following two rewritings are tried in or
8181
So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided
8282
`circle` has type `Circle` and `CircleOps` is given (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`).
8383

84-
### Given Instances for Extension Methods
85-
86-
Given instances that define extension methods can also be defined without a parent. E.g.,
87-
88-
```scala
89-
given stringOps: {
90-
def (xs: Seq[String]) longestStrings: Seq[String] = {
91-
val maxLength = xs.map(_.length).max
92-
xs.filter(_.length == maxLength)
93-
}
94-
}
95-
96-
given {
97-
def [T](xs: List[T]) second = xs.tail.head
98-
}
99-
```
100-
If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
101-
102-
### Given Instances with Collective Parameters
103-
104-
If a given instance has no parent but several extension methods one can pull out the left parameter section
105-
as well as any type parameters of these extension methods into the given instance itself.
106-
For instance, here is a given instance with two extension methods.
107-
```scala
108-
given listOps: {
109-
def [T](xs: List[T]) second: T = xs.tail.head
110-
def [T](xs: List[T]) third: T = xs.tail.tail.head
111-
}
112-
```
113-
The repetition in the parameters can be avoided by hoisting the parameters up into the given instance itself. The following version is a shorthand for the code above.
114-
```scala
115-
given listOps: [T](xs: List[T]) {
116-
def second: T = xs.tail.head
117-
def third: T = xs.tail.tail.head
118-
}
119-
```
120-
This syntax just adds convenience at the definition site. Applications of such extension methods are exactly the same as if their parameters were repeated in each extension method.
121-
Examples:
122-
```scala
123-
val xs = List(1, 2, 3)
124-
xs.second[Int]
125-
ListOps.third[T](xs)
126-
```
127-
12884
### Operators
12985

13086
The extension method syntax also applies to the definition of operators.
@@ -165,6 +121,40 @@ If an extension method has type parameters, they come immediately after the `def
165121
```scala
166122
List(1, 2, 3).second[Int]
167123
```
124+
### Given Instances for Extension Methods
125+
126+
The `given extends` syntax lets on define given instances that define extension methods and nothing else. Examples:
127+
128+
```scala
129+
given stringOps extends (xs: Seq[String]) {
130+
def longestStrings: Seq[String] = {
131+
val maxLength = xs.map(_.length).max
132+
xs.filter(_.length == maxLength)
133+
}
134+
}
135+
136+
given extends [T](xs: List[T]) {
137+
def second = xs.tail.head
138+
def third: T = xs.tail.tail.head
139+
}
140+
```
141+
If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
142+
143+
The extension method definitions above are equivalent to the following standard given instances where
144+
the implemented parent is `AnyRef` and the parameters after the `extend` clause are repeated in each
145+
method definition:
146+
```
147+
given stringOps: AnyRef {
148+
def (xs: Seq[String]) longestStrings: Seq[String] = {
149+
val maxLength = xs.map(_.length).max
150+
xs.filter(_.length == maxLength)
151+
}
152+
}
153+
given AnyRef {
154+
def [T](xs: List[T]) second = xs.tail.head
155+
def [T](xs: List[T]) third: T = xs.tail.tail.head
156+
}
157+
```
168158

169159
### Syntax
170160

@@ -174,6 +164,7 @@ to the [current syntax](../../internals/syntax.md).
174164
DefSig ::= ...
175165
| ExtParamClause [nl] id DefParamClauses
176166
GivenDef ::= ...
177-
[GivenSig ‘:’] [ExtParamClause] TemplateBody
178-
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
167+
[id] ‘extends’ ExtParamClause {GivenParamClause} ExtMethods
168+
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
169+
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
179170
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
object Test with
2+
3+
case class Circle(x: Double, y: Double, radius: Double)
4+
5+
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
6+
7+
val circle = Circle(0, 0, 1)
8+
circle.circumference
9+
assert(circle.circumference == circumference(circle))
10+
11+
trait StringSeqOps {
12+
def (xs: Seq[String]) longestStrings = {
13+
val maxLength = xs.map(_.length).max
14+
xs.filter(_.length == maxLength)
15+
}
16+
}
17+
given ops1: StringSeqOps
18+
19+
List("here", "is", "a", "list").longestStrings
20+
21+
locally {
22+
object ops2 extends StringSeqOps
23+
import ops2.longestStrings
24+
List("here", "is", "a", "list").longestStrings
25+
}
26+
27+
def (x: String) < (y: String) = x.compareTo(y) < 0
28+
def [Elem](x: Elem) #: (xs: Seq[Elem]) = x +: xs
29+
30+
assert("a" < "bb")
31+
val xs = 1 #: Vector(2, 3)
32+
33+
def [T](xs: List[T]) second =
34+
xs.tail.head
35+
36+
def [T](xs: List[List[T]]) flattened =
37+
xs.foldLeft[List[T]](Nil)(_ ++ _)
38+
39+
def [T: Numeric](x: T) + (y: T): T =
40+
summon[Numeric[T]].plus(x, y)
41+
42+
List(1, 2, 3).second[Int]
43+
44+
given stringOps extends (xs: Seq[String]) {
45+
def longestStrings: Seq[String] = {
46+
val maxLength = xs.map(_.length).max
47+
xs.filter(_.length == maxLength)
48+
}
49+
}
50+
51+
given extends [T](xs: List[T]) with
52+
def second = xs.tail.head
53+
def third: T = xs.tail.tail.head
54+
55+
given stringOps1: AnyRef {
56+
def (xs: Seq[String]) longestStrings: Seq[String] = {
57+
val maxLength = xs.map(_.length).max
58+
xs.filter(_.length == maxLength)
59+
}
60+
}
61+
given AnyRef {
62+
def [T](xs: List[T]) second = xs.tail.head
63+
def [T](xs: List[T]) third: T = xs.tail.tail.head
64+
}
65+
66+
end Test

0 commit comments

Comments
 (0)