Skip to content

Commit 29f5ce4

Browse files
committed
Syntax change for type parameters of extension methods
The new syntax has the type parameters come first. I.e. ``` def [T](xs: List[T]) append (ys: List[T]): List[T] = ... ``` instead of ``` def (xs: List[T]) append [T] (ys: List[T]): List[T] = ... ``` Application is unchanged, so it is still ``` xs.append[String](ys) ``` An argument for the old syntax is that it aligns definition and call syntax. On the other hand, the new syntax maintains the general rule that parameter introductions always com before parameter uses. The decisive argument to switch is to be consistent with the new collective parameter syntax, where `append` would be written like this: ``` given [T](xs: List[T]) def append (ys: List[T]): List[T] = ... ``` To avoid misalignment of type parameters between definition and call syntax, we considered disallowing explicit type parameters for extension methods altogether, and to require that the method is called as a normal method instead. But that would not work for anonymous givens as in the last exaple above.
1 parent d2db509 commit 29f5ce4

32 files changed

+122
-122
lines changed

compiler/src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,16 +208,13 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro
208208
case tree: DefDef if tree.mods.is(Extension) =>
209209
tree.vparamss match {
210210
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
211-
check(vparams2)
212211
check(tree.tparams)
212+
check(vparams2)
213213
check(vparams1)
214214
check(rest)
215-
case vparams1 :: rest =>
216-
check(vparams1)
217-
check(tree.tparams)
218-
check(rest)
219215
case _ =>
220216
check(tree.tparams)
217+
check(tree.vparamss)
221218
}
222219
check(tree.tpt)
223220
check(tree.rhs)

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -754,9 +754,10 @@ object Trees {
754754
def unforced: LazyTree[T] = preRhs
755755
protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x
756756

757-
override def disableOverlapChecks = rawMods.is(Given)
758-
// disable order checks for implicit aliases since their given clause follows
759-
// their for clause, but the two appear swapped in the DefDef.
757+
override def disableOverlapChecks = rawMods.is(Extension)
758+
// disable order checks for extension methods as long as we parse
759+
// type parameters both before and after the leading parameter section.
760+
// TODO drop this once syntax of type parameters has settled.
760761
}
761762

