Skip to content
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

Syntax Change: Allow '.' in front of extension method #7914

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3105,7 +3105,7 @@ object Parsers {
* | this ParamClause ParamClauses `=' ConstrExpr
* DefDcl ::= DefSig `:' Type
* DefSig ::= id [DefTypeParamClause] DefParamClauses
* | ExtParamClause [nl] id DefParamClauses
* | ExtParamClause [nl] [‘.’] id DefParamClauses
*/
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
def scala2ProcedureSyntax(resultTypeStr: String) = {
Expand Down Expand Up @@ -3134,7 +3134,11 @@ object Parsers {
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
}
else {
def extParamss() = try paramClause(0, prefix = true) :: Nil finally newLineOpt()
def extParamss() =
try paramClause(0, prefix = true) :: Nil
finally
if in.token == DOT then in.nextToken()
else newLineOpt()
val (leadingTparams, leadingVparamss, flags) =
if in.token == LBRACKET then
(typeParamClause(ParamOwner.Def), extParamss(), Method | Extension)
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/contributing/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ But you can also do:
assertPositioned(tree.reporting(s"Tree is: $result"))
```

`def (a: A) reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.
`def (a: A).reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.

## Printing out trees after phases
To print out the trees you are compiling after the FrontEnd (scanner, parser, namer, typer) phases:
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ ValDcl ::= ids ‘:’ Type
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
DefSig ::= id [DefTypeParamClause] DefParamClauses
| ExtParamClause [nl] id DefParamClauses
| ExtParamClause [nl] [‘.’] id DefParamClauses
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound

Def ::= ‘val’ PatDef
Expand Down
20 changes: 12 additions & 8 deletions docs/docs/reference/contextual/extension-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Extension methods allow one to add methods to a type after the type is defined.
```scala
case class Circle(x: Double, y: Double, radius: Double)

def (c: Circle) circumference: Double = c.radius * math.Pi * 2
def (c: Circle).circumference: Double = c.radius * math.Pi * 2
```

Like regular methods, extension methods can be invoked with infix `.`:
Expand Down Expand Up @@ -42,7 +42,7 @@ As an example, consider an extension method `longestStrings` on `Seq[String]` de

```scala
trait StringSeqOps {
def (xs: Seq[String]) longestStrings = {
def (xs: Seq[String]).longestStrings = {
val maxLength = xs.map(_.length).max
xs.filter(_.length == maxLength)
}
Expand Down Expand Up @@ -80,22 +80,26 @@ So `circle.circumference` translates to `CircleOps.circumference(circle)`, provi

### Operators

The extension method syntax also applies to the definition of operators.
In each case the definition syntax mirrors the way the operator is applied.
The extension method syntax also applies to the definition of operators.
In this case it is allowed and preferable to omit the period between the leading parameter list
and the operator. In each case the definition syntax mirrors the way the operator is applied.
Examples:
```scala
def (x: String) < (y: String) = ...
def (x: Elem) +: (xs: Seq[Elem]) = ...
def (x: Number) min (y: Number) = ...

"ab" < "c"
1 +: List(2, 3)
x min 3
```
The two definitions above translate to
The three definitions above translate to
```scala
def < (x: String)(y: String) = ...
def +: (xs: Seq[Elem])(x: Elem) = ...
def min(x: Number)(y: Number) = ...
```
Note that swap of the two parameters `x` and `xs` when translating
Note the swap of the two parameters `x` and `xs` when translating
the right-binding operator `+:` to an extension method. This is analogous
to the implementation of right binding operators as normal methods.

Expand Down Expand Up @@ -144,7 +148,7 @@ If a given extension is anonymous (as in the last clause), its name is synthesiz
The extensions above are equivalent to the following regular given instances where the implemented parent is `AnyRef` and the parameters in the `extension` clause are repeated in each extension method definition:
```scala
given stringOps: AnyRef {
def (xs: Seq[String]) longestStrings: Seq[String] = {
def (xs: Seq[String]).longestStrings: Seq[String] = {
val maxLength = xs.map(_.length).max
xs.filter(_.length == maxLength)
}
Expand All @@ -165,7 +169,7 @@ Here are the syntax changes for extension methods and given extensions relative
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.
```
DefSig ::= ...
| ExtParamClause [nl] id DefParamClauses
| ExtParamClause [nl] [‘.’] id DefParamClauses
GivenDef ::= ...
[id ‘:’] ‘extension’ ExtParamClause {GivenParamClause} ExtMethods
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/contextual/implicit-function-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ object PostConditions {

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

def (x: T) ensuring[T](condition: (given WrappedResult[T]) => Boolean): T = {
def (x: T).ensuring[T](condition: (given WrappedResult[T]) => Boolean): T = {
assert(condition(given x))
x
}
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/contextual/relationship-implicits.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ will map to given clauses instead.

Extension methods have no direct counterpart in Scala 2, but they can be simulated with implicit classes. For instance, the extension method
```scala
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
def (c: Circle).circumference: Double = c.radius * math.Pi * 2
```
could be simulated to some degree by
```scala
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/reference/contextual/typeclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ with canonical implementations defined by given instances. Here are some example

```scala
trait SemiGroup[T] with
def (x: T) combine (y: T): T
def (x: T).combine(y: T): T

trait Monoid[T] extends SemiGroup[T] with
def unit: T
Expand All @@ -20,11 +20,11 @@ object Monoid with
def apply[T](given Monoid[T]) = summon[Monoid[T]]

given Monoid[String] with
def (x: String) combine (y: String): String = x.concat(y)
def (x: String).combine(y: String): String = x.concat(y)
def unit: String = ""

given Monoid[Int] with
def (x: Int) combine (y: Int): Int = x + y
def (x: Int).combine(y: Int): Int = x + y
def unit: Int = 0

def sum[T: Monoid](xs: List[T]): T =
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/dropped-features/package-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def b = a._2
case class C()

implicit object Cops {
def (x: C) pair (y: C) = (x, y)
def (x: C).pair(y: C) = (x, y)
}
```
There may be several source files in a package containing such toplevel definitions, and source files can freely mix toplevel value, method, and type definitions with classes and objects.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ This might be used to then perform an implicit search as in:


```scala
inline def (sc: StringContext) showMe(args: =>Any*): String = ${ showMeExpr('sc, 'args) }
inline def (sc: StringContext).showMe(args: =>Any*): String = ${ showMeExpr('sc, 'args) }

private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(given qctx: QuoteContext): Expr[String] = {
argsExpr match {
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/other-new-features/opaques.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ object Access {

def (x: Permissions) & (y: Permissions): Permissions = x | y
def (x: PermissionChoice) | (y: PermissionChoice): PermissionChoice = x | y
def (x: Permissions) is (y: Permissions) = (x & y) == y
def (x: Permissions) isOneOf (y: PermissionChoice) = (x & y) != 0
def (x: Permissions).is(y: Permissions) = (x & y) == y
def (x: Permissions).isOneOf(y: PermissionChoice) = (x & y) != 0

val NoPermission: Permission = 0
val ReadOnly: Permission = 1
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/reference/other-new-features/tupled-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Examples
* @tparam Args the tuple type with the same types as the function arguments of F
* @tparam R the return type of F
*/
def (f: F) tupled[F, Args <: Tuple, R](given tf: TupledFunction[F, Args => R]): Args => R = tf.tupled(f)
def [F, Args <: Tuple, R](f: F).tupled(given tf: TupledFunction[F, Args => R]): Args => R = tf.tupled(f)
```

`TupledFunction` can be used to generalize the `Function.untupled` methods to functions of any arities ([full example](https://github.com/lampepfl/dotty/blob/master/tests/run/tupled-function-untupled.scala))
Expand All @@ -58,7 +58,7 @@ def (f: F) tupled[F, Args <: Tuple, R](given tf: TupledFunction[F, Args => R]):
* @tparam Args the tuple type with the same types as the function arguments of F
* @tparam R the return type of F
*/
def (f: Args => R) untupled[F, Args <: Tuple, R](given tf: TupledFunction[F, Args => R]): F = tf.untupled(f)
def [F, Args <: Tuple, R](f: Args => R).untupled(given tf: TupledFunction[F, Args => R]): F = tf.untupled(f)
```

`TupledFunction` can also be used to generalize the [`Tuple1.compose`](https://github.com/lampepfl/dotty/blob/master/tests/run/tupled-function-compose.scala) and [`Tuple1.andThen`](https://github.com/lampepfl/dotty/blob/master/tests/run/tupled-function-andThen.scala) methods to compose functions of larger arities and with functions that return tuples.
Expand All @@ -72,7 +72,7 @@ def (f: Args => R) untupled[F, Args <: Tuple, R](given tf: TupledFunction[F, Arg
* @tparam GArgs the tuple type with the same types as the function arguments of G
* @tparam R the return type of F
*/
def (f: F) compose[F, G, FArgs <: Tuple, GArgs <: Tuple, R](g: G)(given tg: TupledFunction[G, GArgs => FArgs], tf: TupledFunction[F, FArgs => R]): GArgs => R = {
def [F, G, FArgs <: Tuple, GArgs <: Tuple, R](f: F).compose(g: G)(given tg: TupledFunction[G, GArgs => FArgs], tf: TupledFunction[F, FArgs => R]): GArgs => R = {
(x: GArgs) => tf.tupled(f)(tg.tupled(g)(x))
}
```
30 changes: 15 additions & 15 deletions library/src-bootstrapped/scala/IArray.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,27 @@ object opaques
* @param n the index of the element to select
* @return the element of the array at the given index
*/
def (arr: IArray[Byte]) apply (n: Int): Byte = arr.asInstanceOf[Array[Byte]].apply(n)
def (arr: IArray[Short]) apply (n: Int): Short = arr.asInstanceOf[Array[Short]].apply(n)
def (arr: IArray[Char]) apply (n: Int): Char = arr.asInstanceOf[Array[Char]].apply(n)
def (arr: IArray[Int]) apply (n: Int): Int = arr.asInstanceOf[Array[Int]].apply(n)
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[Byte]).apply(n: Int): Byte = arr.asInstanceOf[Array[Byte]].apply(n)
def (arr: IArray[Short]).apply(n: Int): Short = arr.asInstanceOf[Array[Short]].apply(n)
def (arr: IArray[Char]).apply(n: Int): Char = arr.asInstanceOf[Array[Char]].apply(n)
def (arr: IArray[Int]).apply(n: Int): Int = arr.asInstanceOf[Array[Int]].apply(n)
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 [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
*/
def (arr: IArray[Byte]) length: Int = arr.asInstanceOf[Array[Byte]].length
def (arr: IArray[Short]) length: Int = arr.asInstanceOf[Array[Short]].length
def (arr: IArray[Char]) length: Int = arr.asInstanceOf[Array[Char]].length
def (arr: IArray[Int]) length: Int = arr.asInstanceOf[Array[Int]].length
def (arr: IArray[Long]) length: Int = arr.asInstanceOf[Array[Long]].length
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[Byte]).length: Int = arr.asInstanceOf[Array[Byte]].length
def (arr: IArray[Short]).length: Int = arr.asInstanceOf[Array[Short]].length
def (arr: IArray[Char]).length: Int = arr.asInstanceOf[Array[Char]].length
def (arr: IArray[Int]).length: Int = arr.asInstanceOf[Array[Int]].length
def (arr: IArray[Long]).length: Int = arr.asInstanceOf[Array[Long]].length
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 [T](arr: IArray[T]) length: Int = arr.asInstanceOf[Array[T]].length

/** Returns this array concatenated with the given array. */
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/extmethods-tparams.scala
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def (self: T) foo[T] = ??? // error
def (self: T).foo[T] = ??? // error
def [T1](self: T1) bar[T2] = ??? // error // error
2 changes: 1 addition & 1 deletion tests/neg-macros/i6432/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import scala.quoted.autolift.given
import scala.quoted.matching._

object Macro {
inline def (sc: => StringContext) foo (args: String*): Unit = ${ impl('sc) }
inline def (sc: => StringContext).foo(args: String*): Unit = ${ impl('sc) }

def impl(sc: Expr[StringContext])(given qctx: QuoteContext): Expr[Unit] = {
import qctx.tasty.{_, given}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/i6432b/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import scala.quoted.autolift.given
import scala.quoted.matching._

object Macro {
inline def (sc: => StringContext) foo (args: String*): Unit = ${ impl('sc) }
inline def (sc: => StringContext).foo(args: String*): Unit = ${ impl('sc) }

def impl(sc: Expr[StringContext])(given qctx: QuoteContext): Expr[Unit] = {
import qctx.tasty.{_, given}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/reflect-inline/assert_1.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import scala.quoted._

object api {
inline def (inline x: String) stripMargin2: String =
inline def (inline x: String).stripMargin2: String =
${ stripImpl(x) }

private def stripImpl(x: String)(given qctx: QuoteContext): Expr[String] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import scala.language.implicitConversions

object Macro {

implicit inline def (strCtx: => StringContext) f2 (args: =>Any*): String = ${FIntepolator.apply('strCtx, 'args)}
implicit inline def (strCtx: => StringContext).f2(args: =>Any*): String = ${FIntepolator.apply('strCtx, 'args)}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import scala.language.implicitConversions

object Macro {

implicit inline def (strCtx: => StringContext) f3 (args: =>Any*): String = ${FIntepolator.apply('strCtx, 'args)}
implicit inline def (strCtx: => StringContext).f3(args: =>Any*): String = ${FIntepolator.apply('strCtx, 'args)}

}

Expand Down
4 changes: 2 additions & 2 deletions tests/neg/extension-methods.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
object Test {

implicit object O {
def (x: String) l1 = x.length
def (x: String).l1 = x.length
def l1(x: Int) = x * x
def l2(x: String) = x.length
}
Expand All @@ -11,7 +11,7 @@ object Test {
1.l1 // error

given [T](xs: List[T]) extended with {
def (x: Int) f1: T = ??? // error: No extension method allowed here, since collective parameters are given
def (x: Int).f1: T = ??? // error: No extension method allowed here, since collective parameters are given
def f2[T]: T = ??? // error: T is already defined as type T
def f3(xs: List[T]) = ??? // error: xs is already defined as value xs
}
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/extmethod-override.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class A {
def f(x: Int)(y: Int): Int = 0
def (x: Int) g (y: Int): Int = 1
def (x: Int).g(y: Int): Int = 1
}
class B extends A {
override def (x: Int) f (y: Int): Int = 1 // error
override def (x: Int).f(y: Int): Int = 1 // error
override def g(x: Int)(y: Int): Int = 0 // error
}
10 changes: 5 additions & 5 deletions tests/neg/i5773.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
trait Semigroup[T] {
def (lhs: T) append (rhs: T): T
def (lhs: Int) appendS (rhs: T): T = ???
def (lhs: T).append(rhs: T): T
def (lhs: Int).appendS(rhs: T): T = ???
}

object Semigroup {
implicit object stringAppend extends Semigroup[String] {
override def (lhs: String) append (rhs: String): String = lhs + rhs
override def (lhs: String).append(rhs: String): String = lhs + rhs
}

implicit def sumSemigroup[N](implicit N: Numeric[N]): Semigroup[N] = new {
override def (lhs: N) append (rhs: N): N = N.plus(lhs, rhs)
def (lhs: Int) appendS (rhs: N): N = ??? // N.plus(lhs, rhs)
override def (lhs: N).append(rhs: N): N = N.plus(lhs, rhs)
def (lhs: Int).appendS(rhs: N): N = ??? // N.plus(lhs, rhs)
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i6762b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ type Liftable
given Liftable = ???

implicit object ExprOps {
def (x: T) toExpr[T](given Liftable): Expr[T] = ???
def (x: T).toExpr[T](given Liftable): Expr[T] = ???
}
2 changes: 1 addition & 1 deletion tests/neg/i6779.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ type G[T]
type Stuff
given Stuff = ???

def (x: T) f[T](given Stuff): F[T] = ???
def (x: T).f[T](given Stuff): F[T] = ???


def g1[T](x: T): F[G[T]] = x.f(given summon[Stuff]) // error
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i7438.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Tr[+A]
inline def (tr: Tr[A]) map[A, B](f: A => B): Tr[B] = ???
inline def (tr: Tr[A]).map[A, B](f: A => B): Tr[B] = ???

def (d: Double) func: None.type => Some[Double] = ???
def (d: Double).func: None.type => Some[Double] = ???

def run[A](query: None.type => Some[A]): Some[A] = ???

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/indent.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object Test {

def (x: Int) gt (y: Int) = x > y
def (x: Int).gt(y: Int) = x > y
val y3 =
if (1) max 10 gt 0 // error: end of statement expected but integer literal found // error // error // error
1
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/opaque-bounds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ object Access {

def (x: Permissions) & (y: Permissions): Permissions = x & y
def (x: PermissionChoice) | (y: PermissionChoice): PermissionChoice = x | y
def (x: Permissions) is (y: Permissions) = (x & y) == y
def (x: Permissions) isOneOf (y: PermissionChoice) = (x & y) != 0
def (x: Permissions).is(y: Permissions) = (x & y) == y
def (x: Permissions).isOneOf(y: PermissionChoice) = (x & y) != 0

val NoPermission: Permission = 0
val ReadOnly: Permission = 1
Expand Down
Loading