Skip to content

Commit ab3de89

Browse files
committed
Syntax change: "extension on" instead of "extension of"
"extension on" is more precise. The thing after the "of/on" is a parameter. The definition is not an extension "of" this parameter (what does that even mean?), but rather an extension of the parameter's underlying type. Note that Dart also uses "extension on".
1 parent 6c380b2 commit ab3de89

27 files changed

+54
-53
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,8 @@ object StdNames {
529529
val notify_ : N = "notify"
530530
val null_ : N = "null"
531531
val nullExpr: N = "nullExpr"
532-
val of: N = "of"
533532
val ofDim: N = "ofDim"
533+
val on: N = "on"
534534
val opaque: N = "opaque"
535535
val open: N = "open"
536536
val ordinal: N = "ordinal"

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,9 @@ object Parsers {
948948
def followingIsExtension() =
949949
val lookahead = in.LookaheadScanner()
950950
lookahead.nextToken()
951-
if lookahead.isIdent && !lookahead.isIdent(nme.of) then
951+
if lookahead.isIdent && !lookahead.isIdent(nme.on) then
952952
lookahead.nextToken()
953-
lookahead.isIdent(nme.of)
953+
lookahead.isIdent(nme.on)
954954

955955
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
956956

@@ -3538,9 +3538,9 @@ object Parsers {
35383538
*/
35393539
def extensionDef(start: Offset, mods: Modifiers): ModuleDef =
35403540
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()
3541+
val name = if isIdent && !isIdent(nme.on) then ident() else EmptyTermName
3542+
if !isIdent(nme.on) then syntaxErrorOrIncomplete("`on` expected")
3543+
if isIdent(nme.on) then in.nextToken()
35443544
val tparams = typeParamClauseOpt(ParamOwner.Def)
35453545
val extParams = paramClause(0, prefix = true)
35463546
val givenParamss = paramClauses(givenOnly = true)

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,19 @@ A collective extension defines one or more concrete methods that have the same t
128128
and prefix parameter. Examples:
129129

130130
```scala
131-
extension stringOps of (xs: Seq[String]) {
131+
extension stringOps on (xs: Seq[String]) {
132132
def longestStrings: Seq[String] = {
133133
val maxLength = xs.map(_.length).max
134134
xs.filter(_.length == maxLength)
135135
}
136136
}
137137

138-
extension listOps of [T](xs: List[T]) {
138+
extension listOps on [T](xs: List[T]) {
139139
def second = xs.tail.head
140140
def third: T = xs.tail.tail.head
141141
}
142142

143-
extension of [T](xs: List[T])(given Ordering[T]) {
143+
extension on [T](xs: List[T])(given Ordering[T]) {
144144
def largest(n: Int) = xs.sorted.takeRight(n)
145145
}
146146
```
@@ -164,18 +164,19 @@ given extension_largest_List_T: AnyRef {
164164
}
165165
```
166166

167-
`extension` and `of` are soft keywords. They can also be used as a regular identifiers.
167+
`extension` and `on` are soft keywords. They can also be used as a regular identifiers.
168168

169169
### Syntax
170170

171171
Here are the syntax changes for extension methods and given extensions relative
172-
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.
172+
to the [current syntax](../../internals/syntax.md). `extension` is a soft keyword, recognized only in tandem with `on`. It can be used as an identifier everywhere else.
173+
173174
```
174175
DefSig ::= ...
175176
| ExtParamClause [nl] [‘.’] id DefParamClauses
176177
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
177178
TmplDef ::= ...
178179
| ‘extension’ ExtensionDef
179-
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ExtMethods
180+
ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods
180181
ExtMethods ::= ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
181182
```

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Anonymous collective extensions also get compiler synthesized names, which are f
7272

7373
For example, the extension
7474
```scala
75-
extension of [T] (xs: List[T]) {
75+
extension on [T] (xs: List[T]) {
7676
def second = ...
7777
}
7878
```

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-
extension of [T](xs: List[T]) {
13+
extension on [T](xs: List[T]) {
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,9 +1,9 @@
11
object Test {
2-
extension a of (x: Int) {
2+
extension a on (x: Int) {
33
def |+| (y: Int) = x + y
44
}
55

6-
extension b of (x: Int) {
6+
extension b on (x: Int) {
77
def |+| (y: String) = x + y.length
88
}
99
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-
extension of (x: Nat) {
14+
extension on (x: Nat) {
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-
extension myNumericOps of [T](x: T) {
1+
extension myNumericOps on [T](x: T) {
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-
extension of [A](a: A) with
4+
extension on [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-
extension fooOps of [A](a: A) with
1+
extension fooOps on [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-
extension listExtension of [T](xs: List[T]) with
10+
extension listExtension on [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-
extension of [A, C](a: A) with
24+
extension on [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-
extension of (y: Any) {
5+
extension on (y: Any) {
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-
extension of [T](tup: T) {
9+
extension on [T](tup: T) {
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-
extension of (xs: FlagSet) {
12+
extension on (xs: FlagSet) {
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-
extension of (m: Matrix) with
6+
extension on (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-
extension of [T <: Product](gen: Generic[T]) {
6+
extension on [T <: Product](gen: Generic[T]) {
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-
extension of [F[_] <: Product, T](gen: Generic[F]) {
13+
extension on [F[_] <: Product, T](gen: Generic[F]) {
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-
extension stringOps of (xs: Seq[String]) with
42+
extension stringOps on (xs: Seq[String]) with
4343
def longestStrings: Seq[String] =
4444
val maxLength = xs.map(_.length).max
4545
xs.filter(_.length == maxLength)
4646

47-
extension of [T](xs: List[T]) with
47+
extension on [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-
extension of [T](x: T) with
136+
extension on [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-
extension of (xs: Seq[String]) with
156+
extension on (xs: Seq[String]) with
157157
def longestStrings: Seq[String] =
158158
val maxLength = xs.map(_.length).max
159159
xs.filter(_.length == maxLength)
160160

161-
extension of [T](xs: List[T]) with
161+
extension on [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

tests/pos/reference/extension-methods.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,19 @@ object ExtMethods with
4141

4242
List(1, 2, 3).second[Int]
4343

44-
extension stringOps of (xs: Seq[String]) {
44+
extension stringOps on (xs: Seq[String]) {
4545
def longestStrings: Seq[String] = {
4646
val maxLength = xs.map(_.length).max
4747
xs.filter(_.length == maxLength)
4848
}
4949
}
5050

51-
extension listOps of [T](xs: List[T]) with
51+
extension listOps on [T](xs: List[T]) with
5252
def second = xs.tail.head
5353
def third: T = xs.tail.tail.head
5454

5555

56-
extension of [T](xs: List[T])(given Ordering[T]) with
56+
extension on [T](xs: List[T])(given Ordering[T]) with
5757
def largest(n: Int) = xs.sorted.takeRight(n)
5858

5959
given stringOps1: AnyRef {

tests/pos/reference/opaque.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object Logarithms {
1212
}
1313

1414
// Extension methods define opaque types' public APIs
15-
extension of (x: Logarithm) {
15+
extension on (x: Logarithm) {
1616
def toDouble: Double = math.exp(x)
1717
def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y))
1818
def * (y: Logarithm): Logarithm = Logarithm(x + y)

tests/pos/tasty-reflect-opaque-api-proto.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Reflect(val internal: CompilerInterface) {
1010
opaque type Term <: Tree = internal.Term
1111

1212
object Tree {
13-
extension ops of (tree: Tree) {
13+
extension ops on (tree: Tree) {
1414
def show: String = ???
1515
}
1616
}

tests/run/extension-specificity.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
class A
22
class B extends A
33

4-
extension a of (x: A) with
4+
extension a on (x: A) with
55
def foo: Int = 1
66

7-
extension b of (x: B) with
7+
extension b on (x: B) with
88
def foo: Int = 2
99

1010
@main def Test =

tests/run/extmethod-overload.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ object Test extends App {
9797
def (x: Int).yy(y: Int) = x + y
9898
}
9999

100-
extension of (x: Int) {
100+
extension on (x: Int) {
101101
def yy (y: Int) = x - y
102102
}
103103

tests/run/extmethods2.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ object Test extends App {
1616
test(given TC())
1717

1818
object A {
19-
extension listOps of [T](xs: List[T]) {
19+
extension listOps on [T](xs: List[T]) {
2020
def second: T = xs.tail.head
2121
def third: T = xs.tail.tail.head
2222
def concat(ys: List[T]) = xs ++ ys
2323
}
24-
extension polyListOps of [T, U](xs: List[T]) {
24+
extension polyListOps on [T, U](xs: List[T]) {
2525
def zipp(ys: List[U]): List[(T, U)] = xs.zip(ys)
2626
}
27-
extension of (xs: List[Int]) {
27+
extension on (xs: List[Int]) {
2828
def prod = (1 /: xs)(_ * _)
2929
}
3030
}

tests/run/i6902.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
2-
extension of [A](a: A) { def <<< : A = a }
3-
extension of (b: Int) { def <<<< : Int = b }
2+
extension on [A](a: A) { def <<< : A = a }
3+
extension on (b: Int) { def <<<< : Int = b }
44

55
def main(args: Array[String]): Unit = {
66
1.<<<

tests/run/instances-anonymous.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ object Test extends App {
88

99
case class Circle(x: Double, y: Double, radius: Double)
1010

11-
extension of (c: Circle) {
11+
extension on (c: Circle) {
1212
def circumference: Double = c.radius * math.Pi * 2
1313
}
1414

@@ -25,13 +25,13 @@ object Test extends App {
2525
val names = List("hi", "hello", "world")
2626
assert(names.longestStrings == List("hello", "world"))
2727

28-
extension of [T](xs: Seq[T]) {
28+
extension on [T](xs: Seq[T]) {
2929
def second = xs.tail.head
3030
}
3131

3232
assert(names.longestStrings.second == "world")
3333

34-
extension of [T](xs: List[List[T]]) {
34+
extension on [T](xs: List[List[T]]) {
3535
def flattened = xs.foldLeft[List[T]](Nil)(_ ++ _)
3636
}
3737

tests/run/instances.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,27 @@ object Test extends App {
88

99
case class Circle(x: Double, y: Double, radius: Double)
1010

11-
extension circleOps of (c: Circle) with
11+
extension circleOps on (c: Circle) with
1212
def circumference: Double = c.radius * math.Pi * 2
1313

1414
val circle = new Circle(1, 1, 2.0)
1515

1616
assert(circle.circumference == circleOps.circumference(circle))
1717

18-
extension stringOps of (xs: Seq[String]) with
18+
extension stringOps on (xs: Seq[String]) with
1919
def longestStrings: Seq[String] =
2020
val maxLength = xs.map(_.length).max
2121
xs.filter(_.length == maxLength)
2222

2323
val names = List("hi", "hello", "world")
2424
assert(names.longestStrings == List("hello", "world"))
2525

26-
extension of [T](xs: Seq[T]) with
26+
extension on [T](xs: Seq[T]) with
2727
def second = xs.tail.head
2828

2929
assert(names.longestStrings.second == "world")
3030

31-
extension listListOps of [T](xs: List[List[T]]) with
31+
extension listListOps on [T](xs: List[List[T]]) with
3232
def flattened = xs.foldLeft[List[T]](Nil)(_ ++ _)
3333

3434
// A right associative op. Note: can't use given extension for this!

0 commit comments

Comments
 (0)