762763
/** mods class name template or
@@ -789,10 +790,6 @@ object Trees {
789790

790791
def parents: List[Tree[T]] = parentsOrDerived // overridden by DerivingTemplate
791792
def derived: List[untpd.Tree] = Nil // overridden by DerivingTemplate
792-
793-
override def disableOverlapChecks = true
794-
// disable overlaps checks since templates of instance definitions have their
795-
// `given` clause come last, which means that the constructor span can contain the parent spans.
796793
}
797794

798795

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ object Decorators {
170170
}
171171
}
172172

173-
implicit object reportDeco {
174-
def (x: T) reporting[T](
173+
implicit class reportDeco[T](x: T) extends AnyVal {
174+
def reporting(
175175
op: (given WrappedResult[T]) => String,
176176
printer: config.Printers.Printer = config.Printers.default): T = {
177177
printer.println(op(given WrappedResult(x)))

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

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3088,10 +3088,11 @@ object Parsers {
30883088
}
30893089
}
30903090

3091-
/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
3092-
* | this ParamClause ParamClauses `=' ConstrExpr
3093-
* DefDcl ::= DefSig `:' Type
3094-
* DefSig ::= [‘(’ DefParam ‘)’ [nl]] id [DefTypeParamClause] ParamClauses
3091+
/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
3092+
* | this ParamClause ParamClauses `=' ConstrExpr
3093+
* DefDcl ::= DefSig `:' Type
3094+
* DefSig ::= id [DefTypeParamClause] DefParamClauses
3095+
* | ExtParamClause [nl] id DefParamClauses
30953096
*/
30963097
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
30973098
def scala2ProcedureSyntax(resultTypeStr: String) = {
@@ -3120,27 +3121,35 @@ object Parsers {
31203121
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
31213122
}
31223123
else {
3123-
val (leadingParamss, flags) =
3124-
if (in.token == LPAREN)
3125-
try (paramClause(0, prefix = true) :: Nil, Method | Extension)
3126-
finally newLineOpt()
3124+
def extParamss = try paramClause(0, prefix = true) :: Nil finally newLineOpt()
3125+
val (leadingTparams, leadingVparamss, flags) =
3126+
if in.token == LBRACKET then
3127+
(typeParamClause(ParamOwner.Def), extParamss, Method | Extension)
3128+
else if in.token == LPAREN then
3129+
(Nil, extParamss, Method | Extension)
31273130
else
3128-
(Nil, Method)
3131+
(Nil, Nil, Method)
31293132
val mods1 = addFlag(mods, flags)
31303133
val ident = termIdent()
31313134
val name = ident.name.asTermName
3132-
val tparams = typeParamClauseOpt(ParamOwner.Def)
3133-
val vparamss = paramClauses() match {
3134-
case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(ident.name) =>
3135-
rparams :: leadingParamss ::: rparamss
3135+
val tparams =
3136+
if in.token == LBRACKET then
3137+
if flags.is(Extension) then
3138+
if leadingTparams.isEmpty then
3139+
deprecationWarning("type parameters in extension methods should be written after `def`")
3140+
else
3141+
syntaxError("no type parameters allowed here")
3142+
typeParamClause(ParamOwner.Def)
3143+
else leadingTparams
3144+
val vparamss = paramClauses() match
3145+
case rparams :: rparamss if leadingVparamss.nonEmpty && !isLeftAssoc(ident.name) =>
3146+
rparams :: leadingVparamss ::: rparamss
31363147
case rparamss =>
3137-
leadingParamss ::: rparamss
3138-
}
3148+
leadingVparamss ::: rparamss
31393149
var tpt = fromWithinReturnType {
3140-
if (in.token == SUBTYPE && mods.is(Inline)) {
3150+
if in.token == SUBTYPE && mods.is(Inline) then
31413151
in.nextToken()
31423152
TypeBoundsTree(EmptyTree, toplevelTyp())
3143-
}
31443153
else typedOpt()
31453154
}
31463155
if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE)

docs/docs/internals/syntax.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,8 @@ Dcl ::= RefineDcl
357357
ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
358358
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
359359
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
360-
DefSig ::= [‘(’ DefParam ‘)’ [nl]] id
361-
[DefTypeParamClause] DefParamClauses
360+
DefSig ::= id [DefTypeParamClause] DefParamClauses
361+
| ExtParamClause [nl] id DefParamClauses
362362
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
363363
364364
Def ::= ‘val’ PatDef

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ given stringOps: {
9494
}
9595

9696
given {
97-
def (xs: List[T]) second[T] = xs.tail.head
97+
def [T](xs: List[T]) second = xs.tail.head
9898
}
9999
```
100100
If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
@@ -106,8 +106,8 @@ as well as any type parameters of these extension methods into the given instanc
106106
For instance, here is a given instance with two extension methods.
107107
```scala
108108
given listOps: {
109-
def (xs: List[T]) second[T]: T = xs.tail.head
110-
def (xs: List[T]) third[T]: T = xs.tail.tail.head
109+
def [T](xs: List[T]) second: T = xs.tail.head
110+
def [T](xs: List[T]) third: T = xs.tail.tail.head
111111
}
112112
```
113113
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.
@@ -151,13 +151,13 @@ to the implementation of right binding operators as normal methods.
151151
The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method. Examples:
152152

153153
```scala
154-
def (xs: List[T]) second [T] =
154+
def [T](xs: List[T]) second =
155155
xs.tail.head
156156

157-
def (xs: List[List[T]]) flattened [T] =
157+
def [T](xs: List[List[T]]) flattened =
158158
xs.foldLeft[List[T]](Nil)(_ ++ _)
159159

160-
def (x: T) + [T : Numeric](y: T): T =
160+
def [T: Numeric](x: T) + (y: T): T =
161161
summon[Numeric[T]].plus(x, y)
162162
```
163163

@@ -170,7 +170,7 @@ The required syntax extension just adds one clause for extension methods relativ
170170
to the [current syntax](../../internals/syntax.md).
171171
```
172172
DefSig ::= ...
173-
| ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses
173+
| ExtParamClause [nl] id DefParamClauses
174174
GivenDef ::= ...
175175
[GivenSig ‘:’] [ExtParamClause] TemplateBody
176176
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}

docs/docs/reference/contextual/typeclasses.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,25 @@ def sum[T: Monoid](xs: List[T]): T =
3838

