Skip to content

Commit 90492d7

Browse files
oderskybishabosha
authored andcommitted
New syntax for collective extension methods
It's now: ```scala extension listOps of [T](xs: List[T]) with { def second = xs.tail.head def third: T = xs.tail.tail.head } ``` instead of ```scala given listOps: [T](xs: List[T]) extended with { ... }
1 parent f489a35 commit 90492d7

30 files changed

+101
-66
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ object StdNames {
508508
val notify_ : N = "notify"
509509
val null_ : N = "null"
510510
val nullExpr: N = "nullExpr"
511+
val of: N = "of"
511512
val ofDim: N = "ofDim"
512513
val opaque: N = "opaque"
513514
val open: N = "open"

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

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ object Parsers {
188188
/* -------------- TOKEN CLASSES ------------------------------------------- */
189189

190190
def isIdent = in.isIdent
191-
def isIdent(name: Name) = in.token == IDENTIFIER && in.name == name
191+
def isIdent(name: Name) = in.isIdent(name)
192192
def isSimpleLiteral = simpleLiteralTokens contains in.token
193193
def isLiteral = literalTokens contains in.token
194194
def isNumericLit = numericLitTokens contains in.token
@@ -216,10 +216,11 @@ object Parsers {
216216
in.canStartExprTokens.contains(in.token) && !in.isSoftModifierInModifierPosition
217217

218218
def isDefIntro(allowedMods: BitSet, excludedSoftModifiers: Set[TermName] = Set.empty): Boolean =
219-
in.token == AT ||
220-
(defIntroTokens `contains` in.token) ||
221-
(allowedMods `contains` in.token) ||
222-
in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
219+
in.token == AT
220+
|| defIntroTokens.contains(in.token)
221+
|| allowedMods.contains(in.token)
222+
|| in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
223+
|| isIdent(nme.extension) && followingIsExtension()
223224

224225
def isStatSep: Boolean = in.isNewLine || in.token == SEMI
225226

@@ -944,6 +945,13 @@ object Parsers {
944945
lookahead.skipParens()
945946
lookahead.token == COLON || lookahead.token == SUBTYPE
946947

948+
def followingIsExtension() =
949+
val lookahead = in.LookaheadScanner()
950+
lookahead.nextToken()
951+
if lookahead.isIdent && !lookahead.isIdent(nme.of) then
952+
lookahead.nextToken()
953+
lookahead.isIdent(nme.of)
954+
947955
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
948956

949957
var opStack: List[OpInfo] = Nil
@@ -3275,6 +3283,7 @@ object Parsers {
32753283
* | [‘case’] ‘object’ ObjectDef
32763284
* | ‘enum’ EnumDef
32773285
* | ‘given’ GivenDef
3286+
* | ‘extension’ ExtensionDef
32783287
*/
32793288
def tmplDef(start: Int, mods: Modifiers): Tree =
32803289
in.token match {
@@ -3293,8 +3302,11 @@ object Parsers {
32933302
case GIVEN =>
32943303
givenDef(start, mods, atSpan(in.skipToken()) { Mod.Given() })
32953304
case _ =>
3296-
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
3297-
EmptyTree
3305+
if isIdent(nme.extension) && followingIsExtension() then
3306+
extensionDef(start, mods)
3307+
else
3308+
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
3309+
EmptyTree
32983310
}
32993311

33003312
/** ClassDef ::= id ClassConstr TemplateOpt
@@ -3522,6 +3534,23 @@ object Parsers {
35223534
finalizeDef(gdef, mods1, start)
35233535
}
35243536

3537+
/** ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
3538+
*/
3539+
def extensionDef(start: Offset, mods: Modifiers): ModuleDef =
3540+
in.nextToken()
3541+
val name = if isIdent && !isIdent(nme.of) then ident() else EmptyTermName
3542+
if !isIdent(nme.of) then syntaxErrorOrIncomplete("`of` expected")
3543+
if isIdent(nme.of) then in.nextToken()
3544+
val tparams = typeParamClauseOpt(ParamOwner.Def)
3545+
val extParams = paramClause(0, prefix = true)
3546+
val givenParamss = paramClauses(givenOnly = true)
3547+
accept(WITH)
3548+
if !in.isNestedStart then syntaxError("Extension without extension methods")
3549+
val templ = templateBodyOpt(makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
3550+
templ.body.foreach(checkExtensionMethod(tparams, _))
3551+
val edef = ModuleDef(name, templ)
3552+
finalizeDef(edef, addFlag(mods, Given), start)
3553+
35253554
/* -------- TEMPLATES ------------------------------------------- */
35263555

35273556
/** SimpleConstrApp ::= AnnotType {ParArgumentExprs}
@@ -3681,7 +3710,7 @@ object Parsers {
36813710
def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
36823711
var self: ValDef = EmptyValDef
36833712
val stats = new ListBuffer[Tree]
3684-
if (isExprIntro) {
3713+
if (isExprIntro && !isDefIntro(modifierTokens)) {
36853714
val first = expr1()
36863715
if (in.token == ARROW) {
36873716
first match {
@@ -3707,10 +3736,10 @@ object Parsers {
37073736
stats ++= importClause(IMPORT, Import)
37083737
else if (in.token == EXPORT)
37093738
stats ++= importClause(EXPORT, Export.apply)
3710-
else if (isExprIntro)
3711-
stats += expr1()
37123739
else if (isDefIntro(modifierTokensOrCase))
37133740
stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens))
3741+
else if (isExprIntro)
3742+
stats += expr1()
37143743
else if (!isStatSep) {
37153744
exitOnError = mustStartStat
37163745
syntaxErrorOrIncomplete("illegal start of definition")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ object Scanners {
10161016

10171017
def isNewLine = token == NEWLINE || token == NEWLINES
10181018
def isIdent = token == IDENTIFIER || token == BACKQUOTED_IDENT
1019+
def isIdent(name: Name) = token == IDENTIFIER && this.name == name
10191020

10201021
def isNestedStart = token == LBRACE || token == INDENT
10211022
def isNestedEnd = token == RBRACE || token == OUTDENT

docs/blog/_posts/2019-12-20-21th-dotty-milestone-release.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ given extension (s: String) { ... }
183183
```
184184
or
185185
```scala
186-
given listOps: [T](xs: List[T]) extended with { ... }
186+
extension listOps of [T](xs: List[T]) with { ... }
187187

188188
given (s: String) extended with { ... }
189189
```

docs/docs/internals/syntax.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ yield
103103
### Soft keywords
104104

105105
```
106-
as derives inline opaque open
106+
derives extension inline opaque open
107107
~ * | & + -
108108
```
109109

@@ -378,7 +378,7 @@ TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
378378
| [‘case’] ‘object’ ObjectDef
379379
| ‘enum’ EnumDef
380380
| ‘given’ GivenDef
381-
| Export
381+
| ‘extension’ ExtensionDef
382382
ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ)
383383
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
384384
ConstrMods ::= {Annotation} [AccessModifier]
@@ -388,11 +388,11 @@ GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
388388
AnnotType ‘=’ Expr
389389
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
390390
ConstrApps [[‘with’] TemplateBody]
391-
| [id ‘:’] ExtParamClause {GivenParamClause}
392-
‘extended’ ‘with’ ExtMethods
393391
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
392+
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause}
393+
‘with’ ExtMethods
394+
ExtMethods ::= ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
394395
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
395-
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
396396
Template ::= InheritClauses [[‘with’] TemplateBody] Template(constr, parents, self, stats)
397397
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
398398
ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ So `circle.circumference` translates to `CircleOps.circumference(circle)`, provi
8080

8181
### Operators
8282

83-
The extension method syntax also applies to the definition of operators.
83+
The extension method syntax also applies to the definition of operators.
8484
In this case it is allowed and preferable to omit the period between the leading parameter list
8585
and the operator. In each case the definition syntax mirrors the way the operator is applied.
8686
Examples:
@@ -122,24 +122,25 @@ If an extension method has type parameters, they come immediately after the `def
122122
```scala
123123
List(1, 2, 3).second[Int]
124124
```
125-
### Given Instances for Extension Methods
125+
### Collective Extensions
126126

127-
`given` extensions are given instances that define extension methods and nothing else. Examples:
127+
A collective extension defines one or more concrete methods that have the same type parameters
128+
and prefix parameter. Examples:
128129

129130
```scala
130-
given stringOps: (xs: Seq[String]) extended with {
131+
extension stringOps of (xs: Seq[String]) with {
131132
def longestStrings: Seq[String] = {
132133
val maxLength = xs.map(_.length).max
133134
xs.filter(_.length == maxLength)
134135
}
135136
}
136137

137-
given listOps: [T](xs: List[T]) extended with {
138+
extension listOps of [T](xs: List[T]) with {
138139
def second = xs.tail.head
139140
def third: T = xs.tail.tail.head
140141
}
141142

142-
given [T](xs: List[T])(given Ordering[T]) extended with {
143+
extension of [T](xs: List[T])(given Ordering[T]) with {
143144
def largest(n: Int) = xs.sorted.takeRight(n)
144145
}
145146
```
@@ -163,15 +164,18 @@ given given_largest_of_List_T: AnyRef {
163164
}
164165
```
165166

167+
`extension` and `of` are soft keywords. They can also be used as a regular identifiers.
168+
166169
### Syntax
167170

168171
Here are the syntax changes for extension methods and given extensions relative
169172
to the [current syntax](../../internals/syntax.md). `extension` is a soft keyword, recognized only after a `given`. It can be used as an identifier everywhere else.
170173
```
171174
DefSig ::= ...
172175
| ExtParamClause [nl] [‘.’] id DefParamClauses
173-
GivenDef ::= ...
174-
[id ‘:’] ‘extension’ ExtParamClause {GivenParamClause} ExtMethods
175176
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
176-
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
177+
TmplDef ::= ...
178+
| ‘extension’ ExtensionDef
179+
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
180+
ExtMethods ::= ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
177181
```

docs/docs/reference/contextual/relationship-implicits.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Anonymous given instances that define extension methods
6565
get their name from the name of the first extension method and the toplevel type
6666
constructor of its first parameter. For example, the given instance
6767
```scala
68-
given [T] (xs: List[T]) extended with {
68+
extension of [T] (xs: List[T]) with {
6969
def second = ...
7070
}
7171
```

tests/neg/extension-methods.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object Test {
1010
"".l2 // error
1111
1.l1 // error
1212

13-
given [T](xs: List[T]) extended with {
13+
extension of [T](xs: List[T]) with {
1414
def (x: Int).f1: T = ??? // error: No extension method allowed here, since collective parameters are given
1515
def f2[T]: T = ??? // error: T is already defined as type T
1616
def f3(xs: List[T]) = ??? // error: xs is already defined as value xs

tests/neg/extmethod-overload.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
object Test {
2-
given a: (x: Int) extended with
2+
extension a of (x: Int) with
33
def |+| (y: Int) = x + y
44

5-
given b: (x: Int) extended with {
5+
extension b of (x: Int) with {
66
def |+| (y: String) = x + y.length
77
}
88
assert((1 |+| 2) == 3) // error ambiguous

tests/neg/i5455.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object Library {
1111
def toInt(n: Nat): Int = n
1212

1313
}
14-
given (x: Nat) extended with
14+
extension of (x: Nat) with
1515
def * (y: Nat): Nat = x * y
1616
def toInt: Int = x
1717
}

tests/neg/i6801.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
given myNumericOps: [T](x: T) extended with {
1+
extension myNumericOps of [T](x: T) with {
22
def + (y: T)(given n: Numeric[T]): T = n.plus(x,y)
33
}
44
def foo[T: Numeric](x: T) = 1f + x // error: no implicit argument of type Numeric[Any]

tests/neg/i6900.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
object Test2 {
22

33
// Works with extension method
4-
given [A](a: A) extended with
4+
extension of [A](a: A) with
55
def foo[C]: C => A = _ => a // error: extension method cannot have type parameters
66

77
1.foo.foo

tests/neg/i7529.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
given fooOps: [A](a: A) extended with
1+
extension fooOps of [A](a: A) with
22

33
@nonsense // error: not found: nonsense
44
def foo = ???

tests/neg/missing-implicit1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object testObjectInstance with
77
object instances {
88
given zipOption: Zip[Option] = ???
99
given traverseList: Traverse[List] = ???
10-
given listExtension: [T](xs: List[T]) extended with
10+
extension listExtension of [T](xs: List[T]) with
1111
def second: T = xs.tail.head
1212
def [T](xs: List[T]) first: T = xs.head
1313
}

tests/pos/i6900.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object Test1 {
2121
object Test2 {
2222

2323
// Works with extension method
24-
given [A, C](a: A) extended with
24+
extension of [A, C](a: A) with
2525
def foo: C => A = _ => a
2626

2727
1.foo.foo

tests/pos/i7084.scala

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

33
type Foo
44

5-
given (y: Any) extended with {
5+
extension of (y: Any) with {
66
def g(given Foo): Any = ???
77
}
88

tests/pos/i7087.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type F[T] = T match {
66
case G[a] => String
77
}
88

9-
given [T](tup: T) extended with {
9+
extension of [T](tup: T) with {
1010
def g(given Foo: F[T]) = ???
1111
}
1212

tests/pos/implicit-scope.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ object A {
99
type FlagSet = opaques.FlagSet
1010
def FlagSet(bits: Long): FlagSet = opaques.FlagSet(bits)
1111

12-
given (xs: FlagSet) extended with {
12+
extension of (xs: FlagSet) with {
1313
def bits: Long = opaques.toBits(xs)
1414
def | (ys: FlagSet): FlagSet = FlagSet(xs.bits | ys.bits)
1515
}

tests/pos/matrixOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ object Test with
33
type Matrix = Array[Array[Double]]
44
type Vector = Array[Double]
55

6-
given (m: Matrix) extended with
6+
extension of (m: Matrix) with
77
def nRows = m.length
88
def nCols = m(0).length
99
def row(i: Int): Vector = m(i)

tests/pos/mirror-implicit-scope.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import scala.deriving._
33
object Test {
44
object K0 {
55
type Generic[T] = Mirror { type Scope = K0.type ; type MirroredType = T ; type MirroredElemTypes }
6-
given [T <: Product](gen: Generic[T]) extended with {
6+
extension of [T <: Product](gen: Generic[T]) with {
77
inline def toRepr (t: T): gen.MirroredElemTypes = Tuple.fromProduct(t).asInstanceOf
88
}
99
}
1010

1111
object K1 {
1212
type Generic[F[_]] = Mirror { type Scope = K1.type ; type MirroredType = F ; type MirroredElemTypes[_] }
13-
given [F[_] <: Product, T](gen: Generic[F]) extended with {
13+
extension of [F[_] <: Product, T](gen: Generic[F]) with {
1414
inline def toRepr (t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
1515
}
1616
}

tests/pos/reference/delegates.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ object Instances extends Common with
3939
if (fst != 0) fst else xs1.compareTo(ys1)
4040
end listOrd
4141

42-
given stringOps: (xs: Seq[String]) extended with
42+
extension stringOps of (xs: Seq[String]) with
4343
def longestStrings: Seq[String] =
4444
val maxLength = xs.map(_.length).max
4545
xs.filter(_.length == maxLength)
4646

47-
given [T](xs: List[T]) extended with
47+
extension of [T](xs: List[T]) with
4848
def second = xs.tail.head
4949
def third = xs.tail.tail.head
5050

@@ -133,7 +133,7 @@ object PostConditions with
133133

134134
def result[T](given x: WrappedResult[T]): T = x
135135

136-
given [T](x: T) extended with
136+
extension of [T](x: T) with
137137
def ensuring(condition: (given WrappedResult[T]) => Boolean): T =
138138
assert(condition(given x))
139139
x
@@ -153,12 +153,12 @@ object AnonymousInstances extends Common with
153153
val fst = x.compareTo(y)
154154
if (fst != 0) fst else xs1.compareTo(ys1)
155155

156-
given (xs: Seq[String]) extended with
156+
extension of (xs: Seq[String]) with
157157
def longestStrings: Seq[String] =
158158
val maxLength = xs.map(_.length).max
159159
xs.filter(_.length == maxLength)
160160

161-
given [T](xs: List[T]) extended with
161+
extension of [T](xs: List[T]) with
162162
def second = xs.tail.head
163163

164164
given [From, To]: (c: Convertible[From, To]) => Convertible[List[From], List[To]] with

0 commit comments

Comments
 (0)