Skip to content

Syntax change for type parameters of extension methods #7455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,16 +208,13 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro
case tree: DefDef if tree.mods.is(Extension) =>
tree.vparamss match {
case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
check(vparams2)
check(tree.tparams)
check(vparams2)
check(vparams1)
check(rest)
case vparams1 :: rest =>
check(vparams1)
check(tree.tparams)
check(rest)
case _ =>
check(tree.tparams)
check(tree.vparamss)
}
check(tree.tpt)
check(tree.rhs)
Expand Down
11 changes: 4 additions & 7 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -754,9 +754,10 @@ object Trees {
def unforced: LazyTree[T] = preRhs
protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x

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

/** mods class name template or
Expand Down Expand Up @@ -789,10 +790,6 @@ object Trees {

def parents: List[Tree[T]] = parentsOrDerived // overridden by DerivingTemplate
def derived: List[untpd.Tree] = Nil // overridden by DerivingTemplate

override def disableOverlapChecks = true
// disable overlaps checks since templates of instance definitions have their
// `given` clause come last, which means that the constructor span can contain the parent spans.
}


Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ object Decorators {
}
}

implicit object reportDeco {
def (x: T) reporting[T](
implicit class reportDeco[T](x: T) extends AnyVal {
def reporting(
op: (given WrappedResult[T]) => String,
printer: config.Printers.Printer = config.Printers.default): T = {
printer.println(op(given WrappedResult(x)))
Expand Down
43 changes: 26 additions & 17 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3088,10 +3088,11 @@ object Parsers {
}
}

/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
* | this ParamClause ParamClauses `=' ConstrExpr
* DefDcl ::= DefSig `:' Type
* DefSig ::= [‘(’ DefParam ‘)’ [nl]] id [DefTypeParamClause] ParamClauses
/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
* | this ParamClause ParamClauses `=' ConstrExpr
* DefDcl ::= DefSig `:' Type
* DefSig ::= id [DefTypeParamClause] DefParamClauses
* | ExtParamClause [nl] id DefParamClauses
*/
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
def scala2ProcedureSyntax(resultTypeStr: String) = {
Expand Down Expand Up @@ -3120,27 +3121,35 @@ object Parsers {
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
}
else {
val (leadingParamss, flags) =
if (in.token == LPAREN)
try (paramClause(0, prefix = true) :: Nil, Method | Extension)
finally newLineOpt()
def extParamss = try paramClause(0, prefix = true) :: Nil finally newLineOpt()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: def extParamss() since the function is effectful

val (leadingTparams, leadingVparamss, flags) =
if in.token == LBRACKET then
(typeParamClause(ParamOwner.Def), extParamss, Method | Extension)
else if in.token == LPAREN then
(Nil, extParamss, Method | Extension)
else
(Nil, Method)
(Nil, Nil, Method)
val mods1 = addFlag(mods, flags)
val ident = termIdent()
val name = ident.name.asTermName
val tparams = typeParamClauseOpt(ParamOwner.Def)
val vparamss = paramClauses() match {
case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(ident.name) =>
rparams :: leadingParamss ::: rparamss
val tparams =
if in.token == LBRACKET then
if flags.is(Extension) then
if leadingTparams.isEmpty then
deprecationWarning("type parameters in extension methods should be written after `def`")
else
syntaxError("no type parameters allowed here")
typeParamClause(ParamOwner.Def)
else leadingTparams
val vparamss = paramClauses() match
case rparams :: rparamss if leadingVparamss.nonEmpty && !isLeftAssoc(ident.name) =>
rparams :: leadingVparamss ::: rparamss
case rparamss =>
leadingParamss ::: rparamss
}
leadingVparamss ::: rparamss
var tpt = fromWithinReturnType {
if (in.token == SUBTYPE && mods.is(Inline)) {
if in.token == SUBTYPE && mods.is(Inline) then
in.nextToken()
TypeBoundsTree(EmptyTree, toplevelTyp())
}
else typedOpt()
}
if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE)
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ Dcl ::= RefineDcl
ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
DefSig ::= [‘(’ DefParam ‘)’ [nl]] id
[DefTypeParamClause] DefParamClauses
DefSig ::= id [DefTypeParamClause] DefParamClauses
| ExtParamClause [nl] id DefParamClauses
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound

Def ::= ‘val’ PatDef
Expand Down
14 changes: 7 additions & 7 deletions docs/docs/reference/contextual/extension-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ given stringOps: {
}

given {
def (xs: List[T]) second[T] = xs.tail.head
def [T](xs: List[T]) second = xs.tail.head
}
```
If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method.
Expand All @@ -106,8 +106,8 @@ as well as any type parameters of these extension methods into the given instanc
For instance, here is a given instance with two extension methods.
```scala
given listOps: {
def (xs: List[T]) second[T]: T = xs.tail.head
def (xs: List[T]) third[T]: T = xs.tail.tail.head
def [T](xs: List[T]) second: T = xs.tail.head
def [T](xs: List[T]) third: T = xs.tail.tail.head
}
```
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.
Expand Down Expand Up @@ -151,13 +151,13 @@ to the implementation of right binding operators as normal methods.
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:

```scala
def (xs: List[T]) second [T] =
def [T](xs: List[T]) second =
xs.tail.head

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

def (x: T) + [T : Numeric](y: T): T =
def [T: Numeric](x: T) + (y: T): T =
summon[Numeric[T]].plus(x, y)
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This need to be updated. Also I would add a paragraph about the position of explicit type parameters at call site.

Expand All @@ -170,7 +170,7 @@ The required syntax extension just adds one clause for extension methods relativ
to the [current syntax](../../internals/syntax.md).
```
DefSig ::= ...
| ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses
| ExtParamClause [nl] id DefParamClauses
GivenDef ::= ...
[GivenSig ‘:’] [ExtParamClause] TemplateBody
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
Expand Down
10 changes: 5 additions & 5 deletions docs/docs/reference/contextual/typeclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,25 @@ def sum[T: Monoid](xs: List[T]): T =

```scala
trait Functor[F[_]] {
def (x: F[A]) map [A, B] (f: A => B): F[B]
def [A, B](x: F[A]) map (f: A => B): F[B]
}

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

def pure[A](x: A): F[A]
}

given listMonad: Monad[List] {
def (xs: List[A]) flatMap [A, B] (f: A => List[B]): List[B] =
def [A, B](xs: List[A]) flatMap (f: A => List[B]): List[B] =
xs.flatMap(f)
def pure[A](x: A): List[A] =
List(x)
}

given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] {
def (r: Ctx => A) flatMap [A, B] (f: A => Ctx => B): Ctx => B =
def [A, B](r: Ctx => A) flatMap (f: A => Ctx => B): Ctx => B =
ctx => f(r(ctx))(ctx)
def pure[A](x: A): Ctx => A =
ctx => x
Expand Down
6 changes: 3 additions & 3 deletions library/src-bootstrapped/scala/IArray.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ object opaques {
def (arr: IArray[Long]) apply (n: Int): Long = arr.asInstanceOf[Array[Long]].apply(n)
def (arr: IArray[Float]) apply (n: Int): Float = arr.asInstanceOf[Array[Float]].apply(n)
def (arr: IArray[Double]) apply (n: Int): Double = arr.asInstanceOf[Array[Double]].apply(n)
def (arr: IArray[T]) apply[T <: Object] (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
def (arr: IArray[T]) apply[T] (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
def [T <: Object](arr: IArray[T]) apply (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)
def [T](arr: IArray[T]) apply (n: Int): T = arr.asInstanceOf[Array[T]].apply(n)

/** The number of elements in an immutable array
* @param arr the immutable array
Expand All @@ -37,7 +37,7 @@ object opaques {
def (arr: IArray[Float]) length: Int = arr.asInstanceOf[Array[Float]].length
def (arr: IArray[Double]) length: Int = arr.asInstanceOf[Array[Double]].length
def (arr: IArray[Object]) length: Int = arr.asInstanceOf[Array[Object]].length
def (arr: IArray[T]) length[T] : Int = arr.asInstanceOf[Array[T]].length
def [T](arr: IArray[T]) length: Int = arr.asInstanceOf[Array[T]].length
}
}
type IArray[+T] = opaques.IArray[T]
Expand Down
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/quoted/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ package object quoted {

implicit object ExprOps {
@deprecated("Use scala.quoted.Expr.apply instead", "0.19.0")
def (x: T) toExpr[T: Liftable](given QuoteContext): Expr[T] = Expr(x)
def [T: Liftable](x: T) toExpr (given QuoteContext): Expr[T] = Expr(x)
}
}
6 changes: 3 additions & 3 deletions tests/neg/capture1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ object Test extends App {

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

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

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

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

List(l, mutable.ArrayBuffer(1))
.emap(list => list)
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i7060.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object PostConditions {

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

def (e: T) ensure[T](cond: (given Box[T]) => Boolean): T = {
def [T](e: T) ensure (cond: (given Box[T]) => Boolean): T = {
if (cond(given Box(e))) e
else throw new AssertionError("condition not fulfilled")
}
Expand Down
8 changes: 4 additions & 4 deletions tests/pos/i6734.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
object Bug {

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

def (a: A) leftErr[A, B](b: B): A = (a, b).pipe2((a, b) => a) //Did not compile before.
def (a: A) leftOk1[A, B](b: B): A = Tuple2(a, b).pipe2((a, b) => a) //Compiles
def (a: A) leftOk2[A, B](b: B): A = {
def [A, B](a: A) leftErr(b: B): A = (a, b).pipe2((a, b) => a) //Did not compile before.
def [A, B](a: A) leftOk1(b: B): A = Tuple2(a, b).pipe2((a, b) => a) //Compiles
def [A, B](a: A) leftOk2(b: B): A = {
val t = (a, b)
t.pipe2((a, b) => a) //Compiles
}
Expand Down
4 changes: 2 additions & 2 deletions tests/pos/i6847.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
trait Syntax[F[_]] {
def (a: A) ret[A]: F[A]
def [A](a: A) ret: F[A]
}

trait Instance[A]

implicit val instanceSyntax: Syntax[Instance] = new Syntax[Instance] {
def (a: A) ret[A]: Instance[A] = new Instance[A] {}
def [A](a: A) ret: Instance[A] = new Instance[A] {}
}

object Instance {
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i6900.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Test {
given bla[A]: { def (a: A) foo[C]: C => A = _ => a }
given bla[A]: { def [C](a: A) foo: C => A = _ => a }

1.foo.foo
1.foo.foo[String]
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i7041.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.util.control.NonLocalReturns._

inline def (op: => T) rescue[T, E <: Throwable] (fallback: PartialFunction[E, T]) =
inline def [T, E <: Throwable](op: => T) rescue (fallback: PartialFunction[E, T]) =
try op
catch {
case ex: ReturnThrowable[_] => throw ex
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i7087.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type F[T] = T match {
}

given {
def (tup: T) g[T](given Foo: F[T]) = ???
def [T](tup: T) g (given Foo: F[T]) = ???
}

def f(x: G[Int])(given Foo: String) = x.g
4 changes: 2 additions & 2 deletions tests/pos/mirror-implicit-scope.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ object Test {
object K0 {
type Generic[T] = Mirror { type Scope = K0.type ; type MirroredType = T ; type MirroredElemTypes }
given {
inline def (gen: Generic[T]) toRepr[T <: Product](t: T): gen.MirroredElemTypes = Tuple.fromProduct(t).asInstanceOf
inline def [T <: Product](gen: Generic[T]) toRepr (t: T): gen.MirroredElemTypes = Tuple.fromProduct(t).asInstanceOf
}
}

object K1 {
type Generic[F[_]] = Mirror { type Scope = K1.type ; type MirroredType = F ; type MirroredElemTypes[_] }
given {
inline def (gen: Generic[F]) toRepr[F[_] <: Product, T](t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
inline def [F[_] <: Product, T](gen: Generic[F]) toRepr (t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
}
}

Expand Down
9 changes: 3 additions & 6 deletions tests/pos/postconditions.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
object PostConditions {
object PostConditions with
opaque type WrappedResult[T] = T

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

def (x: T) ensuring [T](condition: (given WrappedResult[T]) => Boolean): T = {
def [T](x: T) ensuring (condition: (given WrappedResult[T]) => Boolean): T =
given WrappedResult[T] = x
assert(condition)
x
}
}

object Test {
object Test with
import PostConditions.{ensuring, result}
val s = List(1, 2, 3).sum.ensuring(result == 6)
}
Loading