3939
```scala
4040
trait Functor[F[_]] {
41-
def (x: F[A]) map [A, B] (f: A => B): F[B]
41+
def [A, B](x: F[A]) map (f: A => B): F[B]
4242
}
4343

4444
trait Monad[F[_]] extends Functor[F] {
45-
def (x: F[A]) flatMap [A, B] (f: A => F[B]): F[B]
46-
def (x: F[A]) map [A, B] (f: A => B) = x.flatMap(f `andThen` pure)
45+
def [A, B](x: F[A]) flatMap (f: A => F[B]): F[B]
46+
def [A, B](x: F[A]) map (f: A => B) = x.flatMap(f `andThen` pure)
4747

4848
def pure[A](x: A): F[A]
4949
}
5050

5151
given listMonad: Monad[List] {
52-
def (xs: List[A]) flatMap [A, B] (f: A => List[B]): List[B] =
52+
def [A, B](xs: List[A]) flatMap (f: A => List[B]): List[B] =
5353
xs.flatMap(f)
5454
def pure[A](x: A): List[A] =
5555
List(x)
5656
}
5757

5858
given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] {
59-
def (r: Ctx => A) flatMap [A, B] (f: A => Ctx => B): Ctx => B =
59+
def [A, B](r: Ctx => A) flatMap (f: A => Ctx => B): Ctx => B =
6060
ctx => f(r(ctx))(ctx)
6161
def pure[A](x: A): Ctx => A =
6262
ctx => x

library/src-bootstrapped/scala/IArray.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ object opaques {
2323
def (arr: IArray[Long]) apply (n: Int): Long = arr.asInstanceOf[Array[Long]].apply(n)
2424
def (arr: IArray[Float]) apply (n: Int): Float = arr.asInstanceOf[Array[Float]].apply(n)
2525
def (arr: IArray[Double]) apply (n: Int): Double = arr.asInstanceOf[Array[Double]].apply(n)
26-
def (arr: IArray[T]) apply[T <: Object] (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
27-
def (arr: IArray[T]) apply[T] (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
26+
def [T <: Object](arr: IArray[T]) apply (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
27+
def [T](arr: IArray[T]) apply (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
2828

2929
/** The number of elements in an immutable array
3030
* @param arr the immutable array
@@ -37,7 +37,7 @@ object opaques {
3737
def (arr: IArray[Float]) length: Int = arr.asInstanceOf[Array[Float]].length
3838
def (arr: IArray[Double]) length: Int = arr.asInstanceOf[Array[Double]].length
3939
def (arr: IArray[Object]) length: Int = arr.asInstanceOf[Array[Object]].length
40-
def (arr: IArray[T]) length[T] : Int = arr.asInstanceOf[Array[T]].length
40+
def [T](arr: IArray[T]) length: Int = arr.asInstanceOf[Array[T]].length
4141
}
4242
}
4343
type IArray[+T] = opaques.IArray[T]

library/src-bootstrapped/scala/quoted/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ package object quoted {
44

55
implicit object ExprOps {
66
@deprecated("Use scala.quoted.Expr.apply instead", "0.19.0")
7-
def (x: T) toExpr[T: Liftable](given QuoteContext): Expr[T] = Expr(x)
7+
def [T: Liftable](x: T) toExpr (given QuoteContext): Expr[T] = Expr(x)
88
}
99
}

tests/neg/capture1.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ object Test extends App {
44

55
val l: mutable.Seq[String] = mutable.ArrayBuffer()
66

7-
def (xs: List[T]) emap[T, U] (f: T => U): List[U] = xs.map(f)
7+
def [T, U](xs: List[T]) emap (f: T => U): List[U] = xs.map(f)
88

9-
def (xs: List[T]) ereduce[T] (f: (T, T) => T): T = xs.reduceLeft(f)
9+
def [T](xs: List[T]) ereduce (f: (T, T) => T): T = xs.reduceLeft(f)
1010

11-
def (xs: mutable.Seq[T]) append[T] (ys: mutable.Seq[T]): mutable.Seq[T] = xs ++ ys
11+
def [T](xs: mutable.Seq[T]) append (ys: mutable.Seq[T]): mutable.Seq[T] = xs ++ ys
1212

1313
List(l, mutable.ArrayBuffer(1))
1414
.emap(list => list)

tests/neg/i7060.scala

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

1313
def res[T](given b: Box[T]): T = b.t
1414

15-
def (e: T) ensure[T](cond: (given Box[T]) => Boolean): T = {
15+
def [T](e: T) ensure (cond: (given Box[T]) => Boolean): T = {
1616
if (cond(given Box(e))) e
1717
else throw new AssertionError("condition not fulfilled")
1818
}

tests/pos/i6734.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
object Bug {
22

3-
def (ab: (A, B)) pipe2[A, B, Z](f: (A, B) => Z): Z = f(ab._1, ab._2)
3+
def [A, B, Z](ab: (A, B)) pipe2(f: (A, B) => Z): Z = f(ab._1, ab._2)
44

5-
def (a: A) leftErr[A, B](b: B): A = (a, b).pipe2((a, b) => a) //Did not compile before.
6-
def (a: A) leftOk1[A, B](b: B): A = Tuple2(a, b).pipe2((a, b) => a) //Compiles
7-
def (a: A) leftOk2[A, B](b: B): A = {
5+
def [A, B](a: A) leftErr(b: B): A = (a, b).pipe2((a, b) => a) //Did not compile before.
6+
def [A, B](a: A) leftOk1(b: B): A = Tuple2(a, b).pipe2((a, b) => a) //Compiles
7+
def [A, B](a: A) leftOk2(b: B): A = {
88
val t = (a, b)
99
t.pipe2((a, b) => a) //Compiles
1010
}

tests/pos/i6847.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
trait Syntax[F[_]] {
2-
def (a: A) ret[A]: F[A]
2+
def [A](a: A) ret: F[A]
33
}
44

55
trait Instance[A]
66

77
implicit val instanceSyntax: Syntax[Instance] = new Syntax[Instance] {
8-
def (a: A) ret[A]: Instance[A] = new Instance[A] {}
8+
def [A](a: A) ret: Instance[A] = new Instance[A] {}
99
}
1010

1111
object Instance {

tests/pos/i6900.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Test {
2-
given bla[A]: { def (a: A) foo[C]: C => A = _ => a }
2+
given bla[A]: { def [C](a: A) foo: C => A = _ => a }
33

44
1.foo.foo
55
1.foo.foo[String]

tests/pos/i7041.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import scala.util.control.NonLocalReturns._
22

3-
inline def (op: => T) rescue[T, E <: Throwable] (fallback: PartialFunction[E, T]) =
3+
inline def [T, E <: Throwable](op: => T) rescue (fallback: PartialFunction[E, T]) =
44
try op
55
catch {
66
case ex: ReturnThrowable[_] => throw ex

tests/pos/i7087.scala

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

99
given {
10-
def (tup: T) g[T](given Foo: F[T]) = ???
10+
def [T](tup: T) g (given Foo: F[T]) = ???
1111
}
1212

1313
def f(x: G[Int])(given Foo: String) = x.g

tests/pos/mirror-implicit-scope.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ object Test {
44
object K0 {
55
type Generic[T] = Mirror { type Scope = K0.type ; type MirroredType = T ; type MirroredElemTypes }
66
given {
7-
inline def (gen: Generic[T]) toRepr[T <: Product](t: T): gen.MirroredElemTypes = Tuple.fromProduct(t).asInstanceOf
7+
inline def [T <: Product](gen: Generic[T]) 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[_] }
1313
given {
14-
inline def (gen: Generic[F]) toRepr[F[_] <: Product, T](t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
14+
inline def [F[_] <: Product, T](gen: Generic[F]) toRepr (t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
1515
}
1616
}
1717

tests/pos/postconditions.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
object PostConditions {
1+
object PostConditions with
22
opaque type WrappedResult[T] = T
33

44
def result[T](given r: WrappedResult[T]): T = r
55

6-
def (x: T) ensuring [T](condition: (given WrappedResult[T]) => Boolean): T = {
6+
def [T](x: T) ensuring (condition: (given WrappedResult[T]) => Boolean): T =
77
given WrappedResult[T] = x
88
assert(condition)
99
x
10-
}
11-
}
1210

13-
object Test {
11+
object Test with
1412
import PostConditions.{ensuring, result}
1513
val s = List(1, 2, 3).sum.ensuring(result == 6)
16-
}

0 commit comments

Comments
 (0)