Skip to content

Commit b44c6af

Browse files
authored
Merge pull request #7455 from dotty-staging/change-extmethod-tparams
Syntax change for type parameters of extension methods
2 parents d06a5ff + 9af572d commit b44c6af

34 files changed

+130
-125
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
@@ -3089,10 +3089,11 @@ object Parsers {
30893089
}
30903090
}
30913091

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

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ class CompilationTests extends ParallelTesting {
141141
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
142142
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
143143
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
144-
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings"))
144+
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")),
145+
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings"))
145146
).checkExpectedErrors()
146147
}
147148

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: 11 additions & 9 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,26 +151,28 @@ 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

164-
As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause.
165-
164+
If an extension method has type parameters, they come immediately after the `def` and are followed by the extended parameter. When calling a generic extension method, any explicitly given type arguments follow the method name. So the `second` method can be instantiated as follows:
165+
```scala
166+
List(1, 2, 3).second[Int]
167+
```
166168

167169
### Syntax
168170

169171
The required syntax extension just adds one clause for extension methods relative
170172
to the [current syntax](../../internals/syntax.md).
171173
```
172174
DefSig ::= ...
173-
| ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses
175+
| ExtParamClause [nl] id DefParamClauses
174176
GivenDef ::= ...
175177
[GivenSig ‘:’] [ExtParamClause] TemplateBody
176178
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
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def (self: T) foo[T] = ??? // error
2+
def [T1](self: T1) bar[T2] = ??? // error // error

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)