From a708680ceec7dd6df865cc2dacab1084e2035cb5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 22:48:16 +0200 Subject: [PATCH 01/56] Establish companionship directly Don't use synthetic companion methods to achieve this. The advantages of the direct approach are: - it's overall simpler - it can be more easily extended to opaque types --- compiler/src/dotty/tools/dotc/core/Phases.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4f91adc45a54..5468cef6e01b 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -364,9 +364,9 @@ object Phases { assert(start <= Periods.MaxPossiblePhaseId, s"Too many phases, Period bits overflow") myBase = base myPeriod = Period(NoRunId, start, end) - myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes - myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses - myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked + myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes + myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses + myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked mySameMembersStartId = if (changesMembers) id else prev.sameMembersStartId mySameParentsStartId = if (changesParents) id else prev.sameParentsStartId mySameBaseTypesStartId = if (changesBaseTypes) id else prev.sameBaseTypesStartId From c78ee104469ecdb49f35b9b8180dba6af28c5a29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 14 Sep 2018 18:13:39 +0200 Subject: [PATCH 02/56] Compare two schemes how extensions could be used to encode typeclasses 1. extensions, common blocks + ThisType 2. Special syntax for extension methods The second scheme is a bit more verbose for some tasks but a lot simpler overall. --- tests/pos/typeclass-encoding3.scala | 403 ++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 tests/pos/typeclass-encoding3.scala diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala new file mode 100644 index 000000000000..c3afe35991da --- /dev/null +++ b/tests/pos/typeclass-encoding3.scala @@ -0,0 +1,403 @@ +object Test { + +/* -------------------------------------------------------------------------- + + Extension Methods & Simple Interfaces + + case class Reactangle(width: Double, height: Double) + case class Circle(radius: Double) + + extension for Rectangle + def area: Double = width * height + + trait HasArea + def area: Double + + extension for Circle : HasArea + def area = radius * 2 * math.Pi + +* ------------------------------------------------------------------------ */ + + case class Rectangle(width: Double, height: Double) + case class Circle(radius: Double) + + implicit class Rectangle_area(`this`: Rectangle) { + def area: Double = `this`.width * `this`.height + } + + implicit class Circle_HasArea(`this`: Circle) { + def area = `this`.radius * 2 * math.Pi + } + +/* -------------------------------------------------------------------------- + + Monomorphic: + + trait SemiGroup + def combine(that: This): This + common + type This + + trait Monoid extends SemiGroup + common + def unit: This + + class Str(val s: String) extends Monoid + def combine(that: Str): Str = new Str(this.s + that.s) + object Str: + def unit = "" + + extension for String : Monoid + def combine(that: String): String = this + that + common + def unit = "" + + def f[X: Monoid](x: X) = Monoid.by[X].unit.combine(x) + + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(Monoid.by[T].unit)(_ `combine` _) + +* ---------------------------------------------------------------------------- */ + + trait SemiGroup { + val common: SemiGroup.Common + import common._ + + def combine(that: This): This + } + object SemiGroup { + trait Common { self => + type This + def inject(x: This): SemiGroup { val common: self.type } + } + def by[A](implicit ev: SemiGroup.Common { type This = A }): ev.type = ev + } + + trait Monoid extends SemiGroup { + val common: Monoid.Common + import common._ + } + object Monoid { + trait Common extends SemiGroup.Common { self => + def inject(x: This): Monoid { val common: self.type } + def unit: This + } + def by[A](implicit ev: Monoid.Common { type This = A }): ev.type = ev + } + + class Str(val s: String) extends Monoid { + val common: Str.type = Str + def combine(that: Str): Str = new Str(this.s + that.s) + } + object Str extends Monoid.Common { + type This = Str + def inject(x: This): Str = x + def unit = new Str("") + } + + class SubStr(s: String) extends Str(s) + + implicit object String_Monoid extends Monoid.Common { self => + type This = String + class Impl(`this`: This) extends Monoid { + val common: self.type = self + def combine(that: String): String = `this` + that + } + def inject(x: This): Impl = new Impl(x) + def unit = "" + } + + def f[X](x: X)(implicit ev: Monoid.Common { type This = X }) = { + ev.inject(Monoid.by[X].unit).combine(x) + } + + def sum[A](xs: List[A])(implicit ev: Monoid.Common { type This = A }): A = + xs.foldLeft(Monoid.by[A].unit)(ev.inject(_).combine(_)) + + f(new Str("abc"))(Str) + f("abc") + f(new SubStr("abc"))(Str) + + sum(List("A", "B", "C")) + +/* -------------------------------------------------------------------------- + + Generic: + + trait Ord + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + common + val minimum: This + + extension for Int : Ord + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 + common + val minimum = Int.MinValue + + extension [T : Ord] for List[T] : Ord: + def compareTo(that: List[T]): Int = (this, that) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + common + val minimum = Nil + + def max[T: Ord](x: T, y: T) = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (Ord.by[T].minimum /: xs)(max(_, _)) + +* ---------------------------------------------------------------------------- */ + + trait Ord { + val common: Ord.Common + import common._ + + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + } + object Ord { + trait Common { self => + type This + def inject(x: This): Ord { val common: self.type } + val minimum: This + } + def by[A](implicit ev: Ord.Common { type This = A }): ev.type = ev + } + + implicit object Int_Ord extends Ord.Common { self => + type This = Int + class Impl(`this`: This) extends Ord { + val common: self.type = self + def compareTo(that: Int) = + if (`this` < that) -1 else if (`this` > that) +1 else 0 + } + def inject(x: This): Impl = new Impl(x) + val minimum = Int.MinValue + } + + class List_Ord[T](implicit ev: Ord.Common { type This = T }) extends Ord.Common { self => + type This = List[T] + class Impl(`this`: This) extends Ord { + val common: self.type = self + def compareTo(that: List[T]): Int = (`this`, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = ev.inject(x).compareTo(y) + if (fst != 0) fst else List_Ord[T].inject(xs).compareTo(ys) + } + } + def inject(x: This): Impl = new Impl(x) + val minimum = Nil + } + + implicit def List_Ord[T](implicit ev: Ord.Common { type This = T }): List_Ord[T] = + new List_Ord[T] + + def max[T](x: T, y: T)(implicit ev: Ord.Common { type This = T }) = if (ev.inject(x) < y) x else y + + def max[T](xs: List[T])(implicit ev: Ord.Common { type This = T }): T = + (Ord.by[T].minimum /: xs)(max(_, _)) + + val x1 = max(1, 2) + val x2 = max(List(1), Nil) + val x3 = max(List(1, 2, 3)) + val x4 = max(List(List(1, 2), List(3, 3), Nil)) + +/* -------------------------------------------------------------------------- + + Higher-kinded: + + trait Functor[A] + def map[B](f: A => B): This[B] + common + type This[A] + + trait Monad[A] extends Functor[A] + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B): This[B] = flatMap(f `andThen` pure) + def pure[A]: This[A] + + extension [A] for List[A] : Monad[A] + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) + common + def pure[A](x: A) = x :: Nil + + def g[F: Functor, A, B](x: A, f: A => B): Functor.by[F].This[B] = + Functor.by[F].pure(x).map(f) + + def h[F: Monad, A, B](x: A): (A => Monad.by[F].This[B]) => Monad.by[F].This[B]) = + f => Monad.by[F].pure(x).flatMap(f) + +* ---------------------------------------------------------------------------- */ + + trait Functor[A] { + val common: Functor.Common + import common._ + + def map[B](f: A => B): This[B] + } + + object Functor { + trait Common { self => + type This[A] + def inject[A](x: This[A]): Functor[A] { val common: self.type } + } + transparent def by[F[_]](implicit ev: Functor.Common { type This[A] = F[A] }): ev.type = ev + } + + trait Monad[A] extends Functor[A] { self => + val common: Monad.Common + import common._ + + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B): This[B] = flatMap(f `andThen` pure) + } + object Monad { + trait Common extends Functor.Common { self => + def pure[A](x: A): This[A] + def inject[A](x: This[A]): Monad[A] { val common: self.type } + } + def by[F[_]](implicit ev: Monad.Common { type This[A] = F[A] }): ev.type = ev + } + + implicit object List_Monad extends Monad.Common { self => + type This[A] = List[A] + class Impl[A](`this`: This[A]) extends Monad[A] { + val common: self.type = self + def flatMap[B](f: A => This[B]): This[B] = `this`.flatMap(f) + } + def pure[A](x: A): This[A] = x :: Nil + def inject[A](x: List[A]): Impl[A] = new Impl[A](x) + } + + def g[F[_], A, B](x: A, f: A => B)(implicit ev: Functor.Common { type This[A] = F[A] }) + : ev.This[B] = + ev.inject(Functor.by[F].pure(x)).map(f) + + def h[F[_], A, B](x: A)(implicit ev: Monad.Common { type This[A] = F[A] }) + : (A => ev.This[B]) => ev.This[B] = + f => ev.inject(Functor.by[F].pure(x)).flatMap(f) + + val r = g[F = List](1, _.toString) + +/* -------------------------------------------------------------------------- + + Generic and Higher-kinded: + + extension [A, Ctx] for Ctx => A : Monad[A] + def flatMap[B](f: A => Ctx => B): Ctx => B = + ctx => f(this(ctx))(ctx) + common + def pure[A](x: A) = ctx => x + +* ---------------------------------------------------------------------------- */ + + class $eq$gt_Monad[Ctx] extends Monad.Common { self => + type This[A] = Ctx => A + class Impl[A](`this`: This[A]) extends Monad[A] { + val common: self.type = self + def flatMap[B](f: A => This[B]): This[B] = + ctx => f(`this`(ctx))(ctx) + } + def pure[A](x: A): This[A] = + ctx => x + def inject[A](x: This[A]): Impl[A] = new Impl[A](x) + } + + implicit def $eq$gt_Monad[Ctx]: $eq$gt_Monad[Ctx] = new $eq$gt_Monad[Ctx] + + g[F = [X] => Int => X]((ctx: Int) => 1, x => (ctx: Int) => x.toString) + + +/* --------------------------------------------------------------------------------- + + case class Reactangle(width: Double, height: Double) + case class Circle(radius: Double) + + implicit object RectangleArea + def (x: Rectangle).area: Double = x.width * self.height + + trait HasArea[T] + def (x: T).area: Double + + implicit object CircleHasArea extends HasArea[Circle] + def (x: Circle).area = x.radius * 2 * math.Pi + + --------------------------------------------------------------------------------- + + trait SemiGroup[T] + def (x: T).combine(y: T): T + + trait Monoid[T] extends SemiGroup[T] + def unit: T + + def f[X: Monoid](x: X) = implicitly[Monoid[X]].unit.combine(x) + + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_ `combine` _) + + // Class `Str` is not definable + + implicit object StringMonoid extends Monoid[String] + def (x: String).combine(y: String): String = x + y + def unit = "" + + trait Ord[T] + def (x: T).compareTo(y: T): Int + def (x: T) < (that: T) = x.compareTo(y) < 0 + def (x: T) > (that: T) = x.compareTo(y) > 0 + val minimum: T + } + + implicit object IntOrd { + def (x: Int).compareTo(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + } + + implicit class ListOrd[T: Ord] { + def (xs: List[T]).compareTo(ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + val minimum = Nil + } + + def max[T: Ord](x: T, y: T) = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + --------------------------------------------------------------------------------- + + trait Functor[F[_]] + def (x: F[A]).map[A, B](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 pure[A]: F[A] + + implicit object ListMonad extends Monad[List] + def (xs: List[A]).flatMap[A, B](f: A => List[B]): List[B] = xs.flatMap(f) + def pure[A]: List[A] = List.Nil + + implicit class ReaderMonad[Ctx] extends Monad[Ctx => _] + def (r: Ctx => A).flatMap[A, B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = ctx => x + + +*/ + +} \ No newline at end of file From 81a3cd5d4831486434de90beb1eded93806fa766 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 15:09:08 +0200 Subject: [PATCH 03/56] New proposal for extension methods --- .../reference/extend/extension-methods.md | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 docs/docs/reference/extend/extension-methods.md diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md new file mode 100644 index 000000000000..dbc9eab98029 --- /dev/null +++ b/docs/docs/reference/extend/extension-methods.md @@ -0,0 +1,254 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +implicit object CircleOps { + def (c: Circle).circumference: Double = c.radius * math.Pi * 2 +} +``` + +`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Translation of Extension Methods + +Extension methods are methods that have a parameter clause in front of the defined +identifier. They translate to methods where the leading parameter section is moved +to after the defined identifier. So, the definition of `circumference` above translates +to the plain method, and can also be invoked as such: +```scala +def circumference(c: Circle): Double = c.radius * math.Pi * 2 + +assert(circle.circumference == CircleOps.circumference(circle)) +``` + +### Translation of Calls to Extension Methods + +The rules for resolving a selection `e.m` are augmented as follows: If `m` is not a +member of the type `T` of `e`, and there is an implicit value `i` that defines `m` +in either the current scope or in the implicit scope of `T`, then `e.m` is expanded +to `i.m(e)`. This expansion is attempted at the time where the compiler also tries an implicit conversion from `T` to a type containing `m`. If there is more than one way +of expanding, an ambiguity error results. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). + +### Extended Types + +Extension methods can be added to arbitrary types. For instance, the following +object adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +implicit object StringOps { + def (xs: Seq[String).longestStrings = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +``` + +### Generic Extensions + +The previous example 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: + +```scala +implicit object ListOps { + def (xs: List[T]).second[T] = xs.tail.head +} +``` + +or: + + +```scala +implicit object ListListOps { + def (xs: List[List[T]]).flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the parameter clause that precedes +the defined method name. + +### A Larger Example + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + implicit object Ensuring { + def (x: T).ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} + +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } + +### Extension Operators + +The `.` between leading parameter section and defined name in an extension method is optional. If the extension method is an operator, leaving out the dot leads to clearer +syntax that resembles the intended usage pattern: + +```scala +implicit object NumericOps { + def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) +} +``` + +An infix operation `x op y` of an extension method `op` coming from `z` is always translated to `z.op(x)(y)`, irrespective of whether `op` is right-associative or not. So, no implicit swapping of arguments takes place for extension methods ending in a `:`. For instance, +here is the "domino"-operator brought back as an extension method: + +```scala +implicit object SeqOps { + def (x: A) /: [A, B](xs: Seq[B])(op: (A, B) => A): A = + xs.foldLeft(x)(op) +} +``` +A call like +```scala +(0 /: List(1, 2, 3)) (_ + _) +``` +is translated to +```scala +SeqOps./: (0) (List(1, 2, 3)) (_ + _) +``` + +### Extension Methods and TypeClasses + +The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. +```scala + // Two typeclasses: + trait SemiGroup[T] { + def (x: T).combine(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + implicit object StringMonoid extends Monoid[String] { + def (x: String).combine(y: String): String + def unit: String + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) +``` +In the last line, the call to `_combine(_)` expands to `(x1, x2) => x1.combine(x)`, +which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit +evidence parameter summoned by the context bound `[T: Monoid]`. This works since +extension methods apply everywhere their enclosing object is available as an implicit. + +### Generic Extension Classes + +As another example, consider implementations of an `Ord` type class: +```scala + trait Ord[T] + def (x: T).compareTo(y: T): Int + def (x: T) < (that: T) = x.compareTo(y) < 0 + def (x: T) > (that: T) = x.compareTo(y) > 0 + } + + implicit object IntOrd { + def (x: Int).compareTo(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + } + + implicit class ListOrd[T: Ord] { + def (xs: List[T]).compareTo(ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + + def max[T: Ord](x: T, y: T) = if (x < y) y else x +``` +The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since +implicit classes can only define implicit converions that take exactly one non-implicit +value parameter. We propose to drop this requirement and to also allow implicit classes +without any value parameters, or with only implicit value parameters. The generated implicit method would in each case follow the signature of the class. That is, for `ListOrd` we'd generate the method: +```scala + implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] +``` + +### Higher Kinds + +Extension methods generalize to higher-kinded types without requiring special provisions. Example: + +```scala + trait Functor[F[_]] { + def (x: F[A]).map[A, B](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 pure[A]: F[A] + } + + implicit object ListMonad extends Monad[List] { + def (xs: List[A]).flatMap[A, B](f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A]: List[A] = + List.Nil + } + + implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { + def (r: Ctx => A).flatMap[A, B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => + A = ctx => x + } +``` +### Syntax + +The required syntax extension just adds one clause for extension methods relative +to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +``` +DefSig ::= ... + | DefParamClause [‘.’] id [DefTypeParamClause] DefParamClauses +``` + + + + From 45ea7aef9ceee0faeb016690f58bd12148c06343 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 15:47:36 +0200 Subject: [PATCH 04/56] Fix wording of translation rule --- docs/docs/reference/extend/extension-methods.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index dbc9eab98029..e3455e8f4c67 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -35,9 +35,9 @@ assert(circle.circumference == CircleOps.circumference(circle)) ### Translation of Calls to Extension Methods The rules for resolving a selection `e.m` are augmented as follows: If `m` is not a -member of the type `T` of `e`, and there is an implicit value `i` that defines `m` -in either the current scope or in the implicit scope of `T`, then `e.m` is expanded -to `i.m(e)`. This expansion is attempted at the time where the compiler also tries an implicit conversion from `T` to a type containing `m`. If there is more than one way +member of the type `T` of `e`, and there is an implicit value `i` +in either the current scope or in the implicit scope of `T`, and `i` defines an extension +method named `m`, then `e.m` is expanded to `i.m(e)`. This expansion is attempted at the time where the compiler also tries an implicit conversion from `T` to a type containing `m`. If there is more than one way of expanding, an ambiguity error results. So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided @@ -236,8 +236,8 @@ Extension methods generalize to higher-kinded types without requiring special pr implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { def (r: Ctx => A).flatMap[A, B](f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) - def pure[A](x: A): Ctx => - A = ctx => x + def pure[A](x: A): Ctx => A = + ctx => x } ``` ### Syntax From 04f4877d46af43eed0e879d12732878cdde463e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 16:21:18 +0200 Subject: [PATCH 05/56] Fix example code --- tests/pos/typeclass-encoding3.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index c3afe35991da..065b0a5b3b27 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -279,9 +279,9 @@ object Test { def inject[A](x: List[A]): Impl[A] = new Impl[A](x) } - def g[F[_], A, B](x: A, f: A => B)(implicit ev: Functor.Common { type This[A] = F[A] }) + def g[F[_], A, B](x: A, f: A => B)(implicit ev: Monad.Common { type This[A] = F[A] }) : ev.This[B] = - ev.inject(Functor.by[F].pure(x)).map(f) + ev.inject(Monad.by[F].pure(x)).map(f) def h[F[_], A, B](x: A)(implicit ev: Monad.Common { type This[A] = F[A] }) : (A => ev.This[B]) => ev.This[B] = From e74fc4def33c89adb67a6984ba4798e2f39cb4ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 16:27:54 +0200 Subject: [PATCH 06/56] Address review comments --- docs/docs/reference/extend/extension-methods.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index e3455e8f4c67..5a2bfc885a5c 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -50,7 +50,7 @@ object adds a `longestStrings` extension method to a `Seq[String]`: ```scala implicit object StringOps { - def (xs: Seq[String).longestStrings = { + def (xs: Seq[String]).longestStrings = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } @@ -97,9 +97,9 @@ object PostConditions { implicit object Ensuring { def (x: T).ensuring(condition: implicit WrappedResult[T] => Boolean): T = { - implicit val wrapped = WrappedResult.wrap(this) + implicit val wrapped = WrappedResult.wrap(x) assert(condition) - this + x } } } From dca503d268a778dace7c9a6e4872489546a5cf3f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 17:18:33 +0200 Subject: [PATCH 07/56] Another fix for example code --- tests/pos/typeclass-encoding3.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index 065b0a5b3b27..611e3edbb59e 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -285,7 +285,7 @@ object Test { def h[F[_], A, B](x: A)(implicit ev: Monad.Common { type This[A] = F[A] }) : (A => ev.This[B]) => ev.This[B] = - f => ev.inject(Functor.by[F].pure(x)).flatMap(f) + f => ev.inject(Monad.by[F].pure(x)).flatMap(f) val r = g[F = List](1, _.toString) From 54e6c855883d4449bc573dec0139d87dc1514240 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 17:50:32 +0200 Subject: [PATCH 08/56] Add missing type parameter --- docs/docs/reference/extend/extension-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 5a2bfc885a5c..97e1f1feec6b 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -96,7 +96,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) implicit object Ensuring { - def (x: T).ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + def (x: T).ensuring[T](condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(x) assert(condition) x From 75ea0818e413a6ad1a22b8c596a330266831e7fc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 18:04:22 +0200 Subject: [PATCH 09/56] Add example file to pickling blacklist Fails, probably for the same reason as typeclass-encoding2.scala. A widened Int type is mapped to a singleton subtype. Should be harmless. --- compiler/test/dotc/pos-test-pickling.blacklist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index ec2b64d5e57f..74d55dc670d0 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -41,5 +41,6 @@ t6976 t7264 t7532b t8062 -typeclass-encoding2.scala typelevel0.scala +typeclass-encoding2.scala +typeclass-encoding3.scala From fdfe2f413c6b101e9e9adc4bd926d65e33b777fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Sep 2018 18:36:50 +0200 Subject: [PATCH 10/56] Parsing and pretty-printing of extension methods --- compiler/src/dotty/tools/dotc/core/Flags.scala | 5 ++++- .../tools/dotc/core/tasty/TastyFormat.scala | 6 +++++- .../tools/dotc/core/tasty/TreePickler.scala | 1 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../tools/dotc/printing/RefinedPrinter.scala | 16 +++++++++++----- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 86d8f1c83ddb..a6dcc73b8e13 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -334,6 +334,9 @@ object Flags { /** A method that has default params */ final val DefaultParameterized: FlagSet = termFlag(27, "") + /** An extension method */ + final val Extension = termFlag(28, "") + /** Symbol is defined by a Java class */ final val JavaDefined: FlagSet = commonFlag(30, "") @@ -466,7 +469,7 @@ object Flags { HigherKinded.toCommonFlags | Param | ParamAccessor.toCommonFlags | Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | - NonMember | ImplicitCommon | Permanent | Synthetic | + Extension.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline /** Flags that are not (re)set when completing the denotation, or, if symbol is diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 8db9e0a4d277..9172fc2049e9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -202,6 +202,7 @@ Standard-Section: "ASTs" TopLevelStat* SCALA2X // Imported from Scala2.x DEFAULTparameterized // Method with default parameters STABLE // Method that is assumed to be stable + EXTENSION // An extension method PARAMsetter // A setter without a body named `x_=` where `x` is pickled as a PARAM Annotation @@ -309,7 +310,8 @@ object TastyFormat { final val MACRO = 33 final val ERASED = 34 final val OPAQUE = 35 - final val PARAMsetter = 36 + final val EXTENSION = 36 + final val PARAMsetter = 37 // Cat. 2: tag Nat @@ -479,6 +481,7 @@ object TastyFormat { | SCALA2X | DEFAULTparameterized | STABLE + | EXTENSION | PARAMsetter | ANNOTATION | PRIVATEqualified @@ -538,6 +541,7 @@ object TastyFormat { case SCALA2X => "SCALA2X" case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" + case EXTENSION => "EXTENSION" case PARAMsetter => "PARAMsetter" case SHAREDterm => "SHAREDterm" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0d6b7a52915d..950863fa565f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -652,6 +652,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is CaseAccessor) writeByte(CASEaccessor) if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) + if (flags is Extension) writeByte(EXTENSION) if (flags is ParamAccessor) writeByte(PARAMsetter) assert(!(flags is Label)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b97c48194813..bc8a8cc53595 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -629,6 +629,7 @@ class TreeUnpickler(reader: TastyReader, case SCALA2X => addFlag(Scala2x) case DEFAULTparameterized => addFlag(DefaultParameterized) case STABLE => addFlag(Stable) + case EXTENSION => addFlag(Extension) case PARAMsetter => addFlag(ParamAccessor) case PRIVATEqualified => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 038d65920e32..d53116675fb6 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -662,9 +662,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def tparamsText[T >: Untyped](params: List[Tree[T]]): Text = "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty - def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]]): Text = - (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")") - + def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]], isExtension: Boolean = false): Text = { + def paramsText(params: List[ValDef[T]]) = "(" ~ toText(params, ", ") ~ ")" + val (leading, paramss) = + if (isExtension && vparamss.nonEmpty) (paramsText(vparamss.head) ~ "." ~ txt, vparamss.tail) + else (txt, vparamss) + (txt /: paramss)((txt, params) => txt ~ paramsText(params)) + } protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _} dclTextOr(tree) { @@ -678,9 +682,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { import untpd.{modsDeco => _} dclTextOr(tree) { val prefix = modText(tree.mods, tree.symbol, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) + val isExtension = tree.hasType && tree.symbol.is(Extension) withEnclosingDef(tree) { - addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ - optText(tree.rhs)(" = " ~ _) + addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss, isExtension) ~ + optAscription(tree.tpt) ~ + optText(tree.rhs)(" = " ~ _) } } } From 61b89c81e3ae4b2356507b14a535af65d701c473 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Sep 2018 10:15:36 +0200 Subject: [PATCH 11/56] Fixes to code and syntax --- docs/docs/reference/extend/extension-methods.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 97e1f1feec6b..c840fa4dcab4 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -162,8 +162,8 @@ The rules for expanding extension methods make sure that they work seamlessly wi // An instance declaration: implicit object StringMonoid extends Monoid[String] { - def (x: String).combine(y: String): String - def unit: String + def (x: String).combine(y: String): String = x.concat(y) + def unit: String = "" } // Abstracting over a typeclass with a context bound: @@ -223,14 +223,14 @@ Extension methods generalize to higher-kinded types without requiring special pr 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 pure[A]: F[A] + def pure[A](x: A): F[A] } implicit object ListMonad extends Monad[List] { def (xs: List[A]).flatMap[A, B](f: A => List[B]): List[B] = xs.flatMap(f) - def pure[A]: List[A] = - List.Nil + def pure[A](x: A): List[A] = + List(x) } implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { @@ -246,7 +246,7 @@ The required syntax extension just adds one clause for extension methods relativ to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). ``` DefSig ::= ... - | DefParamClause [‘.’] id [DefTypeParamClause] DefParamClauses + | ‘(’ DefParam ‘)’ [‘.’] id [DefTypeParamClause] DefParamClauses ``` From 4b7121cf453ac86e20c3279d5c1dd008c6163401 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Sep 2018 16:30:02 +0200 Subject: [PATCH 12/56] More fixes to doc --- docs/docs/reference/extend/extension-methods.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index c840fa4dcab4..15af701c8bd8 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -170,27 +170,29 @@ The rules for expanding extension methods make sure that they work seamlessly wi def sum[T: Monoid](xs: List[T]): T = xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) ``` -In the last line, the call to `_combine(_)` expands to `(x1, x2) => x1.combine(x)`, +In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit evidence parameter summoned by the context bound `[T: Monoid]`. This works since extension methods apply everywhere their enclosing object is available as an implicit. ### Generic Extension Classes -As another example, consider implementations of an `Ord` type class: +As another example, consider implementations of an `Ord` type class with a `minimum` value: ```scala trait Ord[T] def (x: T).compareTo(y: T): Int def (x: T) < (that: T) = x.compareTo(y) < 0 def (x: T) > (that: T) = x.compareTo(y) > 0 + val minimum: T } - implicit object IntOrd { + implicit object IntOrd extends Ord[Int] { def (x: Int).compareTo(y: Int) = if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue } - implicit class ListOrd[T: Ord] { + implicit class ListOrd[T: Ord] extends Ord[List[T]] { def (xs: List[T]).compareTo(ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -198,9 +200,12 @@ As another example, consider implementations of an `Ord` type class: case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) if (fst != 0) fst else xs1.compareTo(ys1) + val minimum: T } - def max[T: Ord](x: T, y: T) = if (x < y) y else x + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) ``` The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since implicit classes can only define implicit converions that take exactly one non-implicit From 85efcb41ae8e34aefacb39f2c23070e46d0d3b6d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Sep 2018 17:59:08 +0200 Subject: [PATCH 13/56] Switch to prefix type arguments A trial balloon to test prefix type argument syntax. --- .../reference/extend/extension-methods.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 15af701c8bd8..700d676e633b 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -64,7 +64,7 @@ to extend a generic type by adding type parameters to an extension method: ```scala implicit object ListOps { - def (xs: List[T]).second[T] = xs.tail.head + def [T](xs: List[T]).second = xs.tail.head } ``` @@ -73,7 +73,7 @@ or: ```scala implicit object ListListOps { - def (xs: List[List[T]]).flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) + def [T](xs: List[List[T]]).flattened = xs.foldLeft[List[T]](Nil)(_ ++ _) } ``` @@ -96,7 +96,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) implicit object Ensuring { - def (x: T).ensuring[T](condition: implicit WrappedResult[T] => Boolean): T = { + def [T](x: T).ensuring(condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(x) assert(condition) x @@ -126,7 +126,7 @@ syntax that resembles the intended usage pattern: ```scala implicit object NumericOps { - def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) + def [T : Numeric](x: T) + (y: T): T = implicitly[Numeric[T]].plus(x, y) } ``` @@ -135,7 +135,7 @@ here is the "domino"-operator brought back as an extension method: ```scala implicit object SeqOps { - def (x: A) /: [A, B](xs: Seq[B])(op: (A, B) => A): A = + def [A](x: A) /: [B](xs: Seq[B])(op: (A, B) => A): A = xs.foldLeft(x)(op) } ``` @@ -221,25 +221,25 @@ Extension methods generalize to higher-kinded types without requiring special pr ```scala trait Functor[F[_]] { - def (x: F[A]).map[A, B](f: A => B): F[B] + def [A](x: F[A]).map[B](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](x: F[A]).flatMap[B](f: A => F[B]): F[B] + def [A](x: F[A]).map[B](f: A => B) = x.flatMap(f `andThen` pure) def pure[A](x: A): F[A] } implicit object ListMonad extends Monad[List] { - def (xs: List[A]).flatMap[A, B](f: A => List[B]): List[B] = + def [A](xs: List[A]).flatMap[B](f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def (r: Ctx => A).flatMap[A, B](f: A => Ctx => B): Ctx => B = + def [A](r: Ctx => A).flatMap[B](f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x @@ -251,7 +251,8 @@ The required syntax extension just adds one clause for extension methods relativ to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). ``` DefSig ::= ... - | ‘(’ DefParam ‘)’ [‘.’] id [DefTypeParamClause] DefParamClauses + | [DefTypeParamClause] ‘(’ DefParam ‘)’ [‘.’] + id [DefTypeParamClause] DefParamClauses ``` From e88c3a8516c44d3dbc7553410ec03111417cb007 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Sep 2018 12:58:39 +0200 Subject: [PATCH 14/56] Change syntax to "this modifier" scheme --- docs/docs/internals/syntax.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index d83734dc0854..31026136c975 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -277,6 +277,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ +ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam] ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` @@ -319,7 +320,7 @@ 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 ::= id [DefTypeParamClause] DefParamClauses +DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType From 328140bb982d235e2d21003f12ba332e60beb144 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Sep 2018 13:36:44 +0200 Subject: [PATCH 15/56] Parsing and pretty-printing for "this as modifier" scheme --- .../dotty/tools/dotc/parsing/Parsers.scala | 34 ++++++++++++------- .../tools/dotc/printing/RefinedPrinter.scala | 12 ++++--- docs/docs/internals/syntax.md | 2 +- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 70541b54857b..66c95974a0df 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1964,15 +1964,19 @@ object Parsers { * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param * DefParamClauses ::= {DefParamClause} [[nl] `(' [FunArgMods] DefParams `)'] - * DefParamClause ::= [nl] `(' [`erased'] [DefParams] ')' + * DefParamClause ::= [nl] `(' [DefParams] ')' + * ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] + * + * @return The parameter definitions, and whether leading parameter is tagged `this` */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { + def paramClauses(owner: Name, ofCaseClass: Boolean = false): (List[List[ValDef]], Boolean) = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once - var firstClauseOfCaseClass = ofCaseClass + var firstClause = true + var isExtension = false def param(): ValDef = { val start = in.offset var mods = annotsAsMods() @@ -1991,7 +1995,7 @@ object Parsers { else { if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) syntaxError("`val' or `var' expected") - if (firstClauseOfCaseClass) mods + if (firstClause && ofCaseClass) mods else mods | PrivateLocal } } @@ -2020,6 +2024,11 @@ object Parsers { } def paramClause(): List[ValDef] = inParens { if (in.token == RPAREN) Nil + else if (in.token == THIS && firstClause && !owner.isTypeName && owner != nme.CONSTRUCTOR) { + in.nextToken() + isExtension = true + param() :: Nil + } else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -2041,7 +2050,7 @@ object Parsers { if (in.token == LPAREN) { imods = EmptyModifiers paramClause() :: { - firstClauseOfCaseClass = false + firstClause = false if (imods is Implicit) Nil else clauses() } } else Nil @@ -2059,7 +2068,7 @@ object Parsers { listOfErrors.foreach { vparam => syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) } - result + (result, isExtension) } /* -------- DEFS ------------------------------------------- */ @@ -2211,7 +2220,7 @@ object Parsers { /** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type - * DefSig ::= id [DefTypeParamClause] ParamClauses + * DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses */ def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { @@ -2225,7 +2234,7 @@ object Parsers { } if (in.token == THIS) { in.nextToken() - val vparamss = paramClauses(nme.CONSTRUCTOR) + val (vparamss, _) = paramClauses(nme.CONSTRUCTOR) if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) @@ -2233,10 +2242,11 @@ object Parsers { } makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start)) } else { - val mods1 = addFlag(mods, Method) + var mods1 = addFlag(mods, Method) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(name) + val (vparamss, isExtension) = paramClauses(name) + if (isExtension) mods1 = mods1 | Extension var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { in.nextToken() @@ -2362,10 +2372,10 @@ object Parsers { /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses */ - def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { + def classConstr(owner: TypeName, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val (vparamss, _) = paramClauses(owner, isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d53116675fb6..48e26ba4de94 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -663,11 +663,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]], isExtension: Boolean = false): Text = { - def paramsText(params: List[ValDef[T]]) = "(" ~ toText(params, ", ") ~ ")" - val (leading, paramss) = - if (isExtension && vparamss.nonEmpty) (paramsText(vparamss.head) ~ "." ~ txt, vparamss.tail) - else (txt, vparamss) - (txt /: paramss)((txt, params) => txt ~ paramsText(params)) + var isExtensionParam = isExtension + def paramsText(params: List[ValDef[T]]) = { + val txt = "(" ~ (("this ": Text) provided isExtensionParam) ~ toText(params, ", ") ~ ")" + isExtensionParam = false + txt + } + (txt /: vparamss)((txt, vparams) => txt ~ paramsText(vparams)) } protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _} diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 31026136c975..e04269fa4c54 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -277,7 +277,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ -ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam] ‘)’ +ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` From 9b4dc40a2def34e6c12fd46883d21f559812fecd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Sep 2018 15:14:51 +0200 Subject: [PATCH 16/56] Dotty docs for "this as modifier" scheme --- .../reference/extend/extension-methods.md | 86 ++++++------------- 1 file changed, 25 insertions(+), 61 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 700d676e633b..fa14ca5abb71 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -9,7 +9,7 @@ Extension methods allow one to add methods to a type after the type is defined. case class Circle(x: Double, y: Double, radius: Double) implicit object CircleOps { - def (c: Circle).circumference: Double = c.radius * math.Pi * 2 + def circumference(this c: Circle): Double = c.radius * math.Pi * 2 } ``` @@ -20,15 +20,9 @@ implicit object CircleOps { circle.circumference ``` -### Translation of Extension Methods - -Extension methods are methods that have a parameter clause in front of the defined -identifier. They translate to methods where the leading parameter section is moved -to after the defined identifier. So, the definition of `circumference` above translates -to the plain method, and can also be invoked as such: +Extension methods are methods that have a `this` modifier for the first parameter. +They can also be invoked as plain methods. So the following holds: ```scala -def circumference(c: Circle): Double = c.radius * math.Pi * 2 - assert(circle.circumference == CircleOps.circumference(circle)) ``` @@ -50,7 +44,7 @@ object adds a `longestStrings` extension method to a `Seq[String]`: ```scala implicit object StringOps { - def (xs: Seq[String]).longestStrings = { + def longestStrings(this xs: Seq[String]) = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } @@ -64,7 +58,7 @@ to extend a generic type by adding type parameters to an extension method: ```scala implicit object ListOps { - def [T](xs: List[T]).second = xs.tail.head + def second[T](this xs: List[T]) = xs.tail.head } ``` @@ -73,12 +67,11 @@ or: ```scala implicit object ListListOps { - def [T](xs: List[List[T]]).flattened = xs.foldLeft[List[T]](Nil)(_ ++ _) + def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) } ``` -As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the parameter clause that precedes -the defined method name. +As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the parameter clause that precedes the defined method name. ### A Larger Example @@ -96,7 +89,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) implicit object Ensuring { - def [T](x: T).ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + def ensuring[T](this x: T)(condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(x) assert(condition) x @@ -119,34 +112,9 @@ to pass along to the `result` method. `WrappedResult` is a fresh type, to make s result } -### Extension Operators - -The `.` between leading parameter section and defined name in an extension method is optional. If the extension method is an operator, leaving out the dot leads to clearer -syntax that resembles the intended usage pattern: - -```scala -implicit object NumericOps { - def [T : Numeric](x: T) + (y: T): T = implicitly[Numeric[T]].plus(x, y) -} -``` - -An infix operation `x op y` of an extension method `op` coming from `z` is always translated to `z.op(x)(y)`, irrespective of whether `op` is right-associative or not. So, no implicit swapping of arguments takes place for extension methods ending in a `:`. For instance, -here is the "domino"-operator brought back as an extension method: +### Rules for Overriding Extension Methods -```scala -implicit object SeqOps { - def [A](x: A) /: [B](xs: Seq[B])(op: (A, B) => A): A = - xs.foldLeft(x)(op) -} -``` -A call like -```scala -(0 /: List(1, 2, 3)) (_ + _) -``` -is translated to -```scala -SeqOps./: (0) (List(1, 2, 3)) (_ + _) -``` +Extension methods may override only extension methods and can be overridden only by extension methods. ### Extension Methods and TypeClasses @@ -154,7 +122,7 @@ The rules for expanding extension methods make sure that they work seamlessly wi ```scala // Two typeclasses: trait SemiGroup[T] { - def (x: T).combine(y: T): T + def combine(this x: T)(y: T): T } trait Monoid[T] extends SemiGroup[T] { def unit: T @@ -162,7 +130,7 @@ The rules for expanding extension methods make sure that they work seamlessly wi // An instance declaration: implicit object StringMonoid extends Monoid[String] { - def (x: String).combine(y: String): String = x.concat(y) + def combine(this x: String)(y: String): String = x.concat(y) def unit: String = "" } @@ -180,20 +148,20 @@ extension methods apply everywhere their enclosing object is available as an imp As another example, consider implementations of an `Ord` type class with a `minimum` value: ```scala trait Ord[T] - def (x: T).compareTo(y: T): Int - def (x: T) < (that: T) = x.compareTo(y) < 0 - def (x: T) > (that: T) = x.compareTo(y) > 0 + def compareTo(this x: T)(y: T): Int + def < (this x: T)(y: T) = x.compareTo(y) < 0 + def > (this x: T)(y: T) = x.compareTo(y) > 0 val minimum: T } implicit object IntOrd extends Ord[Int] { - def (x: Int).compareTo(y: Int) = + def compareTo(this x: Int)(y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } implicit class ListOrd[T: Ord] extends Ord[List[T]] { - def (xs: List[T]).compareTo(ys: List[T]): Int = (xs, ys) match + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 @@ -207,10 +175,7 @@ As another example, consider implementations of an `Ord` type class with a `mini def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) ``` -The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since -implicit classes can only define implicit converions that take exactly one non-implicit -value parameter. We propose to drop this requirement and to also allow implicit classes -without any value parameters, or with only implicit value parameters. The generated implicit method would in each case follow the signature of the class. That is, for `ListOrd` we'd generate the method: +The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since implicit classes can only define implicit converions that take exactly one non-implicit value parameter. We propose to drop this requirement and to also allow implicit classes without any value parameters, or with only implicit value parameters. The generated implicit method would in each case follow the signature of the class. That is, for `ListOrd` we'd generate the method: ```scala implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] ``` @@ -221,25 +186,25 @@ Extension methods generalize to higher-kinded types without requiring special pr ```scala trait Functor[F[_]] { - def [A](x: F[A]).map[B](f: A => B): F[B] + def map[A, B](this x: F[A])(f: A => B): F[B] } trait Monad[F[_]] extends Functor[F] { - def [A](x: F[A]).flatMap[B](f: A => F[B]): F[B] - def [A](x: F[A]).map[B](f: A => B) = x.flatMap(f `andThen` pure) + def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B] + def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure) def pure[A](x: A): F[A] } implicit object ListMonad extends Monad[List] { - def [A](xs: List[A]).flatMap[B](f: A => List[B]): List[B] = + def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def [A](r: Ctx => A).flatMap[B](f: A => Ctx => B): Ctx => B = + def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x @@ -250,9 +215,8 @@ Extension methods generalize to higher-kinded types without requiring special pr The required syntax extension just adds one clause for extension methods relative to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). ``` -DefSig ::= ... - | [DefTypeParamClause] ‘(’ DefParam ‘)’ [‘.’] - id [DefTypeParamClause] DefParamClauses +DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses +ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ ``` From e3ad509fb1e197581c8332d48627a882758a873e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Sep 2018 15:33:19 +0200 Subject: [PATCH 17/56] Fix rebase breakage --- tests/pos/typeclass-encoding3.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index 611e3edbb59e..e3a9c5e5f4ab 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -251,7 +251,7 @@ object Test { type This[A] def inject[A](x: This[A]): Functor[A] { val common: self.type } } - transparent def by[F[_]](implicit ev: Functor.Common { type This[A] = F[A] }): ev.type = ev + inline def by[F[_]](implicit ev: Functor.Common { type This[A] = F[A] }): ev.type = ev } trait Monad[A] extends Functor[A] { self => From 93bedf53a0efb3480a7aa0a9697f4657ea7ed110 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 13:44:37 +0200 Subject: [PATCH 18/56] Make extension objects eligible for implicit search Also classify the kind of an implicit candidate, in particular whether it can be a conversion, an extension, or both. --- .../src/dotty/tools/dotc/core/Flags.scala | 3 + .../dotty/tools/dotc/typer/Applications.scala | 8 + .../dotty/tools/dotc/typer/Implicits.scala | 177 +++++++++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 10 +- 4 files changed, 126 insertions(+), 72 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index a6dcc73b8e13..2284244f8e68 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -591,6 +591,9 @@ object Flags { /** An inline parameter */ final val InlineParam: FlagConjunction = allOf(Inline, Param) + /** An extension method */ + final val ExtensionMethod = allOf(Method, Extension) + /** An enum case */ final val EnumCase: FlagConjunction = allOf(Enum, Case) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index eea06175ecb0..10e0d9edd611 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1123,6 +1123,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic => tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) } + /** Does `tp` have an extension method named `name` with this-argument `argType` and + * result matching `resultType`? + */ + def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { + val mbr = tp.member(name).suchThat(_.is(ExtensionMethod)) + mbr.exists && isApplicable(argType.select(name, mbr), argType :: Nil, resultType) + } + /** Compare owner inheritance level. * @param sym1 The first owner * @param sym2 The second owner diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 118aba4ea0e6..a810c39408be 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -49,9 +49,16 @@ object Implicits { } /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ - case class Candidate(implicitRef: ImplicitRef, level: Int) { + case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) { def ref: TermRef = implicitRef.underlyingRef } + object Candidate { + type Kind = Int + final val None = 0 + final val Value = 1 + final val Conversion = 2 + final val Extension = 4 + } /** A common base class of contextual implicits and of-type implicits which * represents a set of references to implicit definitions. @@ -78,64 +85,76 @@ object Implicits { /** Return those references in `refs` that are compatible with type `pt`. */ protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") { - def refMatches(ref: TermRef)(implicit ctx: Context) = /*trace(i"refMatches $ref $pt")*/ { - - def discardForView(tpw: Type, argType: Type): Boolean = tpw match { - case mt: MethodType => - mt.isImplicitMethod || - mt.paramInfos.lengthCompare(1) != 0 || - !ctx.test(implicit ctx => - argType relaxed_<:< widenSingleton(mt.paramInfos.head)) - case poly: PolyType => - // We do not need to call ProtoTypes#constrained on `poly` because - // `refMatches` is always called with mode TypevarsMissContext enabled. - poly.resultType match { - case mt: MethodType => - mt.isImplicitMethod || - mt.paramInfos.length != 1 || - !ctx.test(implicit ctx => - argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head))) - case rtp => - discardForView(wildApprox(rtp), argType) - } - case tpw: TermRef => - false // can't discard overloaded refs - case tpw => - // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. - // However, Predef.$conforms is not eligible, because it is a no-op. - // - // In principle, it would be cleanest if only implicit methods qualified - // as implicit conversions. We could achieve that by having standard conversions like - // this in Predef: - // - // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) - // implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a) - // - // (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.) - // But clauses like this currently slow down implicit search a lot, because - // they are eligible for all pairs of types, and therefore are tried too often. - // We emulate instead these conversions directly in the search. - // The reason for leaving out `Predef_conforms` is that we know it adds - // nothing since it only relates subtype with supertype. - // - // We keep the old behavior under -language:Scala2. - val isFunctionInS2 = - ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms - val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter) - val isConforms = // An implementation of <:< counts as a view, except that $conforms is always omitted - tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms - !(isFunctionInS2 || isImplicitConverter || isConforms) - } + def candidateKind(ref: TermRef)(implicit ctx: Context): Candidate.Kind = /*trace(i"candidateKind $ref $pt")*/ { + + def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = { - def discardForValueType(tpw: Type): Boolean = tpw.stripPoly match { - case tpw: MethodType => !tpw.isImplicitMethod - case _ => false + def methodCandidateKind(mt: MethodType, formal: => Type) = + if (!mt.isImplicitMethod && + mt.paramInfos.lengthCompare(1) == 0 && + ctx.test(implicit ctx => argType relaxed_<:< formal)) + Candidate.Conversion + else + Candidate.None + + tpw match { + case mt: MethodType => + methodCandidateKind(mt, widenSingleton(mt.paramInfos.head)) + case poly: PolyType => + // We do not need to call ProtoTypes#constrained on `poly` because + // `candidateKind` is always called with mode TypevarsMissContext enabled. + poly.resultType match { + case mt: MethodType => + methodCandidateKind(mt, wildApprox(widenSingleton(mt.paramInfos.head))) + case rtp => + viewCandidateKind(wildApprox(rtp), argType, resType) + } + case tpw: TermRef => + Candidate.Conversion | Candidate.Extension // can't discard overloaded refs + case tpw => + // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. + // However, Predef.$conforms is not eligible, because it is a no-op. + // + // In principle, it would be cleanest if only implicit methods qualified + // as implicit conversions. We could achieve that by having standard conversions like + // this in Predef: + // + // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) + // implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a) + // + // (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.) + // But clauses like this currently slow down implicit search a lot, because + // they are eligible for all pairs of types, and therefore are tried too often. + // We emulate instead these conversions directly in the search. + // The reason for leaving out `Predef_conforms` is that we know it adds + // nothing since it only relates subtype with supertype. + // + // We keep the old behavior under -language:Scala2. + val isFunctionInS2 = + ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms + val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter) + val isConforms = // An implementation of <:< counts as a view, except that $conforms is always omitted + tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms + val hasExtensions = resType match { + case SelectionProto(name, _, _, _) => + tpw.member(name).hasAltWith(_.symbol.is(ExtensionMethod)) + case _ => false + } + val conversionKind = + if (isFunctionInS2 || isImplicitConverter || isConforms) Candidate.Conversion + else Candidate.None + val extensionKind = + if (hasExtensions) Candidate.Extension + else Candidate.None + conversionKind | extensionKind + } } - def discard = pt match { - case pt: ViewProto => discardForView(ref.widen, pt.argType) - case _: ValueTypeOrProto => !defn.isFunctionType(pt) && discardForValueType(ref.widen) - case _ => false + def valueTypeCandidateKind(tpw: Type): Candidate.Kind = tpw.stripPoly match { + case tpw: MethodType => + if (tpw.isImplicitMethod) Candidate.Value else Candidate.None + case _ => + Candidate.Value } /** Widen singleton arguments of implicit conversions to their underlying type. @@ -152,28 +171,44 @@ object Implicits { case _ => tp } - (ref.symbol isAccessibleFrom ref.prefix) && { - if (discard) { - record("discarded eligible") - false - } - else { - val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749 - val refAdjusted = - if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton) - else ref - val refNorm = normalize(refAdjusted, pt) - NoViewsAllowed.isCompatible(refNorm, ptNorm) + var ckind = + if (!ref.symbol.isAccessibleFrom(ref.prefix)) Candidate.None + else pt match { + case pt: ViewProto => + viewCandidateKind(ref.widen, pt.argType, pt.resType) + case _: ValueTypeOrProto => + if (defn.isFunctionType(pt)) Candidate.Value + else valueTypeCandidateKind(ref.widen) + case _ => + Candidate.Value } + + if (ckind == Candidate.None) + record("discarded eligible") + else { + val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749 + val refAdjusted = + if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton) + else ref + val refNorm = normalize(refAdjusted, pt) + if (!NoViewsAllowed.isCompatible(refNorm, ptNorm)) + ckind = Candidate.None } + ckind } + if (refs.isEmpty) Nil else { val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) - refs - .filter(ref => nestedCtx.test(implicit ctx => refMatches(ref.underlyingRef))) - .map(Candidate(_, level)) + + def matchingCandidate(ref: ImplicitRef): Option[Candidate] = + nestedCtx.test(implicit ctx => candidateKind(ref.underlyingRef)) match { + case Candidate.None => None + case ckind => Some(new Candidate(ref, ckind, level)) + } + + refs.flatMap(matchingCandidate) } } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 46b2fb96025c..55aa7be70240 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -376,7 +376,15 @@ object ProtoTypes { override def resultType(implicit ctx: Context): Type = resType def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = - ctx.typer.isApplicable(tp, argType :: Nil, resultType) + ctx.typer.isApplicable(tp, argType :: Nil, resultType) || { + resType match { + case SelectionProto(name: TermName, mbrType, _, _) => + ctx.typer.hasExtensionMethod(tp, name, argType, mbrType) + //.reporting(res => i"has ext $tp $name $argType $mbrType: $res") + case _ => + false + } + } def derivedViewProto(argType: Type, resultType: Type)(implicit ctx: Context): ViewProto = if ((argType eq this.argType) && (resultType eq this.resultType)) this From 6e224d8731f198ab9a744e2c6858171977ac4615 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 15:12:59 +0200 Subject: [PATCH 19/56] Handle extension methods in implicit search --- .../dotty/tools/dotc/typer/Implicits.scala | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index a810c39408be..8ed1d6746353 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -51,6 +51,9 @@ object Implicits { /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) { def ref: TermRef = implicitRef.underlyingRef + + def isExtension = (kind & Candidate.Extension) != 0 + def isConversion = (kind & Candidate.Conversion) != 0 } object Candidate { type Kind = Int @@ -923,12 +926,20 @@ trait Implicits { self: Typer => val ref = cand.ref var generated: Tree = tpd.ref(ref).withPos(pos.startPos) val locked = ctx.typerState.ownedVars - if (!argument.isEmpty) - generated = typedUnadapted( - untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), - pt, locked) - val generated1 = adapt(generated, pt, locked) - + val generated1 = + if (argument.isEmpty) + adapt(generated, pt, locked) + else { + val untpdGenerated = untpd.TypedSplice(generated) + val untpdArguments = untpd.TypedSplice(argument) :: Nil + if (cand.isConversion) + typed(untpd.Apply(untpdGenerated, untpdArguments), pt, locked) + else { + assert(cand.isExtension) + val SelectionProto(name, mbrType, _, _) = pt + typed(untpd.Apply(untpd.Select(untpdGenerated, name), untpdArguments), mbrType, locked) + } + } lazy val shadowing = typedUnadapted(untpd.Ident(cand.implicitRef.implicitName) withPos pos.toSynthetic)( nestedContext().addMode(Mode.ImplicitShadowing).setExploreTyperState()) From e7c68ef65d6fa5f3ed32d692c16b723b7ae72438 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 15:42:18 +0200 Subject: [PATCH 20/56] Avoid attachments to communicate extension method applications Use a customized tree instead. This avoids side effects which might be tricky to manage in the presence of backtracking. --- .../dotty/tools/dotc/typer/Implicits.scala | 18 +++++++++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 21 ++++++++++++------- .../reference/extend/extension-methods.md | 11 +++++----- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 8ed1d6746353..d7371641e8e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -41,6 +41,13 @@ import scala.annotation.internal.sharable object Implicits { import tpd._ + /** A flag indicating that this an application of an extension method + * with the given name + */ + case class ExtMethodResult(app: Tree) extends tpd.Tree { + override def pos = app.pos + } + /** An implicit definition `implicitRef` that is visible under a different name, `alias`. * Gets generated if an implicit ref is imported via a renaming import. */ @@ -936,9 +943,10 @@ trait Implicits { self: Typer => typed(untpd.Apply(untpdGenerated, untpdArguments), pt, locked) else { assert(cand.isExtension) - val SelectionProto(name, mbrType, _, _) = pt + val SelectionProto(name: TermName, mbrType, _, _) = pt typed(untpd.Apply(untpd.Select(untpdGenerated, name), untpdArguments), mbrType, locked) } + // TODO disambiguate if candidate can be an extension or a conversion } lazy val shadowing = typedUnadapted(untpd.Ident(cand.implicitRef.implicitName) withPos pos.toSynthetic)( @@ -980,8 +988,12 @@ trait Implicits { self: Typer => SearchFailure(generated1.withTypeUnchecked( new ShadowedImplicit(ref, methPart(shadowing).tpe, pt, argument))) } - else - SearchSuccess(generated1, ref, cand.level)(ctx.typerState) + else { + val generated2 = + if (cand.isExtension) ExtMethodResult(generated1).withType(generated1.tpe) + else generated1 + SearchSuccess(generated2, ref, cand.level)(ctx.typerState) + } }} /** Try to type-check implicit reference, after checking that this is not diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a268497fae03..54b14a0c3e12 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -428,14 +428,17 @@ class Typer extends Namer def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { - def typeSelectOnTerm(implicit ctx: Context): Tree = { - val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) - if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) - val select = typedSelect(tree, pt, qual1) - if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) - else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select - else typedDynamicSelect(tree, Nil, pt) - } + def typeSelectOnTerm(implicit ctx: Context): Tree = + typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { + case ExtMethodResult(app) => + app + case qual1 => + if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) + val select = typedSelect(tree, pt, qual1) + if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) + else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select + else typedDynamicSelect(tree, Nil, pt) + } def typeSelectOnType(qual: untpd.Tree)(implicit ctx: Context) = typedSelect(untpd.cpy.Select(tree)(qual, tree.name.toTypeName), pt) @@ -2600,6 +2603,8 @@ class Typer extends Namer else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType) inferView(tree, pt) match { + case SearchSuccess(inferred: ExtMethodResult, _, _) => + inferred // nothing to check or adapt for extension method applications case SearchSuccess(inferred, _, _) => checkImplicitConversionUseOK(inferred.symbol, tree.pos) readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index fa14ca5abb71..595a2906abbf 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -147,10 +147,10 @@ extension methods apply everywhere their enclosing object is available as an imp As another example, consider implementations of an `Ord` type class with a `minimum` value: ```scala - trait Ord[T] + trait Ord[T] { def compareTo(this x: T)(y: T): Int - def < (this x: T)(y: T) = x.compareTo(y) < 0 - def > (this x: T)(y: T) = x.compareTo(y) > 0 + def < (this x: T)(y: T) = compareTo(x)(y) < 0 + def > (this x: T)(y: T) = compareTo(x)(y) < 0 val minimum: T } @@ -161,14 +161,15 @@ As another example, consider implementations of an `Ord` type class with a `mini } implicit class ListOrd[T: Ord] extends Ord[List[T]] { - def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) if (fst != 0) fst else xs1.compareTo(ys1) - val minimum: T + } + val minimum: List[T] = Nil } def max[T: Ord](x: T, y: T): T = if (x < y) y else x From 4481174a6857f8bc22d1c5b9468383d0bea5d825 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 17:53:44 +0200 Subject: [PATCH 21/56] Avoid eta-expanding partially applied extension methods --- .../tools/dotc/printing/RefinedPrinter.scala | 4 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 7 +- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +- .../reference/extend/extension-methods.md | 4 +- tests/run/extension-methods.scala | 127 ++++++++++++++++++ 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 tests/run/extension-methods.scala diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48e26ba4de94..7476634cde49 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -231,8 +231,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => toTextGlobal(args, ", ") } return "FunProto(" ~ argsText ~ "):" ~ toText(resultType) - case tp: IgnoredProto => - return "?" + case IgnoredProto(ignored) => + return "?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided ctx.settings.verbose.value) case tp @ PolyProto(targs, resType) => return "PolyProto(" ~ toTextGlobal(targs, ", ") ~ "): " ~ toText(resType) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 55aa7be70240..09eb3d2b9463 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -202,7 +202,8 @@ object ProtoTypes { /** A prototype for selections in pattern constructors */ class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true) - trait ApplyingProto extends ProtoType + trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto + trait FunOrPolyProto extends ProtoType // common trait of PolyProto and FunProto class FunProtoState { @@ -227,7 +228,7 @@ object ProtoTypes { * [](args): resultType */ case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer, state: FunProtoState = new FunProtoState)(implicit ctx: Context) - extends UncachedGroundType with ApplyingProto { + extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = @@ -414,7 +415,7 @@ object ProtoTypes { * * [] [targs] resultType */ - case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with ProtoType { + case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 54b14a0c3e12..6280be852181 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2523,6 +2523,11 @@ class Typer extends Namer val ptNorm = underlyingApplied(pt) lazy val functionExpected = defn.isFunctionType(ptNorm) lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) + def needsEta = pt match { + case _: SingletonType => false + case IgnoredProto(_: FunOrPolyProto) => false + case _ => true + } wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) @@ -2534,7 +2539,7 @@ class Typer extends Namer // is tried. See strawman-contrib MapDecoratorTest.scala for an example where this happens. err.typeMismatch(tree, pt) } - case wtp: MethodType if !pt.isSingleton => + case wtp: MethodType if needsEta => val arity = if (functionExpected) if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none)) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 595a2906abbf..a5aed7a1630e 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -149,8 +149,8 @@ As another example, consider implementations of an `Ord` type class with a `mini ```scala trait Ord[T] { def compareTo(this x: T)(y: T): Int - def < (this x: T)(y: T) = compareTo(x)(y) < 0 - def > (this x: T)(y: T) = compareTo(x)(y) < 0 + def < (this x: T)(y: T) = x.compareTo(y) < 0 + def > (this x: T)(y: T) = x.compareTo(y) > 0 val minimum: T } diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala new file mode 100644 index 000000000000..3a4977032776 --- /dev/null +++ b/tests/run/extension-methods.scala @@ -0,0 +1,127 @@ +object Test extends App { + + implicit object O { + def em(this x: Int): Boolean = x > 0 + } + + assert(1.em == O.em(1)) + + case class Circle(x: Double, y: Double, radius: Double) + + implicit object CircleOps { + def circumference(this c: Circle): Double = c.radius * math.Pi * 2 + } + + val circle = new Circle(1, 1, 2.0) + + assert(circle.circumference == CircleOps.circumference(circle)) + + implicit object StringOps { + def longestStrings(this xs: Seq[String]): Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } + } + val names = List("hi", "hello", "world") + assert(names.longestStrings == List("hello", "world")) + + implicit object SeqOps { + def second[T](this xs: Seq[T]) = xs.tail.head + } + + assert(names.longestStrings.second == "world") + + implicit object ListListOps { + def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) + } + + assert(List(names, List("!")).flattened == names :+ "!") + assert(Nil.flattened == Nil) + + trait SemiGroup[T] { + def combine(this x: T)(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + implicit object StringMonoid extends Monoid[String] { + def combine(this x: String)(y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) + + println(sum(names)) + + trait Ord[T] { + def compareTo(this x: T)(y: T): Int + def < (this x: T)(y: T) = compareTo(x)(y) < 0 + def > (this x: T)(y: T) = compareTo(x)(y) > 0 + val minimum: T + } + + implicit object IntOrd extends Ord[Int] { + def compareTo(this x: Int)(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + class ListOrd[T: Ord] extends Ord[List[T]] { + def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else compareTo(xs1)(ys1) + } + val minimum: List[T] = Nil + } + implicit def listOrd[T: Ord]: ListOrd[T] = new ListOrd[T] + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + println(max(List[Int]())) + println(max(List(1, 2, 3))) + + println(max(List(1, 2, 3), List(2))) + + trait Functor[F[_]] { + def map[A, B](this x: F[A])(f: A => B): F[B] + } + + trait Monad[F[_]] extends Functor[F] { + def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B] + def map[A, B](this x: F[A])(f: A => B) = flatMap(x)(f `andThen` pure) + + def pure[A](x: A): F[A] + } + + implicit object ListMonad extends Monad[List] { + def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + } + + class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { + def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } + implicit def readerMonad[Ctx]: ReaderMonad[Ctx] = new ReaderMonad[Ctx] + + def mappAll[F[_]: Monad, T](x: T, fs: List[T => T]): F[T] = + fs.foldLeft(implicitly[Monad[F]].pure(x))((x: F[T], f: T => T) => + if (true) implicitly[Monad[F]].map(x)(f) + else x.map(f) + // does not work yet: x.map[T, T](f) + ) +} \ No newline at end of file From 4a97662d6c3a637d1bb5555ba16c5650fa481e53 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 18:25:17 +0200 Subject: [PATCH 22/56] Add check file to test --- tests/run/extension-methods.check | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tests/run/extension-methods.check diff --git a/tests/run/extension-methods.check b/tests/run/extension-methods.check new file mode 100644 index 000000000000..5cd13ae2676a --- /dev/null +++ b/tests/run/extension-methods.check @@ -0,0 +1,4 @@ +hihelloworld +-2147483648 +3 +List(2) From 18029a999aefa70712f7849eaaa18c016a04fc65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 Sep 2018 23:28:00 +0200 Subject: [PATCH 23/56] Add extensions-methods to pickling blacklist Another example of widened vs singleton type. This feels a but like fighting with windmills. --- compiler/test/dotc/run-test-pickling.blacklist | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 91ab8c1a70a8..23420419ff39 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -1,5 +1,6 @@ Course-2002-07.scala eff-dependent.scala +extension-methods.scala i5257.scala lazy-implicit-lists.scala lazy-implicit-nums.scala From 46f1a5c15ac54ac99961e24c7ef976f1f88102c7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 28 Sep 2018 14:20:40 +0200 Subject: [PATCH 24/56] Handle extension method applications with explicit type arguments # Conflicts: # compiler/src/dotty/tools/dotc/core/Types.scala --- .../src/dotty/tools/dotc/core/Types.scala | 3 ++ .../dotty/tools/dotc/typer/Applications.scala | 54 ++++++++++--------- .../dotty/tools/dotc/typer/Implicits.scala | 7 ++- .../dotty/tools/dotc/typer/ProtoTypes.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 15 ++++-- tests/run/extension-methods.scala | 4 +- 6 files changed, 51 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d8d5d17182e5..0813d9f1f061 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1379,6 +1379,9 @@ object Types { */ def deepenProto(implicit ctx: Context): Type = this + /** If this is an ignored proto type, its underlying type, otherwise the type itself */ + def revealIgnored: Type = this + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 10e0d9edd611..cc728c9e79c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -870,22 +870,26 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) - val typedFn = typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) - typedFn.tpe.widen match { - case pt: PolyType => - if (typedArgs.length <= pt.paramInfos.length && !isNamed) - if (typedFn.symbol == defn.Predef_classOf && typedArgs.nonEmpty) { - val arg = typedArgs.head - checkClassType(arg.tpe, arg.pos, traitReq = false, stablePrefixReq = false) - } - case _ => - } - def tryDynamicTypeApply(): Tree = typedFn match { - case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) - case _ => tree.withType(TryDynamicCallType) + typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) match { + case Implicits.ExtMethodResult(app) => + app + case typedFn => + typedFn.tpe.widen match { + case pt: PolyType => + if (typedArgs.length <= pt.paramInfos.length && !isNamed) + if (typedFn.symbol == defn.Predef_classOf && typedArgs.nonEmpty) { + val arg = typedArgs.head + checkClassType(arg.tpe, arg.pos, traitReq = false, stablePrefixReq = false) + } + case _ => + } + def tryDynamicTypeApply(): Tree = typedFn match { + case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) + case _ => tree.withType(TryDynamicCallType) + } + if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() + else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } - if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() - else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } /** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray. @@ -1339,16 +1343,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. */ - def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { - case IgnoredProto(ignored) => resultConforms(altSym, altType, ignored) - case _: ValueType => - altType.widen match { - case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) - case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) - case _ => true - } - case _ => true - } + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = + resultType.revealIgnored match { + case resultType: ValueType => + altType.widen match { + case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) + case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) + case _ => true + } + case _ => true + } /** If the `chosen` alternative has a result type incompatible with the expected result * type `pt`, run overloading resolution again on all alternatives that do match `pt`. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index d7371641e8e9..67e83d1658c3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -944,7 +944,12 @@ trait Implicits { self: Typer => else { assert(cand.isExtension) val SelectionProto(name: TermName, mbrType, _, _) = pt - typed(untpd.Apply(untpd.Select(untpdGenerated, name), untpdArguments), mbrType, locked) + val sel = untpd.Select(untpdGenerated, name) + val core = mbrType.revealIgnored match { + case PolyProto(targs, _) => untpd.TypeApply(sel, targs.map(untpd.TypeTree)) + case _ => sel + } + typed(untpd.Apply(core, untpdArguments), mbrType, locked) } // TODO disambiguate if candidate can be an extension or a conversion } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 09eb3d2b9463..2e05016d7250 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -96,6 +96,7 @@ object ProtoTypes { /** A class marking ignored prototypes that can be revealed by `deepenProto` */ case class IgnoredProto(ignored: Type) extends UncachedGroundType with MatchAlways { + override def revealIgnored = ignored.revealIgnored override def deepenProto(implicit ctx: Context): Type = ignored } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6280be852181..a96717fe674b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -430,8 +430,11 @@ class Typer extends Namer def typeSelectOnTerm(implicit ctx: Context): Tree = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { - case ExtMethodResult(app) => - app + case qual1 @ ExtMethodResult(app) => + pt.revealIgnored match { + case _: PolyProto => qual1 // keep the ExtMethodResult to strip at next typedTypeApply + case _ => app + } case qual1 => if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) val select = typedSelect(tree, pt, qual1) @@ -2096,10 +2099,9 @@ class Typer extends Namer } /** Is `pt` a prototype of an `apply` selection, or a parameterless function yielding one? */ - def isApplyProto(pt: Type)(implicit ctx: Context): Boolean = pt match { + def isApplyProto(pt: Type)(implicit ctx: Context): Boolean = pt.revealIgnored match { case pt: SelectionProto => pt.name == nme.apply case pt: FunProto => pt.args.isEmpty && isApplyProto(pt.resultType) - case pt: IgnoredProto => isApplyProto(pt.ignored) case _ => false } @@ -2687,7 +2689,10 @@ class Typer extends Namer case pt: FunProto => adaptToArgs(wtp, pt) case pt: PolyProto => - tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply + tree match { + case _: ExtMethodResult => tree + case _ => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply + } case _ => if (ctx.mode is Mode.Type) adaptType(tree.tpe) else adaptNoArgs(wtp) diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala index 3a4977032776..65533c6d7e5b 100644 --- a/tests/run/extension-methods.scala +++ b/tests/run/extension-methods.scala @@ -121,7 +121,7 @@ object Test extends App { def mappAll[F[_]: Monad, T](x: T, fs: List[T => T]): F[T] = fs.foldLeft(implicitly[Monad[F]].pure(x))((x: F[T], f: T => T) => if (true) implicitly[Monad[F]].map(x)(f) - else x.map(f) - // does not work yet: x.map[T, T](f) + else if (true) x.map(f) + else x.map[T, T](f) ) } \ No newline at end of file From d45fd68278df2bf4e5bfadd20fe5d54c6c847960 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 28 Sep 2018 14:43:58 +0200 Subject: [PATCH 25/56] Represent PolyProto arguments as trees, not types This allows us to keep the original trees in extension method calls with explicit type arguments. --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 3 +-- compiler/src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 8 ++++---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 6b1b7079b493..f4fab05d9f37 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -459,8 +459,7 @@ class TreeChecker extends Phase with SymTransformer { if (ctx.mode.isExpr && !tree.isEmpty && !isPrimaryConstructorReturn && - !pt.isInstanceOf[FunProto] && - !pt.isInstanceOf[PolyProto]) + !pt.isInstanceOf[FunOrPolyProto]) assert(tree.tpe <:< pt, { val mismatch = err.typeMismatchMsg(tree.tpe, pt) i"""|${mismatch.msg} diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index cc728c9e79c2..25062f61d4c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -870,7 +870,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) - typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) match { + typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { case Implicits.ExtMethodResult(app) => app case typedFn => @@ -1484,7 +1484,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case pt @ PolyProto(targs1, pt1) if targs.isEmpty => val alts1 = alts filter pt.isMatchedBy - resolveOverloaded(alts1, pt1, targs1) + resolveOverloaded(alts1, pt1, targs1.tpes) case defn.FunctionOf(args, resultType, _, _) => narrowByTypes(alts, args, resultType) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index f2c42f05262e..e9f9d6d5ea50 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -40,7 +40,7 @@ object ErrorReporting { def expectedTypeStr(tp: Type): String = tp match { case tp: PolyProto => - em"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" + em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(tp.resultType)}" case tp: FunProto => val result = tp.resultType match { case _: WildcardType | _: IgnoredProto => "" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 67e83d1658c3..5598dcb7b2db 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -946,7 +946,7 @@ trait Implicits { self: Typer => val SelectionProto(name: TermName, mbrType, _, _) = pt val sel = untpd.Select(untpdGenerated, name) val core = mbrType.revealIgnored match { - case PolyProto(targs, _) => untpd.TypeApply(sel, targs.map(untpd.TypeTree)) + case PolyProto(targs, _) => untpd.TypeApply(sel, targs) case _ => sel } typed(untpd.Apply(core, untpdArguments), mbrType, locked) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 2e05016d7250..f71d6351a1e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -416,7 +416,7 @@ object ProtoTypes { * * [] [targs] resultType */ - case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with FunOrPolyProto { + case class PolyProto(targs: List[Tree], resType: Type) extends UncachedGroundType with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType @@ -428,17 +428,17 @@ object ProtoTypes { isInstantiatable(tp) || tp.member(nme.apply).hasAltWith(d => isInstantiatable(d.info)) } - def derivedPolyProto(targs: List[Type], resultType: Type): PolyProto = + def derivedPolyProto(targs: List[Tree], resultType: Type): PolyProto = if ((targs eq this.targs) && (resType eq this.resType)) this else PolyProto(targs, resType) override def notApplied: Type = WildcardType def map(tm: TypeMap)(implicit ctx: Context): PolyProto = - derivedPolyProto(targs mapConserve tm, tm(resultType)) + derivedPolyProto(targs, tm(resultType)) def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = - ta(ta.foldOver(x, targs), resultType) + ta(ta.foldOver(x, targs.tpes), resultType) override def deepenProto(implicit ctx: Context): PolyProto = derivedPolyProto(targs, resultType.deepenProto) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a96717fe674b..b5e2ab963ee1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -439,7 +439,7 @@ class Typer extends Namer if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) val select = typedSelect(tree, pt, qual1) if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) - else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select + else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select else typedDynamicSelect(tree, Nil, pt) } From 91729bc0fcf44b464b15baba022c5bef34312ce7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 28 Sep 2018 16:07:35 +0200 Subject: [PATCH 26/56] Refactorings and polishings Motivation is to make extension methods in scope easier to implement --- .../dotty/tools/dotc/typer/Applications.scala | 26 ++++++++++++++++++- .../dotty/tools/dotc/typer/Implicits.scala | 16 ++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 8 +++--- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 25062f61d4c8..1e9792526b77 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -164,6 +164,12 @@ object Applications { def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree + + /** A wrapper indicating that its argument is an application of an extension method. + */ + case class ExtMethodApply(app: Tree) extends tpd.Tree { + override def pos = app.pos + } } @@ -871,7 +877,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { - case Implicits.ExtMethodResult(app) => + case ExtMethodApply(app) => app case typedFn => typedFn.tpe.widen match { @@ -1634,5 +1640,23 @@ trait Applications extends Compatibility { self: Typer with Dynamic => */ private def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] = harmonizeWith(tpes)(identity, (tp, pt) => pt) + + /** The typed application + * + * .() or + * .[]() + * + * where and possibly come from the expected type `pt`, which must be + * a SelectionProto + */ + def extMethodApply(provider: Tree, receiver: Tree, pt: Type)(implicit ctx: Context) = { + val SelectionProto(name: TermName, mbrType, _, _) = pt + val sel = untpd.Select(untpd.TypedSplice(provider), name) + val (core, pt1) = mbrType.revealIgnored match { + case PolyProto(targs, restpe) => (untpd.TypeApply(sel, targs.map(untpd.TypedSplice(_))), restpe) + case _ => (sel, mbrType) + } + typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars) + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5598dcb7b2db..0a574b123d32 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -937,19 +937,13 @@ trait Implicits { self: Typer => if (argument.isEmpty) adapt(generated, pt, locked) else { - val untpdGenerated = untpd.TypedSplice(generated) - val untpdArguments = untpd.TypedSplice(argument) :: Nil if (cand.isConversion) - typed(untpd.Apply(untpdGenerated, untpdArguments), pt, locked) + typed( + untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), + pt, locked) else { assert(cand.isExtension) - val SelectionProto(name: TermName, mbrType, _, _) = pt - val sel = untpd.Select(untpdGenerated, name) - val core = mbrType.revealIgnored match { - case PolyProto(targs, _) => untpd.TypeApply(sel, targs) - case _ => sel - } - typed(untpd.Apply(core, untpdArguments), mbrType, locked) + extMethodApply(generated, argument, pt) } // TODO disambiguate if candidate can be an extension or a conversion } @@ -995,7 +989,7 @@ trait Implicits { self: Typer => } else { val generated2 = - if (cand.isExtension) ExtMethodResult(generated1).withType(generated1.tpe) + if (cand.isExtension) ExtMethodApply(generated1).withType(generated1.tpe) else generated1 SearchSuccess(generated2, ref, cand.level)(ctx.typerState) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b5e2ab963ee1..1aeaeec75e47 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -430,9 +430,9 @@ class Typer extends Namer def typeSelectOnTerm(implicit ctx: Context): Tree = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { - case qual1 @ ExtMethodResult(app) => + case qual1 @ Applications.ExtMethodApply(app) => pt.revealIgnored match { - case _: PolyProto => qual1 // keep the ExtMethodResult to strip at next typedTypeApply + case _: PolyProto => qual1 // keep the ExtMethodApply to strip at next typedTypeApply case _ => app } case qual1 => @@ -2610,7 +2610,7 @@ class Typer extends Namer else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType) inferView(tree, pt) match { - case SearchSuccess(inferred: ExtMethodResult, _, _) => + case SearchSuccess(inferred: Applications.ExtMethodApply, _, _) => inferred // nothing to check or adapt for extension method applications case SearchSuccess(inferred, _, _) => checkImplicitConversionUseOK(inferred.symbol, tree.pos) @@ -2690,7 +2690,7 @@ class Typer extends Namer adaptToArgs(wtp, pt) case pt: PolyProto => tree match { - case _: ExtMethodResult => tree + case _: Applications.ExtMethodApply => tree case _ => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply } case _ => From 708504b75f8acc5fc7a7c35c0921ff3dd8058506 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 29 Sep 2018 18:21:57 +0200 Subject: [PATCH 27/56] Consider extension methods if they are in scope --- .../dotty/tools/dotc/config/Printers.scala | 1 + compiler/src/dotty/tools/dotc/core/Mode.scala | 3 ++ .../dotty/tools/dotc/typer/Applications.scala | 38 ++++++++++--------- .../dotty/tools/dotc/typer/Implicits.scala | 6 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 22 ++++++++++- tests/neg/extension-methods.scala | 14 +++++++ tests/run/extension-methods.scala | 8 ++-- 7 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 tests/neg/extension-methods.scala diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 0c888b849857..048558a2211f 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,6 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter + val debug = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index fa41c4bc3573..311428e905b2 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -100,4 +100,7 @@ object Mode { /** Read comments from definitions when unpickling from TASTY */ val ReadComments: Mode = newMode(22, "ReadComments") + + /** Suppress insertion of apply or implicit conversion on qualifier */ + val FixedQualifier: Mode = newMode(23, "FixedQualifier") } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1e9792526b77..c06fc802f423 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -784,13 +784,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * part. Return an optional value to indicate success. */ def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = - tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => - tryEither { - implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] - } { - (_, _) => None + if (ctx.mode.is(Mode.FixedQualifier)) None + else + tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => + tryEither { + implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] + } { + (_, _) => None + } } - } fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) @@ -1643,20 +1645,22 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** The typed application * - * .() or - * .[]() + * () or + * []() * - * where and possibly come from the expected type `pt`, which must be - * a SelectionProto + * where comes from `pt` if it is a PolyProto. */ - def extMethodApply(provider: Tree, receiver: Tree, pt: Type)(implicit ctx: Context) = { - val SelectionProto(name: TermName, mbrType, _, _) = pt - val sel = untpd.Select(untpd.TypedSplice(provider), name) - val (core, pt1) = mbrType.revealIgnored match { - case PolyProto(targs, restpe) => (untpd.TypeApply(sel, targs.map(untpd.TypedSplice(_))), restpe) - case _ => (sel, mbrType) + def extMethodApply(methodRef: untpd.Tree, receiver: Tree, pt: Type)(implicit ctx: Context) = { + val (core, pt1) = pt.revealIgnored match { + case PolyProto(targs, restpe) => (untpd.TypeApply(methodRef, targs.map(untpd.TypedSplice(_))), restpe) + case _ => (methodRef, pt) } - typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars) + val app = + typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)( + ctx.addMode(Mode.FixedQualifier)) + if (!app.symbol.is(Extension)) + ctx.error(em"not an extension method: $methodRef", receiver.pos) + app } } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0a574b123d32..4ee1451399e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -937,13 +937,15 @@ trait Implicits { self: Typer => if (argument.isEmpty) adapt(generated, pt, locked) else { + val untpdGenerated = untpd.TypedSplice(generated) if (cand.isConversion) typed( - untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), + untpd.Apply(untpdGenerated, untpd.TypedSplice(argument) :: Nil), pt, locked) else { assert(cand.isExtension) - extMethodApply(generated, argument, pt) + val SelectionProto(name: TermName, mbrType, _, _) = pt + extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) } // TODO disambiguate if candidate can be an extension or a conversion } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1aeaeec75e47..e4bbfacbfad3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2150,7 +2150,8 @@ class Typer extends Namer def tryImplicit(fallBack: => Tree) = tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack) - pt match { + if (ctx.mode.is(Mode.FixedQualifier)) tree + else pt match { case pt @ FunProto(Nil, _) if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) && tree.getAttachment(DroppedEmptyArgs).isEmpty => @@ -2602,6 +2603,25 @@ class Typer extends Namer } case _ => } + // try an extension method in scope + pt match { + case SelectionProto(name, mbrType, _, _) => + def tryExtension(implicit ctx: Context): Tree = { + val id = typedUnadapted(untpd.Ident(name).withPos(tree.pos)) + id.tpe match { + case ref: TermRef if ref.denot.hasAltWith(_.symbol.is(ExtensionMethod)) => + extMethodApply(untpd.TypedSplice(id), tree, mbrType) + case _ => EmptyTree + } + } + val nestedCtx = ctx.fresh.setNewTyperState() + val app = tryExtension(nestedCtx) + if (!app.isEmpty && !nestedCtx.reporter.hasErrors) { + nestedCtx.typerState.commit() + return Applications.ExtMethodApply(app).withType(app.tpe) + } + case _ => + } // try an implicit conversion val prevConstraint = ctx.typerState.constraint def recover(failure: SearchFailureType) = diff --git a/tests/neg/extension-methods.scala b/tests/neg/extension-methods.scala new file mode 100644 index 000000000000..28fa3662bad6 --- /dev/null +++ b/tests/neg/extension-methods.scala @@ -0,0 +1,14 @@ +object Test { + + implicit object O { + def l1(this x: String) = x.length + def l1(x: Int) = x * x + def l2(x: String) = x.length + } + + "".l1 // OK + "".l2 // error + 1.l1 // error + + +} \ No newline at end of file diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala index 65533c6d7e5b..d7bfeec14714 100644 --- a/tests/run/extension-methods.scala +++ b/tests/run/extension-methods.scala @@ -59,8 +59,8 @@ object Test extends App { trait Ord[T] { def compareTo(this x: T)(y: T): Int - def < (this x: T)(y: T) = compareTo(x)(y) < 0 - def > (this x: T)(y: T) = compareTo(x)(y) > 0 + def < (this x: T)(y: T) = x.compareTo(y) < 0 + def > (this x: T)(y: T) = x.compareTo(y) > 0 val minimum: T } @@ -77,7 +77,7 @@ object Test extends App { case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) - if (fst != 0) fst else compareTo(xs1)(ys1) + if (fst != 0) fst else xs1.compareTo(ys1) } val minimum: List[T] = Nil } @@ -98,7 +98,7 @@ object Test extends App { trait Monad[F[_]] extends Functor[F] { def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B] - def map[A, B](this x: F[A])(f: A => B) = flatMap(x)(f `andThen` pure) + def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure) def pure[A](x: A): F[A] } From c87a4a0c77bcac7e4fd1f86d73d5ce4db8563de2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 29 Sep 2018 18:34:37 +0200 Subject: [PATCH 28/56] Catch cyclic references when trying extension methods We need to catch cyclic references and other type errors so that they translated to a failed extension method attempt and we can try an implicit conversion instead. Without the catch, we'd get a hard error. This was observed by compiling scala/tasty/util/ShowSourceCode.scala before this commit. --- .../src/dotty/tools/dotc/typer/Typer.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e4bbfacbfad3..f6402e7a3c63 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2606,14 +2606,18 @@ class Typer extends Namer // try an extension method in scope pt match { case SelectionProto(name, mbrType, _, _) => - def tryExtension(implicit ctx: Context): Tree = { - val id = typedUnadapted(untpd.Ident(name).withPos(tree.pos)) - id.tpe match { - case ref: TermRef if ref.denot.hasAltWith(_.symbol.is(ExtensionMethod)) => - extMethodApply(untpd.TypedSplice(id), tree, mbrType) - case _ => EmptyTree + def tryExtension(implicit ctx: Context): Tree = + try { + val id = typedUnadapted(untpd.Ident(name).withPos(tree.pos)) + id.tpe match { + case ref: TermRef if ref.denot.hasAltWith(_.symbol.is(ExtensionMethod)) => + extMethodApply(untpd.TypedSplice(id), tree, mbrType) + case _ => EmptyTree + } + } + catch { + case ex: TypeError => errorTree(tree, ex.toMessage, tree.pos) } - } val nestedCtx = ctx.fresh.setNewTyperState() val app = tryExtension(nestedCtx) if (!app.isEmpty && !nestedCtx.reporter.hasErrors) { From 6d0a157c6487792a2d33cec4549df14a57076ce7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 30 Sep 2018 12:25:05 +0200 Subject: [PATCH 29/56] Make findRef accessible outside of typedIdent --- .../src/dotty/tools/dotc/typer/Typer.scala | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f6402e7a3c63..296ce386d06a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -107,32 +107,11 @@ class Typer extends Namer def newLikeThis: Typer = new Typer - /** Attribute an identifier consisting of a simple name or wildcard - * - * @param tree The tree representing the identifier. - * Transformations: (1) Prefix class members with this. - * (2) Change imported symbols to selections. - * (3) Change pattern Idents id (but not wildcards) to id @ _ - */ - def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = track("typedIdent") { + /** Find the denotation of enclosing `name` in given context `ctx`. */ + def findRef(name: Name, pt: Type, pos: Position)(implicit ctx: Context): Type = { val refctx = ctx - val name = tree.name val noImports = ctx.mode.is(Mode.InPackageClauseName) - /** Method is necessary because error messages need to bind to - * to typedIdent's context which is lost in nested calls to findRef - */ - def error(msg: => Message, pos: Position) = ctx.error(msg, pos) - - /** Does this identifier appear as a constructor of a pattern? */ - def isPatternConstr = - if (ctx.mode.isExpr && (ctx.outer.mode is Mode.Pattern)) - ctx.outer.tree match { - case Apply(`tree`, _) => true - case _ => false - } - else false - /** A symbol qualifies if it really exists and is not a package class. * In addition, if we are in a constructor of a pattern, we ignore all definitions * which are methods and not accessors (note: if we don't do that @@ -156,7 +135,7 @@ class Typer extends Namer * @param prevCtx The context of the previous denotation, * or else `NoContext` if nothing was found yet. */ - def findRef(previous: Type, prevPrec: Int, prevCtx: Context)(implicit ctx: Context): Type = { + def findRefRecur(previous: Type, prevPrec: Int, prevCtx: Context)(implicit ctx: Context): Type = { import BindingPrec._ /** Check that any previously found result from an inner context @@ -179,14 +158,14 @@ class Typer extends Namer } else { if (!scala2pkg && !previous.isError && !found.isError) { - error(AmbiguousImport(name, newPrec, prevPrec, prevCtx), tree.pos) + refctx.error(AmbiguousImport(name, newPrec, prevPrec, prevCtx), pos) } previous } def selection(imp: ImportInfo, name: Name) = if (imp.sym.isCompleting) { - ctx.warning(i"cyclic ${imp.sym}, ignored", tree.pos) + ctx.warning(i"cyclic ${imp.sym}, ignored", pos) NoType } else if (unimported.nonEmpty && unimported.contains(imp.site.termSymbol)) NoType @@ -208,8 +187,7 @@ class Typer extends Namer def checkUnambiguous(found: Type) = { val other = recur(selectors.tail) if (other.exists && found.exists && (found != other)) - error(em"reference to `$name` is ambiguous; it is imported twice in ${ctx.tree}", - tree.pos) + refctx.error(em"reference to `$name` is ambiguous; it is imported twice", pos) found } @@ -309,7 +287,7 @@ class Typer extends Namer if (defDenot.symbol is Package) result = checkNewOrShadowed(previous orElse found, packageClause) else if (prevPrec < packageClause) - result = findRef(found, packageClause, ctx)(ctx.outer) + result = findRefRecur(found, packageClause, ctx)(ctx.outer) } } } @@ -325,11 +303,11 @@ class Typer extends Namer else if (isPossibleImport(namedImport) && (curImport ne outer.importInfo)) { val namedImp = namedImportRef(curImport) if (namedImp.exists) - findRef(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) + findRefRecur(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) else if (isPossibleImport(wildImport) && !curImport.sym.isCompleting) { val wildImp = wildImportRef(curImport) if (wildImp.exists) - findRef(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) + findRefRecur(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) else { updateUnimported() loop(ctx)(outer) @@ -345,10 +323,23 @@ class Typer extends Namer } } - // begin findRef + // begin findRefRecur loop(NoContext)(ctx) } + findRefRecur(NoType, BindingPrec.nothingBound, NoContext) + } + + /** Attribute an identifier consisting of a simple name or wildcard + * + * @param tree The tree representing the identifier. + * Transformations: (1) Prefix class members with this. + * (2) Change imported symbols to selections. + * (3) Change pattern Idents id (but not wildcards) to id @ _ + */ + def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = track("typedIdent") { + val name = tree.name + // begin typedIdent def kind = if (name.isTermName) "" else "type " typr.println(s"typed ident $kind$name in ${ctx.owner}") @@ -365,7 +356,7 @@ class Typer extends Namer unimported = Set.empty foundUnderScala2 = NoType try { - var found = findRef(NoType, BindingPrec.nothingBound, NoContext) + var found = findRef(name, pt, tree.pos) if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { ctx.migrationWarning( ex"""Name resolution will change. From 1cf72c6fdc3ffd6301b3e80c3e8543f632fea020 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 30 Sep 2018 12:25:53 +0200 Subject: [PATCH 30/56] Add infrastructure to be able to search specifically for extension methods --- .../src/dotty/tools/dotc/core/Denotations.scala | 14 ++++++++++---- compiler/src/dotty/tools/dotc/core/Flags.scala | 6 ++++++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 3 +++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index c4357138abad..15c81eef6178 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -1085,7 +1085,7 @@ object Denotations { else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) final def filterExcluded(excluded: FlagSet)(implicit ctx: Context): SingleDenotation = - if (excluded.isEmpty || !(this overlaps excluded)) this else NoDenotation + if (excluded.isEmpty || !this.clashes(excluded)) this else NoDenotation type AsSeenFromResult = SingleDenotation protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { @@ -1098,9 +1098,15 @@ object Denotations { else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner)) } - private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match { - case sd: SymDenotation => sd is fs - case _ => symbol is fs + /** Does this denotation have any of the normal flags in `fs`, or alternatively, + * does it lack any of the "flipped member" flags in fs? + */ + private def clashes(fs: FlagSet)(implicit ctx: Context): Boolean = { + val symd: SymDenotation = this match { + case symd: SymDenotation => symd + case _ => symbol.denot + } + (symd.relevantFlagsFor(fs) ^ FlippedMemberFlags).is(fs) } } diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 2284244f8e68..2751c554a87c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -37,6 +37,9 @@ object Flags { else FlagSet(tbits | ((this.bits & ~that.bits) & ~KINDFLAGS)) } + def ^ (that: FlagSet) = + FlagSet((bits | that.bits) & KINDFLAGS | (bits ^ that.bits) & ~KINDFLAGS) + /** Does this flag set have a non-empty intersection with the given flag set? * This means that both the kind flags and the carrier bits have non-empty intersection. */ @@ -594,6 +597,9 @@ object Flags { /** An extension method */ final val ExtensionMethod = allOf(Method, Extension) + /** Flags that are required rather than excluded when mentioned in member search */ + final val FlippedMemberFlags = Extension + /** An enum case */ final val EnumCase: FlagConjunction = allOf(Enum, Case) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 4b6261901d5f..b8ad6c4c4e67 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -177,6 +177,9 @@ object SymDenotations { if (myInfo.isInstanceOf[SymbolLoader]) FromStartFlags else AfterLoadFlags) + final def relevantFlagsFor(fs: FlagSet)(implicit ctx: Context) = + if (isCurrent(fs)) myFlags else flags + /** Has this denotation one of the flags in `fs` set? */ final def is(fs: FlagSet)(implicit ctx: Context): Boolean = (if (isCurrent(fs)) myFlags else flags) is fs From 1a661dbef69575b996d2c28572c86f97155e6515 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 30 Sep 2018 15:47:38 +0200 Subject: [PATCH 31/56] Don't complete normal symbols when searching for an extension method --- .../src/dotty/tools/dotc/typer/Namer.scala | 13 ++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 22 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e1f5e59e3259..9cace40596c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -41,20 +41,23 @@ trait NamerContextOps { this: Context => sym } - /** The denotation with the given name in current context */ - def denotNamed(name: Name): Denotation = + /** The denotation with the given name in current context + * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + */ + def denotNamed(name: Name, exclusive: FlagSet = EmptyFlags): Denotation = if (owner.isClass) if (outer.owner == owner) { // inner class scope; check whether we are referring to self if (scope.size == 1) { val elem = scope.lastEntry if (elem.name == name) return elem.sym.denot // return self } - owner.thisType.member(name) + val pre = owner.thisType + pre.findMember(name, pre, exclusive) } else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. - owner.findMember(name, owner.thisType, EmptyFlags) + owner.findMember(name, owner.thisType, exclusive) else - scope.denotsNamed(name).toDenot(NoPrefix) + scope.denotsNamed(name).filterExcluded(exclusive).toDenot(NoPrefix) /** Either the current scope, or, if the current context owner is a class, * the declarations of the current class. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 296ce386d06a..7ba94d0c6b10 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -107,8 +107,13 @@ class Typer extends Namer def newLikeThis: Typer = new Typer - /** Find the denotation of enclosing `name` in given context `ctx`. */ - def findRef(name: Name, pt: Type, pos: Position)(implicit ctx: Context): Type = { + /** Find the denotation of enclosing `name` in given context `ctx`. + * @param name the name of the denotation + * @param pt the expected type + * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + * @param pos position to use for error reporting + */ + def findRef(name: Name, pt: Type, exclusive: FlagSet, pos: Position)(implicit ctx: Context): Type = { val refctx = ctx val noImports = ctx.mode.is(Mode.InPackageClauseName) @@ -171,7 +176,7 @@ class Typer extends Namer NoType else { val pre = imp.site - val denot = pre.member(name).accessibleFrom(pre)(refctx) + val denot = pre.memberExcluding(name, exclusive).accessibleFrom(pre)(refctx) // Pass refctx so that any errors are reported in the context of the // reference instead of the if (reallyExists(denot)) pre.select(name, denot) else NoType @@ -261,7 +266,7 @@ class Typer extends Namer else (ctx.scope ne lastCtx.scope) || (curOwner ne lastCtx.owner) if (isNewDefScope) { - val defDenot = ctx.denotNamed(name) + val defDenot = ctx.denotNamed(name, exclusive) if (qualifies(defDenot)) { val found = if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType @@ -356,7 +361,7 @@ class Typer extends Namer unimported = Set.empty foundUnderScala2 = NoType try { - var found = findRef(name, pt, tree.pos) + var found = findRef(name, pt, EmptyFlags, tree.pos) if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { ctx.migrationWarning( ex"""Name resolution will change. @@ -2599,10 +2604,9 @@ class Typer extends Namer case SelectionProto(name, mbrType, _, _) => def tryExtension(implicit ctx: Context): Tree = try { - val id = typedUnadapted(untpd.Ident(name).withPos(tree.pos)) - id.tpe match { - case ref: TermRef if ref.denot.hasAltWith(_.symbol.is(ExtensionMethod)) => - extMethodApply(untpd.TypedSplice(id), tree, mbrType) + findRef(name, WildcardType, Extension, tree.pos) match { + case ref: TermRef => + extMethodApply(untpd.ref(ref).withPos(tree.pos), tree, mbrType) case _ => EmptyTree } } From 6b31519763bc03ab513fcb8bdfc7ca817c740b28 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 30 Sep 2018 16:19:36 +0200 Subject: [PATCH 32/56] excluded -> exclusive # Conflicts: # compiler/src/dotty/tools/dotc/core/Types.scala --- .../dotty/tools/dotc/core/Denotations.scala | 23 ++++++++-------- .../tools/dotc/core/SymDenotations.scala | 6 ++--- .../src/dotty/tools/dotc/core/Types.scala | 27 ++++++++++--------- .../tools/dotc/transform/patmat/Space.scala | 4 +-- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 35 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 15c81eef6178..777fee36d345 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -110,10 +110,11 @@ object Denotations { */ def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation - /** Keep only those denotations in this group whose flags do not intersect - * with `excluded`. + /** Keep only those denotations in this group whose flags do not clash with `exclusive`. + * "clash" means have one of the flags in `exclusive & ~FlippedMemberFlags` or + * not have one of the flags in `exclusive & FlippedMemberFlags`. */ - def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation + def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): PreDenotation private[this] var cachedPrefix: Type = _ private[this] var cachedAsSeenFrom: AsSeenFromResult = _ @@ -256,11 +257,11 @@ object Denotations { /** Find member of this denotation with given name and * produce a denotation that contains the type of the member - * as seen from given prefix `pre`. Exclude all members that have - * flags in `excluded` from consideration. + * as seen from given prefix `pre`. + * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) */ - def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = - info.findMember(name, pre, excluded) + def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = + info.findMember(name, pre, exclusive) /** If this denotation is overloaded, filter with given predicate. * If result is still overloaded throw a TypeError. @@ -1084,8 +1085,8 @@ object Denotations { if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) - final def filterExcluded(excluded: FlagSet)(implicit ctx: Context): SingleDenotation = - if (excluded.isEmpty || !this.clashes(excluded)) this else NoDenotation + final def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): SingleDenotation = + if (exclusive.isEmpty || !this.clashes(exclusive)) this else NoDenotation type AsSeenFromResult = SingleDenotation protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { @@ -1191,8 +1192,8 @@ object Denotations { derivedUnion(denot1 filterDisjoint denot, denot2 filterDisjoint denot) def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) - def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation = - derivedUnion(denot1.filterExcluded(excluded), denot2.filterExcluded(excluded)) + def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): PreDenotation = + derivedUnion(denot1.filterExclusive(exclusive), denot2.filterExclusive(exclusive)) protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation): PreDenotation = if ((denot1 eq this.denot1) && (denot2 eq this.denot2)) this else denot1 union denot2 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b8ad6c4c4e67..454d32e26eb9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1671,9 +1671,9 @@ object SymDenotations { else collect(ownDenots, classParents) } - override final def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = { - val raw = if (excluded is Private) nonPrivateMembersNamed(name) else membersNamed(name) - raw.filterExcluded(excluded).asSeenFrom(pre).toDenot(pre) + override final def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = { + val raw = if (exclusive is Private) nonPrivateMembersNamed(name) else membersNamed(name) + raw.filterExclusive(exclusive).asSeenFrom(pre).toDenot(pre) } /** Compute tp.baseType(this) */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0813d9f1f061..ada634ade1f5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -504,7 +504,7 @@ object Types { */ @tailrec final def findDecl(name: Name, excluded: FlagSet)(implicit ctx: Context): Denotation = this match { case tp: ClassInfo => - tp.decls.denotsNamed(name).filterExcluded(excluded).toDenot(NoPrefix) + tp.decls.denotsNamed(name).filterExclusive(excluded).toDenot(NoPrefix) case tp: TypeProxy => tp.underlying.findDecl(name, excluded) case err: ErrorType => @@ -515,29 +515,32 @@ object Types { /** The member of this type with the given name */ final def member(name: Name)(implicit ctx: Context): Denotation = /*>|>*/ track("member") /*<|<*/ { - memberExcluding(name, EmptyFlags) + memberExclusive(name, EmptyFlags) } /** The non-private member of this type with the given name. */ final def nonPrivateMember(name: Name)(implicit ctx: Context): Denotation = track("nonPrivateMember") { - memberExcluding(name, Flags.Private) + memberExclusive(name, Flags.Private) } - final def memberExcluding(name: Name, excluding: FlagSet)(implicit ctx: Context): Denotation = { + /** The member with given `name` and required and/or excluded flags + * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + */ + final def memberExclusive(name: Name, exclusive: FlagSet)(implicit ctx: Context): Denotation = { // We need a valid prefix for `asSeenFrom` val pre = this match { case tp: ClassInfo => tp.appliedRef case _ => widenIfUnstable } - findMember(name, pre, excluding) + findMember(name, pre, exclusive) } /** Find member of this type with given name and * produce a denotation that contains the type of the member - * as seen from given prefix `pre`. Exclude all members that have - * flags in `excluded` from consideration. + * as seen from given prefix `pre`. + * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) */ - final def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = { + final def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = { @tailrec def go(tp: Type): Denotation = tp match { case tp: TermRef => go (tp.underlying match { @@ -547,7 +550,7 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => d.findMember(name, pre, excluded) + case d: ClassDenotation => d.findMember(name, pre, exclusive) case d => go(d.info) } case tp: AppliedType => @@ -579,7 +582,7 @@ object Types { case tp: TypeProxy => go(tp.underlying) case tp: ClassInfo => - tp.cls.findMember(name, pre, excluded) + tp.cls.findMember(name, pre, exclusive) case AndType(l, r) => goAnd(l, r) case tp: OrType => @@ -589,7 +592,7 @@ object Types { // supertype of `pre`. go(tp.join) case tp: JavaArrayType => - defn.ObjectType.findMember(name, pre, excluded) + defn.ObjectType.findMember(name, pre, exclusive) case err: ErrorType => ctx.newErrorSymbol(pre.classSymbol orElse defn.RootClass, name, err.msg) case _ => @@ -799,7 +802,7 @@ object Types { /** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */ final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("membersBasedOnFlags") { memberDenots(takeAllFilter, - (name, buf) => buf ++= memberExcluding(name, excludedFlags).altsWith(x => x.is(requiredFlags))) + (name, buf) => buf ++= memberExclusive(name, excludedFlags).altsWith(x => x.is(requiredFlags))) } /** All members of this type. Warning: this can be expensive to compute! */ diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index bf58b204d575..e08a11fb3e8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -833,8 +833,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { /** does the companion object of the given symbol have custom unapply */ def hasCustomUnapply(sym: Symbol): Boolean = { val companion = sym.companionModule - companion.findMember(nme.unapply, NoPrefix, excluded = Synthetic).exists || - companion.findMember(nme.unapplySeq, NoPrefix, excluded = Synthetic).exists + companion.findMember(nme.unapply, NoPrefix, exclusive = Synthetic).exists || + companion.findMember(nme.unapplySeq, NoPrefix, exclusive = Synthetic).exists } def doShow(s: Space, mergeList: Boolean = false): String = s match { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9cace40596c0..bc55f62fbf6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -57,7 +57,7 @@ trait NamerContextOps { this: Context => else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. owner.findMember(name, owner.thisType, exclusive) else - scope.denotsNamed(name).filterExcluded(exclusive).toDenot(NoPrefix) + scope.denotsNamed(name).filterExclusive(exclusive).toDenot(NoPrefix) /** Either the current scope, or, if the current context owner is a class, * the declarations of the current class. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4ca25185d907..4f1bd69faa61 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -49,7 +49,7 @@ trait TypeAssigner { def addRefinement(parent: Type, decl: Symbol) = { val inherited = - parentType.findMember(decl.name, cls.thisType, excluded = Private) + parentType.findMember(decl.name, cls.thisType, exclusive = Private) .suchThat(decl.matches(_)) val inheritedInfo = inherited.info if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7ba94d0c6b10..9052953ce289 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -176,7 +176,7 @@ class Typer extends Namer NoType else { val pre = imp.site - val denot = pre.memberExcluding(name, exclusive).accessibleFrom(pre)(refctx) + val denot = pre.memberExclusive(name, exclusive).accessibleFrom(pre)(refctx) // Pass refctx so that any errors are reported in the context of the // reference instead of the if (reallyExists(denot)) pre.select(name, denot) else NoType From 601966f88bcd7a9cda49e2dac349776c152fc0a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 30 Sep 2018 17:37:34 +0200 Subject: [PATCH 33/56] Distinguish between required and excluded flags ... instead of mangling them in a single set. # Conflicts: # compiler/src/dotty/tools/dotc/core/Types.scala --- .../backend/jvm/DottyBackendInterface.scala | 6 +-- .../dotty/tools/dotc/core/Denotations.scala | 46 +++++++++---------- .../src/dotty/tools/dotc/core/Flags.scala | 13 ++++-- .../tools/dotc/core/SymDenotations.scala | 8 ++-- .../src/dotty/tools/dotc/core/Types.scala | 35 +++++++------- .../tools/dotc/transform/patmat/Space.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 11 ++--- .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 16 +++---- .../src/dotty/tools/repl/ReplDriver.scala | 2 +- .../tools/dottydoc/core/DocASTPhase.scala | 2 +- 11 files changed, 73 insertions(+), 73 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index b0c930cdbb51..781885faec65 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -3,7 +3,7 @@ package dotty.tools.backend.jvm import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees import dotty.tools.dotc -import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.core.Flags.{termFlagSet, termFlagConjunction} import dotty.tools.dotc.transform.{Erasure, GenericSignatures} import dotty.tools.dotc.transform.SymUtils._ import java.io.{File => _} @@ -801,7 +801,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def freshLocal(cunit: CompilationUnit, name: String, tpe: Type, pos: Position, flags: Flags): Symbol = { - ctx.newSymbol(sym, name.toTermName, FlagSet(flags), tpe, NoSymbol, pos) + ctx.newSymbol(sym, name.toTermName, termFlagSet(flags), tpe, NoSymbol, pos) } def getter(clz: Symbol): Symbol = decorateSymbol(sym).getter @@ -881,7 +881,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def =:=(other: Type): Boolean = tp =:= other def membersBasedOnFlags(excludedFlags: Flags, requiredFlags: Flags): List[Symbol] = - tp.membersBasedOnFlags(FlagSet(requiredFlags), FlagSet(excludedFlags)).map(_.symbol).toList + tp.membersBasedOnFlags(termFlagConjunction(requiredFlags), termFlagSet(excludedFlags)).map(_.symbol).toList def resultType: Type = tp.resultType diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 777fee36d345..15eb8c1e65a9 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -110,11 +110,10 @@ object Denotations { */ def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation - /** Keep only those denotations in this group whose flags do not clash with `exclusive`. - * "clash" means have one of the flags in `exclusive & ~FlippedMemberFlags` or - * not have one of the flags in `exclusive & FlippedMemberFlags`. + /** Keep only those denotations in this group that have all of the flags in `required`, + * but none of the flags in `excluded`. */ - def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): PreDenotation + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): PreDenotation private[this] var cachedPrefix: Type = _ private[this] var cachedAsSeenFrom: AsSeenFromResult = _ @@ -255,13 +254,12 @@ object Denotations { */ def accessibleFrom(pre: Type, superAccess: Boolean = false)(implicit ctx: Context): Denotation - /** Find member of this denotation with given name and - * produce a denotation that contains the type of the member + /** Find member of this denotation with given `name`, all `required` + * flags and no `excluded` flag, and produce a denotation that contains the type of the member * as seen from given prefix `pre`. - * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) */ - def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = - info.findMember(name, pre, exclusive) + def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = + info.findMember(name, pre, required, excluded) /** If this denotation is overloaded, filter with given predicate. * If result is still overloaded throw a TypeError. @@ -1077,16 +1075,17 @@ object Denotations { d >= Signature.ParamMatch && info.matches(other.info) } - final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = - if (p(this)) this else NoDenotation - final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = - if (denots.exists && denots.matches(this)) NoDenotation else this def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): SingleDenotation = if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) - final def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): SingleDenotation = - if (exclusive.isEmpty || !this.clashes(exclusive)) this else NoDenotation + + final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = + if (p(this)) this else NoDenotation + final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = + if (denots.exists && denots.matches(this)) NoDenotation else this + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): SingleDenotation = + if (required.isEmpty && excluded.isEmpty || compatibleWith(required, excluded)) this else NoDenotation type AsSeenFromResult = SingleDenotation protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { @@ -1099,15 +1098,14 @@ object Denotations { else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner)) } - /** Does this denotation have any of the normal flags in `fs`, or alternatively, - * does it lack any of the "flipped member" flags in fs? + /** Does this denotation have all the `required` flags but none of the `excluded` flags? */ - private def clashes(fs: FlagSet)(implicit ctx: Context): Boolean = { + private def compatibleWith(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Boolean = { val symd: SymDenotation = this match { case symd: SymDenotation => symd case _ => symbol.denot } - (symd.relevantFlagsFor(fs) ^ FlippedMemberFlags).is(fs) + symd.is(required) && !symd.is(excluded) } } @@ -1186,15 +1184,15 @@ object Denotations { def last: Denotation = denot2.last def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = denot1.matches(other) || denot2.matches(other) + def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation = derivedUnion(denot1 filterWithPredicate p, denot2 filterWithPredicate p) def filterDisjoint(denot: PreDenotation)(implicit ctx: Context): PreDenotation = derivedUnion(denot1 filterDisjoint denot, denot2 filterDisjoint denot) - def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = - derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) - def filterExclusive(exclusive: FlagSet)(implicit ctx: Context): PreDenotation = - derivedUnion(denot1.filterExclusive(exclusive), denot2.filterExclusive(exclusive)) - protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation): PreDenotation = + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): PreDenotation = + derivedUnion(denot1.filterWithFlags(required, excluded), denot2.filterWithFlags(required, excluded)) + protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation) = if ((denot1 eq this.denot1) && (denot2 eq this.denot2)) this else denot1 union denot2 } diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 2751c554a87c..26a71b41747b 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -58,7 +58,7 @@ object Flags { */ def is(flags: FlagConjunction): Boolean = { val fs = bits & flags.bits - (fs & KINDFLAGS) != 0 && + ((fs & KINDFLAGS) != 0 || flags.bits == 0) && (fs >>> TYPESHIFT) == (flags.bits >>> TYPESHIFT) } @@ -122,6 +122,8 @@ object Flags { override def toString: String = flagStrings.mkString(" ") } + def termFlagSet(x: Long) = FlagSet(TERMS | x) + /** A class representing flag sets that should be tested * conjunctively. I.e. for a flag conjunction `fc`, * `x is fc` tests whether `x` contains all flags in `fc`. @@ -130,6 +132,8 @@ object Flags { override def toString: String = FlagSet(bits).toString } + def termFlagConjunction(x: Long) = FlagConjunction(TERMS | x) + private final val TYPESHIFT = 2 private final val TERMindex = 0 private final val TYPEindex = 1 @@ -182,6 +186,8 @@ object Flags { flag } + def allOf(flags: FlagSet) = FlagConjunction(flags.bits) + /** The conjunction of all flags in given flag set */ def allOf(flags1: FlagSet, flags2: FlagSet): FlagConjunction = { assert(flags1.numFlags == 1 && flags2.numFlags == 1, "Flags.allOf doesn't support flag " + (if (flags1.numFlags != 1) flags1 else flags2)) @@ -200,6 +206,8 @@ object Flags { /** The empty flag set */ final val EmptyFlags: FlagSet = FlagSet(0) + final val EmptyFlagConjunction = FlagConjunction(0) + /** The undefined flag set */ final val UndefinedFlags: FlagSet = FlagSet(~KINDFLAGS) @@ -597,9 +605,6 @@ object Flags { /** An extension method */ final val ExtensionMethod = allOf(Method, Extension) - /** Flags that are required rather than excluded when mentioned in member search */ - final val FlippedMemberFlags = Extension - /** An enum case */ final val EnumCase: FlagConjunction = allOf(Enum, Case) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 454d32e26eb9..9150b587e9fd 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1671,9 +1671,9 @@ object SymDenotations { else collect(ownDenots, classParents) } - override final def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = { - val raw = if (exclusive is Private) nonPrivateMembersNamed(name) else membersNamed(name) - raw.filterExclusive(exclusive).asSeenFrom(pre).toDenot(pre) + override final def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { + val raw = if (excluded is Private) nonPrivateMembersNamed(name) else membersNamed(name) + raw.filterWithFlags(required, excluded).asSeenFrom(pre).toDenot(pre) } /** Compute tp.baseType(this) */ @@ -1920,7 +1920,7 @@ object SymDenotations { if (packageObjRunId != ctx.runId) { packageObjRunId = ctx.runId packageObjCache = NoDenotation // break cycle in case we are looking for package object itself - packageObjCache = findMember(nme.PACKAGE, thisType, EmptyFlags).asSymDenotation + packageObjCache = findMember(nme.PACKAGE, thisType, EmptyFlagConjunction, EmptyFlags).asSymDenotation } packageObjCache } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ada634ade1f5..874282366144 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -504,7 +504,7 @@ object Types { */ @tailrec final def findDecl(name: Name, excluded: FlagSet)(implicit ctx: Context): Denotation = this match { case tp: ClassInfo => - tp.decls.denotsNamed(name).filterExclusive(excluded).toDenot(NoPrefix) + tp.decls.denotsNamed(name).filterWithFlags(EmptyFlagConjunction, excluded).toDenot(NoPrefix) case tp: TypeProxy => tp.underlying.findDecl(name, excluded) case err: ErrorType => @@ -515,32 +515,29 @@ object Types { /** The member of this type with the given name */ final def member(name: Name)(implicit ctx: Context): Denotation = /*>|>*/ track("member") /*<|<*/ { - memberExclusive(name, EmptyFlags) + memberBasedOnFlags(name, required = EmptyFlagConjunction, excluded = EmptyFlags) } /** The non-private member of this type with the given name. */ final def nonPrivateMember(name: Name)(implicit ctx: Context): Denotation = track("nonPrivateMember") { - memberExclusive(name, Flags.Private) + memberBasedOnFlags(name, required = EmptyFlagConjunction, excluded = Flags.Private) } - /** The member with given `name` and required and/or excluded flags - * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) - */ - final def memberExclusive(name: Name, exclusive: FlagSet)(implicit ctx: Context): Denotation = { + /** The member with given `name` and required and/or excluded flags */ + final def memberBasedOnFlags(name: Name, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { // We need a valid prefix for `asSeenFrom` val pre = this match { case tp: ClassInfo => tp.appliedRef case _ => widenIfUnstable } - findMember(name, pre, exclusive) + findMember(name, pre, required, excluded) } - /** Find member of this type with given name and - * produce a denotation that contains the type of the member - * as seen from given prefix `pre`. - * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + /** Find member of this type with given `name`, all `required` + * flags and no `excluded` flag and produce a denotation that contains + * the type of the member as seen from given prefix `pre`. */ - final def findMember(name: Name, pre: Type, exclusive: FlagSet)(implicit ctx: Context): Denotation = { + final def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { @tailrec def go(tp: Type): Denotation = tp match { case tp: TermRef => go (tp.underlying match { @@ -550,7 +547,7 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => d.findMember(name, pre, exclusive) + case d: ClassDenotation => d.findMember(name, pre, required, excluded) case d => go(d.info) } case tp: AppliedType => @@ -582,7 +579,7 @@ object Types { case tp: TypeProxy => go(tp.underlying) case tp: ClassInfo => - tp.cls.findMember(name, pre, exclusive) + tp.cls.findMember(name, pre, required, excluded) case AndType(l, r) => goAnd(l, r) case tp: OrType => @@ -592,7 +589,7 @@ object Types { // supertype of `pre`. go(tp.join) case tp: JavaArrayType => - defn.ObjectType.findMember(name, pre, exclusive) + defn.ObjectType.findMember(name, pre, required, excluded) case err: ErrorType => ctx.newErrorSymbol(pre.classSymbol orElse defn.RootClass, name, err.msg) case _ => @@ -799,10 +796,10 @@ object Types { (name, buf) => buf ++= member(name).altsWith(x => !x.is(Method))) } - /** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */ - final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("membersBasedOnFlags") { + /** The set of members of this type that have all of `required` flags but none of `excluded` flags set. */ + final def membersBasedOnFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("membersBasedOnFlags") { memberDenots(takeAllFilter, - (name, buf) => buf ++= memberExclusive(name, excludedFlags).altsWith(x => x.is(requiredFlags))) + (name, buf) => buf ++= memberBasedOnFlags(name, required, excluded).alternatives) } /** All members of this type. Warning: this can be expensive to compute! */ diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index e08a11fb3e8d..42c2580f78c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -833,8 +833,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { /** does the companion object of the given symbol have custom unapply */ def hasCustomUnapply(sym: Symbol): Boolean = { val companion = sym.companionModule - companion.findMember(nme.unapply, NoPrefix, exclusive = Synthetic).exists || - companion.findMember(nme.unapplySeq, NoPrefix, exclusive = Synthetic).exists + companion.findMember(nme.unapply, NoPrefix, required = EmptyFlagConjunction, excluded = Synthetic).exists || + companion.findMember(nme.unapplySeq, NoPrefix, required = EmptyFlagConjunction, excluded = Synthetic).exists } def doShow(s: Space, mergeList: Boolean = false): String = s match { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bc55f62fbf6e..969ae304c052 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -41,10 +41,9 @@ trait NamerContextOps { this: Context => sym } - /** The denotation with the given name in current context - * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + /** The denotation with the given `name` and all `required` flags in current context */ - def denotNamed(name: Name, exclusive: FlagSet = EmptyFlags): Denotation = + def denotNamed(name: Name, required: FlagConjunction = EmptyFlagConjunction): Denotation = if (owner.isClass) if (outer.owner == owner) { // inner class scope; check whether we are referring to self if (scope.size == 1) { @@ -52,12 +51,12 @@ trait NamerContextOps { this: Context => if (elem.name == name) return elem.sym.denot // return self } val pre = owner.thisType - pre.findMember(name, pre, exclusive) + pre.findMember(name, pre, required, EmptyFlags) } else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. - owner.findMember(name, owner.thisType, exclusive) + owner.findMember(name, owner.thisType, required, EmptyFlags) else - scope.denotsNamed(name).filterExclusive(exclusive).toDenot(NoPrefix) + scope.denotsNamed(name).filterWithFlags(required, EmptyFlags).toDenot(NoPrefix) /** Either the current scope, or, if the current context owner is a class, * the declarations of the current class. diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4f1bd69faa61..1faa6d195cc5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -49,7 +49,8 @@ trait TypeAssigner { def addRefinement(parent: Type, decl: Symbol) = { val inherited = - parentType.findMember(decl.name, cls.thisType, exclusive = Private) + parentType.findMember(decl.name, cls.thisType, + required = EmptyFlagConjunction, excluded = Private) .suchThat(decl.matches(_)) val inheritedInfo = inherited.info if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9052953ce289..ebf8edc6ed2a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -107,13 +107,13 @@ class Typer extends Namer def newLikeThis: Typer = new Typer - /** Find the denotation of enclosing `name` in given context `ctx`. - * @param name the name of the denotation + /** Find the type of an identifier with given `name` in given context `ctx`. + * @param name the name of the identifier * @param pt the expected type - * @param exclusive flags to require (if in Flags.FlippedMemberFlags) or exclude (otherwise) + * @param required flags the result's symbol must have * @param pos position to use for error reporting */ - def findRef(name: Name, pt: Type, exclusive: FlagSet, pos: Position)(implicit ctx: Context): Type = { + def findRef(name: Name, pt: Type, required: FlagConjunction, pos: Position)(implicit ctx: Context): Type = { val refctx = ctx val noImports = ctx.mode.is(Mode.InPackageClauseName) @@ -176,7 +176,7 @@ class Typer extends Namer NoType else { val pre = imp.site - val denot = pre.memberExclusive(name, exclusive).accessibleFrom(pre)(refctx) + val denot = pre.memberBasedOnFlags(name, required, EmptyFlags).accessibleFrom(pre)(refctx) // Pass refctx so that any errors are reported in the context of the // reference instead of the if (reallyExists(denot)) pre.select(name, denot) else NoType @@ -266,7 +266,7 @@ class Typer extends Namer else (ctx.scope ne lastCtx.scope) || (curOwner ne lastCtx.owner) if (isNewDefScope) { - val defDenot = ctx.denotNamed(name, exclusive) + val defDenot = ctx.denotNamed(name, required) if (qualifies(defDenot)) { val found = if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType @@ -361,7 +361,7 @@ class Typer extends Namer unimported = Set.empty foundUnderScala2 = NoType try { - var found = findRef(name, pt, EmptyFlags, tree.pos) + var found = findRef(name, pt, EmptyFlagConjunction, tree.pos) if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { ctx.migrationWarning( ex"""Name resolution will change. @@ -2604,7 +2604,7 @@ class Typer extends Namer case SelectionProto(name, mbrType, _, _) => def tryExtension(implicit ctx: Context): Tree = try { - findRef(name, WildcardType, Extension, tree.pos) match { + findRef(name, WildcardType, ExtensionMethod, tree.pos) match { case ref: TermRef => extMethodApply(untpd.ref(ref).withPos(tree.pos), tree, mbrType) case _ => EmptyTree diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index dd15c917d604..15824c4c74e9 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -248,7 +248,7 @@ class ReplDriver(settings: Array[String], val info = symbol.info val defs = info.bounds.hi.finalResultType - .membersBasedOnFlags(Method, Accessor | ParamAccessor | Synthetic | Private) + .membersBasedOnFlags(required = allOf(Method), excluded = Accessor | ParamAccessor | Synthetic | Private) .filterNot { denot => denot.symbol.owner == defn.AnyClass || denot.symbol.owner == defn.ObjectClass || diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 587a80b86bad..b51e93ab49d6 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -44,7 +44,7 @@ class DocASTPhase extends Phase { def membersFromSymbol(sym: Symbol): List[Entity] = { if (sym.info.exists) { - val defs = sym.info.bounds.hi.finalResultType.membersBasedOnFlags(Flags.Method, Flags.Synthetic | Flags.Private) + val defs = sym.info.bounds.hi.finalResultType.membersBasedOnFlags(Flags.allOf(Flags.Method), Flags.Synthetic | Flags.Private) .filterNot(_.symbol.owner.name.show == "Any") .map { meth => DefImpl( From f25ec80102d8ebd9b233e7818ee2d66613f3699f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 12 Oct 2018 12:07:44 +0200 Subject: [PATCH 34/56] Fix rebase breakage --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4ee1451399e4..dd41c65adfd6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -991,7 +991,7 @@ trait Implicits { self: Typer => } else { val generated2 = - if (cand.isExtension) ExtMethodApply(generated1).withType(generated1.tpe) + if (cand.isExtension) Applications.ExtMethodApply(generated1).withType(generated1.tpe) else generated1 SearchSuccess(generated2, ref, cand.level)(ctx.typerState) } From 981cb71c0e78a76935db93eec45417e9bac5eed4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 12 Oct 2018 13:57:26 +0200 Subject: [PATCH 35/56] Update doc page to describe new resolution rules --- .../reference/extend/extension-methods.md | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index a5aed7a1630e..6ba0fc90820d 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -26,34 +26,56 @@ They can also be invoked as plain methods. So the following holds: assert(circle.circumference == CircleOps.circumference(circle)) ``` -### Translation of Calls to Extension Methods -The rules for resolving a selection `e.m` are augmented as follows: If `m` is not a -member of the type `T` of `e`, and there is an implicit value `i` -in either the current scope or in the implicit scope of `T`, and `i` defines an extension -method named `m`, then `e.m` is expanded to `i.m(e)`. This expansion is attempted at the time where the compiler also tries an implicit conversion from `T` to a type containing `m`. If there is more than one way -of expanding, an ambiguity error results. -So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided -`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). +### Translation of Calls to Extension Methods -### Extended Types +When is an extension method considered? There are two possibilities. The first (and recommended one) is by defining the extension method as a member of an implicit value. The method can then be used as an extension method wherever the implicit value is applicable. The second possibility is by making the extension method itself visible under a simple name, typically by importing it. As an example, consider an extension method `longestStrings` on `String`. We can either define it like this: -Extension methods can be added to arbitrary types. For instance, the following -object adds a `longestStrings` extension method to a `Seq[String]`: ```scala -implicit object StringOps { +implicit object StringSeqOps1 { def longestStrings(this xs: Seq[String]) = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } ``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` +as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. + +```scala +implicit object StringSeqOps2{ + def longestStrings(this xs: Seq[String]) = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +import StringSeqOps2.longestStrings +List("here", "is", "a", "list").longestStrings +``` +The precise rules for resolving a selection to an extension method are as follows. + +Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, +and where `T` is the expected type. The following two rewritings are tried in order: + + 1. The selection is rewritten to `m[Ts](e)`. + 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` + in either the current scope or in the implicit scope of `T`, and `i` defines an extension + method named `m`, then selection is expanded to `i.m[Ts](e)`. + This second rewriting is attempted at the time where the compiler also tries an implicit conversion + from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). ### Generic Extensions -The previous example extended a specific instance of a generic type. It is also possible +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: ```scala From 659e63f4cc81a7adc1e4b9eaf657229bb2e8a0eb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 12 Oct 2018 17:25:11 +0200 Subject: [PATCH 36/56] Fix typo. --- docs/docs/reference/extend/extension-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 6ba0fc90820d..858a5c03384c 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -49,7 +49,7 @@ is legal everywhere `StringSeqOps1` is available as an implicit value. Alternati as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. ```scala -implicit object StringSeqOps2{ +object StringSeqOps2{ def longestStrings(this xs: Seq[String]) = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) From c880d8e25802782c5eb1cb6ca152f11d7dce9f39 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 14 Oct 2018 18:18:08 +0200 Subject: [PATCH 37/56] Fixes for hasExtensionMethod Don't widen type before it is selected as a candidate - it might lose precision. --- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 4 +- tests/pos/opaque-propability-xm.scala | 41 +++++++++++++++ tests/pos/opaque-xm.scala | 35 +++++++++++++ tests/pos/opaque-xm1.scala | 34 +++++++++++++ tests/run/opaque-immutable-array-xm.scala | 51 +++++++++++++++++++ 6 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 tests/pos/opaque-propability-xm.scala create mode 100644 tests/pos/opaque-xm.scala create mode 100644 tests/pos/opaque-xm1.scala create mode 100644 tests/run/opaque-immutable-array-xm.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c06fc802f423..553a3f7d1728 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1140,7 +1140,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => */ def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { val mbr = tp.member(name).suchThat(_.is(ExtensionMethod)) - mbr.exists && isApplicable(argType.select(name, mbr), argType :: Nil, resultType) + mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) } /** Compare owner inheritance level. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index dd41c65adfd6..9031d5a3890d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -172,7 +172,7 @@ object Implicits { * Note that we always take the underlying type of a singleton type as the argument * type, so that we get a reasonable implicit cache hit ratio. */ - def adjustSingletonArg(tp: Type): Type = tp match { + def adjustSingletonArg(tp: Type): Type = tp.widenSingleton match { case tp: PolyType => val res = adjustSingletonArg(tp.resType) if (res `eq` tp.resType) tp else tp.derivedLambdaType(resType = res) @@ -198,7 +198,7 @@ object Implicits { else { val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749 val refAdjusted = - if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton) + if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref) else ref val refNorm = normalize(refAdjusted, pt) if (!NoViewsAllowed.isCompatible(refNorm, ptNorm)) diff --git a/tests/pos/opaque-propability-xm.scala b/tests/pos/opaque-propability-xm.scala new file mode 100644 index 000000000000..5fa5ec9334b2 --- /dev/null +++ b/tests/pos/opaque-propability-xm.scala @@ -0,0 +1,41 @@ +object prob { + opaque type Probability = Double + + implicit object Probability { + def apply(n: Double): Option[Probability] = + if (0.0 <= n && n <= 1.0) Some(n) else None + + def unsafe(p: Double): Probability = { + require(0.0 <= p && p <= 1.0, s"probabilities lie in [0, 1] (got $p)") + p + } + + def asDouble(p: Probability): Double = p + + val Never: Probability = 0.0 + val CoinToss: Probability = 0.5 + val Certain: Probability = 1.0 + + implicit val ordering: Ordering[Probability] = + implicitly[Ordering[Double]] + + def unary_~(this p1: Probability): Probability = Certain - p1 + def &(this p1: Probability)(p2: Probability): Probability = p1 * p2 + def |(this p1: Probability)(p2: Probability): Probability = p1 + p2 - (p1 * p2) + + def isImpossible(this p1: Probability): Boolean = p1 == Never + def isCertain(this p1: Probability): Boolean = p1 == Certain + + import scala.util.Random + + def sample(this p1: Probability)(r: Random = Random): Boolean = r.nextDouble <= p1 + def toDouble(this p1: Probability): Double = p1 + } + + val caughtTrain = Probability.unsafe(0.3) + val missedTrain = ~caughtTrain + val caughtCab = Probability.CoinToss + val arrived = caughtTrain | (missedTrain & caughtCab) + + println((1 to 5).map(_ => arrived.sample()).toList) +} diff --git a/tests/pos/opaque-xm.scala b/tests/pos/opaque-xm.scala new file mode 100644 index 000000000000..ce0545890e68 --- /dev/null +++ b/tests/pos/opaque-xm.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + implicit object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + + // This is the second way to unlift the logarithm type + def toDouble(this x: Logarithm): Double = math.exp(x) + def +(this x: Logarithm)(y: Logarithm) = Logarithm(math.exp(x) + math.exp(y)) + def *(this x: Logarithm)(y: Logarithm): Logarithm = Logarithm(x + y) + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = Logarithm.toDouble(l3) + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/pos/opaque-xm1.scala b/tests/pos/opaque-xm1.scala new file mode 100644 index 000000000000..c4f16a4cbed7 --- /dev/null +++ b/tests/pos/opaque-xm1.scala @@ -0,0 +1,34 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + type Logarithm = Double + + implicit object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = ??? + + def safe(d: Double): Option[Logarithm] = ??? + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = ??? + + // Extension methods define opaque types' public APIs + + // This is the second way to unlift the logarithm type + def toDouble(this x: Logarithm): Double = ??? + def +++(this x: Logarithm)(y: Logarithm) = ??? + def ***(this x: Logarithm)(y: Logarithm): Logarithm = ??? + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l *** l2 + val l4 = l +++ l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/run/opaque-immutable-array-xm.scala b/tests/run/opaque-immutable-array-xm.scala new file mode 100644 index 000000000000..dac7463d9bb7 --- /dev/null +++ b/tests/run/opaque-immutable-array-xm.scala @@ -0,0 +1,51 @@ +import scala.reflect.ClassTag +object Test extends App { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + implicit object IArray { + inline def initialize[A](body: => Array[A]): IArray[A] = body + def apply[A: ClassTag](xs: A*): IArray[A] = initialize(Array(xs: _*)) + + // These should be inline but that does not work currently. Try again + // once inliner is moved to ReifyQuotes + def length[A](this ia: IArray[A]): Int = (ia: Array[A]).length + def apply[A](this ia: IArray[A])(i: Int): A = (ia: Array[A])(i) + + // return a sorted copy of the array + def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { + val arr = Arrays.copyOf(ia, (ia: Array[A]).length) + scala.util.Sorting.quickSort(arr) + arr + } + + // use a standard java method to search a sorted IArray. + // (note that this doesn't mutate the array). + def binarySearch(ia: IArray[Long], elem: Long): Int = + Arrays.binarySearch(ia, elem) + } + + // same as IArray.binarySearch but implemented by-hand. + // + // given a sorted IArray, returns index of `elem`, + // or a negative value if not found. + def binaryIndexOf(ia: IArray[Long], elem: Long): Int = { + var lower: Int = 0 + var upper: Int = ia.length - 1 + while (lower <= upper) { + val middle = (lower + upper) >>> 1 + val n = ia(middle) + + if (n == elem) return middle + else if (n < elem) lower = middle + 1 + else upper = middle - 1 + } + -lower - 1 + } + + val xs: IArray[Long] = IArray(1L, 2L, 3L) + assert(binaryIndexOf(xs, 2L) == 1) + assert(binaryIndexOf(xs, 4L) < 0) +} \ No newline at end of file From 5a4adf74bf5edc1e0e04a396c44584b8c9f649ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Oct 2018 16:59:19 +0200 Subject: [PATCH 38/56] Clarify relationship to operators --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- docs/docs/reference/extend/extension-methods.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index b334ca2b1316..9318c9e063b2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -281,7 +281,7 @@ object TypeErasure { // From the spec, "Linearization also satisfies the property that a // linearization of a class always contains the linearization of its - // direct superclass as a suffix"; it's enought to consider every + // direct superclass as a suffix"; it's enough to consider every // candidate up to the first class. val candidates = takeUntil(tp2superclasses)(!_.is(Trait)) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 858a5c03384c..50bbbd813a4b 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -73,6 +73,12 @@ and where `T` is the expected type. The following two rewritings are tried in or So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided `circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). +**Note**: The translation of extension methods is formulated on method calls. It is thus indepenent from the way infix operations are translated to method calls. For instamce, +if `+:` was formulated as an extension method, it would still have the `this` parameter come first, even though, seen as an operator, `+:` is right-binding: +```scala +def +: [T](this xs: Seq[T))(x: T): Seq[T] +``` + ### Generic Extensions The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible From 60aa01996999c9fe1c4529425c3b9749f7b98dbf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Oct 2018 18:34:17 +0200 Subject: [PATCH 39/56] Integrate extension methods docs in sidebar --- docs/docs/reference/{extend => }/extension-methods.md | 0 docs/sidebar.yml | 2 ++ 2 files changed, 2 insertions(+) rename docs/docs/reference/{extend => }/extension-methods.md (100%) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extension-methods.md similarity index 100% rename from docs/docs/reference/extend/extension-methods.md rename to docs/docs/reference/extension-methods.md diff --git a/docs/sidebar.yml b/docs/sidebar.yml index eeb33c3e63c1..805b41359f2b 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -55,6 +55,8 @@ sidebar: url: docs/reference/tasty-reflect.html - title: Opaque Type Aliases url: docs/reference/opaques.html + - title: Extension Methods + url: docs/reference/extension-methods.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling From 286a5d6cf2344d4d56907d9b1b97118a4fd69f5d Mon Sep 17 00:00:00 2001 From: Denis Buzdalov Date: Fri, 26 Oct 2018 13:20:31 +0200 Subject: [PATCH 40/56] Update docs/docs/reference/extension-methods.md Co-Authored-By: odersky --- docs/docs/reference/extension-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md index 50bbbd813a4b..2d34ced1d43b 100644 --- a/docs/docs/reference/extension-methods.md +++ b/docs/docs/reference/extension-methods.md @@ -76,7 +76,7 @@ So `circle.circumference` translates to `CircleOps.circumference(circle)`, provi **Note**: The translation of extension methods is formulated on method calls. It is thus indepenent from the way infix operations are translated to method calls. For instamce, if `+:` was formulated as an extension method, it would still have the `this` parameter come first, even though, seen as an operator, `+:` is right-binding: ```scala -def +: [T](this xs: Seq[T))(x: T): Seq[T] +def +: [T](this xs: Seq[T])(x: T): Seq[T] ``` ### Generic Extensions From c19c24056d96fe8566009fbbec8dcf008fd656c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Oct 2018 10:59:25 +0200 Subject: [PATCH 41/56] Address review comments --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 9150b587e9fd..c71965801142 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -863,7 +863,7 @@ object SymDenotations { /** The module implemented by this module class, NoSymbol if not applicable. */ final def sourceModule(implicit ctx: Context): Symbol = myInfo match { case ClassInfo(_, _, _, _, selfType) if this is ModuleClass => - def sourceOfSelf(tp: Any): Symbol = tp match { + def sourceOfSelf(tp: TypeOrSymbol): Symbol = tp match { case tp: TermRef => tp.symbol case tp: Symbol => sourceOfSelf(tp.info) case tp: RefinedType => sourceOfSelf(tp.parent) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 9031d5a3890d..228bd051ee09 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -525,7 +525,8 @@ trait ImplicitRunInfo { self: Run => for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.classSymbol))) addRef(ref) } - if (tp.widen.typeSymbol.isOpaqueAlias) addCompanionOf(tp.widen.typeSymbol) + val underlyingTypeSym = tp.widen.typeSymbol + if (underlyingTypeSym.isOpaqueAlias) addCompanionOf(underlyingTypeSym) else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) From abddd42876c36d379bf36f6c85933f73c685f049 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Nov 2018 14:55:15 +0100 Subject: [PATCH 42/56] Add disabled string interpolation test --- .../src/dotty/tools/dotc/typer/Inliner.scala | 1 + .../run/xml-interpolation-3/Test_2.scala | 11 +++++++++++ .../run/xml-interpolation-3/XmlQuote_1.scala | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 tests/disabled/run/xml-interpolation-3/Test_2.scala create mode 100644 tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1aef548cd2ae..969e8126a095 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -240,6 +240,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } computeParamBindings(tp.resultType, Nil, argss) case tp: MethodType => + assert(argss.nonEmpty, i"missing bindings: $tp in $call") (tp.paramNames, tp.paramInfos, argss.head).zipped.foreach { (name, paramtp, arg) => paramBinding(name) = arg.tpe.dealias match { case _: SingletonType if isIdempotentExpr(arg) => arg.tpe diff --git a/tests/disabled/run/xml-interpolation-3/Test_2.scala b/tests/disabled/run/xml-interpolation-3/Test_2.scala new file mode 100644 index 000000000000..9a3d8d64161b --- /dev/null +++ b/tests/disabled/run/xml-interpolation-3/Test_2.scala @@ -0,0 +1,11 @@ +import XmlQuote._ + +object Test { + def main(args: Array[String]): Unit = { + + assert(xml"Hello Allan!" == Xml("Hello Allan!", Nil)) + + val name = new Object{} + assert(xml"Hello $name!" == Xml("Hello ??!", List(name))) + } +} diff --git a/tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala b/tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala new file mode 100644 index 000000000000..f99da7d01204 --- /dev/null +++ b/tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala @@ -0,0 +1,19 @@ +import scala.quoted._ +import scala.tasty.Tasty + +import scala.language.implicitConversions + +case class Xml(parts: String, args: List[Any]) + +object XmlQuote { + + implicit object SCOps { + inline def xml(this inline ctx: StringContext)(args: => Any*): Xml = + ~XmlQuote.impl(ctx, '(args)) + } + + def impl(receiver: StringContext, args: Expr[Seq[Any]]): Expr[Xml] = { + val string = receiver.parts.mkString("??") + '(new Xml(~string.toExpr, (~args).toList)) + } +} From cb7a9d803f5a7c800d543ed25c8c72057a67baf3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 00:51:16 +0100 Subject: [PATCH 43/56] Revert: Dotty docs for "this as modifier" scheme (reverted from commit cf68e01b9f54aeca2fe968d68b23a3bc5e4d0483) --- docs/docs/reference/extension-methods.md | 105 +++++++++++++++-------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md index 2d34ced1d43b..1c117aa6ba2a 100644 --- a/docs/docs/reference/extension-methods.md +++ b/docs/docs/reference/extension-methods.md @@ -9,7 +9,7 @@ Extension methods allow one to add methods to a type after the type is defined. case class Circle(x: Double, y: Double, radius: Double) implicit object CircleOps { - def circumference(this c: Circle): Double = c.radius * math.Pi * 2 + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } ``` @@ -20,9 +20,15 @@ implicit object CircleOps { circle.circumference ``` -Extension methods are methods that have a `this` modifier for the first parameter. -They can also be invoked as plain methods. So the following holds: +### Translation of Extension Methods + +Extension methods are methods that have a parameter clause in front of the defined +identifier. They translate to methods where the leading parameter section is moved +to after the defined identifier. So, the definition of `circumference` above translates +to the plain method, and can also be invoked as such: ```scala +def circumference(c: Circle): Double = c.radius * math.Pi * 2 + assert(circle.circumference == CircleOps.circumference(circle)) ``` @@ -35,7 +41,7 @@ When is an extension method considered? There are two possibilities. The first ( ```scala implicit object StringSeqOps1 { - def longestStrings(this xs: Seq[String]) = { + def (xs: Seq[String]) longestStrings = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } @@ -49,8 +55,8 @@ is legal everywhere `StringSeqOps1` is available as an implicit value. Alternati as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. ```scala -object StringSeqOps2{ - def longestStrings(this xs: Seq[String]) = { +object StringOps2 { + def (xs: Seq[String]) longestStrings = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } @@ -73,11 +79,26 @@ and where `T` is the expected type. The following two rewritings are tried in or So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided `circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). -**Note**: The translation of extension methods is formulated on method calls. It is thus indepenent from the way infix operations are translated to method calls. For instamce, -if `+:` was formulated as an extension method, it would still have the `this` parameter come first, even though, seen as an operator, `+:` is right-binding: -```scala -def +: [T](this xs: Seq[T])(x: T): Seq[T] +### 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. +Examples: +``` + def (x: String) < (y: String) = ... + def (x: Elem) +: (xs: Seq[Elem]) = ... + + "ab" + "c" + 1 +: List(2, 3) +``` +The two definitions above translate to +``` + def < (x: String)(y: String) = ... + def +: (xs: Seq[Elem])(x: Elem) = ... ``` +Note that 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. ### Generic Extensions @@ -86,7 +107,7 @@ to extend a generic type by adding type parameters to an extension method: ```scala implicit object ListOps { - def second[T](this xs: List[T]) = xs.tail.head + def (xs: List[T]) second [T] = xs.tail.head } ``` @@ -95,11 +116,19 @@ or: ```scala implicit object ListListOps { - def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) + def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +or: + +```scala +implicit object NumericOps { + def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) } ``` -As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the parameter clause that precedes the defined method name. +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. ### A Larger Example @@ -117,7 +146,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) implicit object Ensuring { - def ensuring[T](this x: T)(condition: implicit WrappedResult[T] => Boolean): T = { + def (x: T) ensuring [T](condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(x) assert(condition) x @@ -140,17 +169,19 @@ to pass along to the `result` method. `WrappedResult` is a fresh type, to make s result } -### Rules for Overriding Extension Methods - -Extension methods may override only extension methods and can be overridden only by extension methods. +**A note on formatting** Having a parameter section in front of the defined +method name makes it visually harder to discern what is being defined. To address +that problem, it is recommended that the name of an extension method is +preceded by a space and is also followed by a space if there are more parameters +to come. -### Extension Methods and TypeClasses +`### Extension Methods and TypeClasses The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. ```scala // Two typeclasses: trait SemiGroup[T] { - def combine(this x: T)(y: T): T + def (x: T) combine(y: T): T } trait Monoid[T] extends SemiGroup[T] { def unit: T @@ -158,7 +189,7 @@ The rules for expanding extension methods make sure that they work seamlessly wi // An instance declaration: implicit object StringMonoid extends Monoid[String] { - def combine(this x: String)(y: String): String = x.concat(y) + def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -175,21 +206,21 @@ extension methods apply everywhere their enclosing object is available as an imp As another example, consider implementations of an `Ord` type class with a `minimum` value: ```scala - trait Ord[T] { - def compareTo(this x: T)(y: T): Int - def < (this x: T)(y: T) = x.compareTo(y) < 0 - def > (this x: T)(y: T) = x.compareTo(y) > 0 + trait Ord[T] + def (x: T) compareTo (y: T): Int + def (x: T) < (y: T) = x.compareTo(y) < 0 + def (x: T) > (y: T) = x.compareTo(y) > 0 val minimum: T } implicit object IntOrd extends Ord[Int] { - def compareTo(this x: Int)(y: Int) = + def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } - implicit class ListOrd[T: Ord] extends Ord[List[T]] { - def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + class ListOrd[T: Ord] extends Ord[List[T]] { + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 @@ -199,15 +230,13 @@ As another example, consider implementations of an `Ord` type class with a `mini } val minimum: List[T] = Nil } + implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] + def max[T: Ord](x: T, y: T): T = if (x < y) y else x def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) ``` -The `ListOrd` class is generic - it works for any type argument `T` that is itself an instance of `Ord`. In current Scala, we could not define `ListOrd` as an implicit class since implicit classes can only define implicit converions that take exactly one non-implicit value parameter. We propose to drop this requirement and to also allow implicit classes without any value parameters, or with only implicit value parameters. The generated implicit method would in each case follow the signature of the class. That is, for `ListOrd` we'd generate the method: -```scala - implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] -``` ### Higher Kinds @@ -215,25 +244,25 @@ Extension methods generalize to higher-kinded types without requiring special pr ```scala trait Functor[F[_]] { - def map[A, B](this x: F[A])(f: A => B): F[B] + def (x: F[A]) map [A, B](f: A => B): F[B] } trait Monad[F[_]] extends Functor[F] { - def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B] - def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure) + 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 pure[A](x: A): F[A] } implicit object ListMonad extends Monad[List] { - def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + def (xs: List[A]) flatMap [A, B](f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + def (r: Ctx => A) flatMap [A, B](f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x @@ -244,8 +273,8 @@ Extension methods generalize to higher-kinded types without requiring special pr The required syntax extension just adds one clause for extension methods relative to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). ``` -DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses -ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ +DefSig ::= ... + | ‘(’ DefParam ‘)’ id [DefTypeParamClause] DefParamClauses ``` From f00cf7bc07f7d903d8e5779d39fa75dad22b3a2d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 00:56:48 +0100 Subject: [PATCH 44/56] Revert: Parsing and pretty-printing for "this as modifier" scheme (reverted from commit dc36648f0708343bd52c9662602d612af31bba2b) --- .../dotty/tools/dotc/parsing/Parsers.scala | 32 +++++++------------ .../tools/dotc/printing/RefinedPrinter.scala | 12 +++---- docs/docs/internals/syntax.md | 1 - 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 66c95974a0df..d6e5006f81fe 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1964,19 +1964,15 @@ object Parsers { * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param * DefParamClauses ::= {DefParamClause} [[nl] `(' [FunArgMods] DefParams `)'] - * DefParamClause ::= [nl] `(' [DefParams] ')' - * ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ + * DefParamClause ::= [nl] `(' [`erased'] [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] - * - * @return The parameter definitions, and whether leading parameter is tagged `this` */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): (List[List[ValDef]], Boolean) = { + def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once - var firstClause = true - var isExtension = false + var firstClauseOfCaseClass = ofCaseClass def param(): ValDef = { val start = in.offset var mods = annotsAsMods() @@ -1995,7 +1991,7 @@ object Parsers { else { if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) syntaxError("`val' or `var' expected") - if (firstClause && ofCaseClass) mods + if (firstClauseOfCaseClass) mods else mods | PrivateLocal } } @@ -2024,11 +2020,6 @@ object Parsers { } def paramClause(): List[ValDef] = inParens { if (in.token == RPAREN) Nil - else if (in.token == THIS && firstClause && !owner.isTypeName && owner != nme.CONSTRUCTOR) { - in.nextToken() - isExtension = true - param() :: Nil - } else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -2050,7 +2041,7 @@ object Parsers { if (in.token == LPAREN) { imods = EmptyModifiers paramClause() :: { - firstClause = false + firstClauseOfCaseClass = false if (imods is Implicit) Nil else clauses() } } else Nil @@ -2068,7 +2059,7 @@ object Parsers { listOfErrors.foreach { vparam => syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) } - (result, isExtension) + result } /* -------- DEFS ------------------------------------------- */ @@ -2220,7 +2211,7 @@ object Parsers { /** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type - * DefSig ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses + * DefSig ::= id [DefTypeParamClause] ParamClauses */ def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { @@ -2234,7 +2225,7 @@ object Parsers { } if (in.token == THIS) { in.nextToken() - val (vparamss, _) = paramClauses(nme.CONSTRUCTOR) + val vparamss = paramClauses(nme.CONSTRUCTOR) if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) @@ -2242,11 +2233,10 @@ object Parsers { } makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start)) } else { - var mods1 = addFlag(mods, Method) + val mods1 = addFlag(mods, Method) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val (vparamss, isExtension) = paramClauses(name) - if (isExtension) mods1 = mods1 | Extension + val vparamss = paramClauses(name) var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { in.nextToken() @@ -2375,7 +2365,7 @@ object Parsers { def classConstr(owner: TypeName, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val (vparamss, _) = paramClauses(owner, isCaseClass) + val vparamss = paramClauses(owner, isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 7476634cde49..46a9748bfd52 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -663,13 +663,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]], isExtension: Boolean = false): Text = { - var isExtensionParam = isExtension - def paramsText(params: List[ValDef[T]]) = { - val txt = "(" ~ (("this ": Text) provided isExtensionParam) ~ toText(params, ", ") ~ ")" - isExtensionParam = false - txt - } - (txt /: vparamss)((txt, vparams) => txt ~ paramsText(vparams)) + def paramsText(params: List[ValDef[T]]) = "(" ~ toText(params, ", ") ~ ")" + val (leading, paramss) = + if (isExtension && vparamss.nonEmpty) (paramsText(vparamss.head) ~ " " ~ txt, vparamss.tail) + else (txt, vparamss) + (txt /: paramss)((txt, params) => txt ~ paramsText(params)) } protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _} diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index e04269fa4c54..483394f8d4ea 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -277,7 +277,6 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ -ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` From 12cb7a169c65de452753a37e29a1b04b94aa1210 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 00:58:09 +0100 Subject: [PATCH 45/56] Revert: Change syntax to "this modifier" scheme (reverted from commit 06bd52989a6d7c0cb68edaa38ff61486c40a01d0) --- docs/docs/internals/syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 483394f8d4ea..d83734dc0854 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -319,7 +319,7 @@ 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 ::= id [DefTypeParamClause] [ExtParamClause] DefParamClauses +DefSig ::= id [DefTypeParamClause] DefParamClauses TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType From dd1db7acf5cc594fb209a188e27e053eed4fdf7a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 01:01:08 +0100 Subject: [PATCH 46/56] Revert: Switch to prefix type arguments (reverted from commit d6ba157192ca9eb8426b69fc6224921b4c44ac8f) --- docs/docs/reference/extension-methods.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md index 1c117aa6ba2a..8c2fb464c299 100644 --- a/docs/docs/reference/extension-methods.md +++ b/docs/docs/reference/extension-methods.md @@ -32,8 +32,6 @@ def circumference(c: Circle): Double = c.radius * math.Pi * 2 assert(circle.circumference == CircleOps.circumference(circle)) ``` - - ### Translation of Calls to Extension Methods When is an extension method considered? There are two possibilities. The first (and recommended one) is by defining the extension method as a member of an implicit value. The method can then be used as an extension method wherever the implicit value is applicable. The second possibility is by making the extension method itself visible under a simple name, typically by importing it. As an example, consider an extension method `longestStrings` on `String`. We can either define it like this: @@ -175,7 +173,7 @@ that problem, it is recommended that the name of an extension method is preceded by a space and is also followed by a space if there are more parameters to come. -`### Extension Methods and TypeClasses +### Extension Methods and TypeClasses The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. ```scala From 401931e5fe11e6d5fbe564631e0c2385c177e014 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 14:13:29 +0100 Subject: [PATCH 47/56] Extension methods with leading parameter list --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 4 +- .../dotty/tools/dotc/parsing/Parsers.scala | 173 ++++++++++-------- docs/docs/internals/syntax.md | 5 +- docs/docs/reference/extension-methods.md | 2 +- tests/pos/opaque-xm.scala | 6 +- 5 files changed, 107 insertions(+), 83 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 69d8b32bd36e..88b0dd515bd8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -345,9 +345,9 @@ object Trees { def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(untpd.Modifiers(flags)) - def setComment(comment: Option[Comment]): ThisTree[Untyped] = { + def setComment(comment: Option[Comment]): this.type = { comment.map(putAttachment(DocComment, _)) - asInstanceOf[ThisTree[Untyped]] + this } /** Destructively update modifiers. To be used with care. */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d6e5006f81fe..738dfd3364ee 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1968,15 +1968,21 @@ object Parsers { * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] - */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { - var imods: Modifiers = EmptyModifiers + * + * @return the list of parameter definitions + */ + def paramClause(ofClass: Boolean = false, // owner is a class + ofCaseClass: Boolean = false, // owner is a case class + ofMethod: Boolean = false, // owner is a method or constructor + prefix: Boolean = false, // clause precedes name of an extension method + firstClause: Boolean = false) // clause is the first in regular list of clauses + : List[ValDef] = { var implicitOffset = -1 // use once - var firstClauseOfCaseClass = ofCaseClass - def param(): ValDef = { + + def param(impliedMods: Modifiers): ValDef = { val start = in.offset - var mods = annotsAsMods() - if (owner.isTypeName) { + var mods = impliedMods.withAnnotations(annotations()) + if (ofClass) { mods = addFlag(modifiers(start = mods), ParamAccessor) mods = atPos(start, in.offset) { @@ -1989,9 +1995,9 @@ object Parsers { addMod(mods, mod) } else { - if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) + if (!(mods.flags &~ (ParamAccessor | Inline | impliedMods.flags)).isEmpty) syntaxError("`val' or `var' expected") - if (firstClauseOfCaseClass) mods + if (firstClause && ofCaseClass) mods else mods | PrivateLocal } } @@ -2004,7 +2010,7 @@ object Parsers { atPos(start, nameStart) { val name = ident() accept(COLON) - if (in.token == ARROW && owner.isTypeName && !(mods is Local)) + if (in.token == ARROW && ofClass && !(mods is Local)) syntaxError(VarValParametersMayNotBeCallByName(name, mods is Mutable)) val tpt = paramType() val default = @@ -2014,56 +2020,75 @@ object Parsers { mods = mods.withPos(mods.pos.union(Position(implicitOffset, implicitOffset))) implicitOffset = -1 } - for (imod <- imods.mods) mods = addMod(mods, imod) ValDef(name, tpt, default).withMods(mods) } } - def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + + def checkVarArgsRules(vparams: List[ValDef]): Unit = vparams match { + case Nil => + case _ :: Nil if !prefix => + case vparam :: rest => + vparam.tpt match { + case PostfixOp(_, op) if op.name == tpnme.raw.STAR => + syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) + case _ => + } + checkVarArgsRules(rest) + } + + // begin paramClause + inParens { + if (in.token == RPAREN && !prefix) Nil else { - def funArgMods(): Unit = { + def funArgMods(mods: Modifiers): Modifiers = if (in.token == IMPLICIT) { implicitOffset = in.offset - imods = addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }) - funArgMods() - } else if (in.token == ERASED) { - imods = addMod(imods, atPos(accept(ERASED)) { Mod.Erased() }) - funArgMods() + funArgMods(addMod(mods, atPos(accept(IMPLICIT)) { Mod.Implicit() })) } - } - funArgMods() - - commaSeparated(() => param()) + else if (in.token == ERASED) + funArgMods(addMod(mods, atPos(accept(ERASED)) { Mod.Erased() })) + else mods + + val paramMods = funArgMods(EmptyModifiers) + val clause = + if (prefix) param(paramMods) :: Nil + else commaSeparated(() => param(paramMods)) + checkVarArgsRules(clause) + clause } } - def clauses(): List[List[ValDef]] = { + } + + /** ClsParamClauses ::= {ClsParamClause} + * DefParamClauses ::= {DefParamClause} + * + * @return The parameter definitions + */ + def paramClauses(ofClass: Boolean = false, + ofCaseClass: Boolean = false, + ofMethod: Boolean = false): (List[List[ValDef]]) = { + def recur(firstClause: Boolean): List[List[ValDef]] = { newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { - imods = EmptyModifiers - paramClause() :: { - firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() - } - } else Nil - } - val start = in.offset - val result = clauses() - if (owner == nme.CONSTRUCTOR && (result.isEmpty || (result.head take 1 exists (_.mods is Implicit)))) { - in.token match { - case LBRACKET => syntaxError("no type parameters allowed here") - case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) - case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), start) + val params = paramClause( + ofClass = ofClass, + ofCaseClass = ofCaseClass, + ofMethod = ofMethod, + firstClause = firstClause) + val lastClause = + params.nonEmpty && params.head.mods.flags.is(Implicit) + params :: (if (lastClause) Nil else recur(firstClause = false)) } + else Nil } - val listOfErrors = checkVarArgsRules(result) - listOfErrors.foreach { vparam => - syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) - } - result + recur(firstClause = true) } /* -------- DEFS ------------------------------------------- */ + def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] = + md.withMods(mods).setComment(in.getDocComment(start)) + /** Import ::= import ImportExpr {`,' ImportExpr} */ def importClause(): List[Tree] = { @@ -2189,29 +2214,16 @@ object Parsers { } else EmptyTree lhs match { case (id @ Ident(name: TermName)) :: Nil => { - ValDef(name, tpt, rhs).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(ValDef(name, tpt, rhs), mods, start) } case _ => PatDef(mods, lhs, tpt, rhs) } } - private def checkVarArgsRules(vparamss: List[List[ValDef]]): List[ValDef] = { - def isVarArgs(tpt: Trees.Tree[Untyped]): Boolean = tpt match { - case PostfixOp(_, op) if op.name == tpnme.raw.STAR => true - case _ => false - } - - vparamss.flatMap { params => - if (params.nonEmpty) { - params.init.filter(valDef => isVarArgs(valDef.tpt)) - } else List() - } - } - /** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type - * DefSig ::= id [DefTypeParamClause] ParamClauses + * DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] ParamClauses */ def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { @@ -2225,7 +2237,13 @@ object Parsers { } if (in.token == THIS) { in.nextToken() - val vparamss = paramClauses(nme.CONSTRUCTOR) + val vparamss = paramClauses() + if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.is(Implicit))) + in.token match { + case LBRACKET => syntaxError("no type parameters allowed here") + case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) + case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart) + } if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) @@ -2233,10 +2251,15 @@ object Parsers { } makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start)) } else { - val mods1 = addFlag(mods, Method) + val (leadingParamss: List[List[ValDef]], flags: FlagSet) = + if (in.token == LPAREN) + (paramClause(ofMethod = true, prefix = true) :: Nil, Method | Extension) + else + (Nil, Method) + val mods1 = addFlag(mods, flags) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(name) + val vparamss = leadingParamss ::: paramClauses(ofMethod = true) var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { in.nextToken() @@ -2262,7 +2285,7 @@ object Parsers { accept(EQUALS) expr() } - DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(in.getDocComment(start)) + finalizeDef(DefDef(name, tparams, vparamss, tpt, rhs), mods1, start) } } @@ -2302,7 +2325,7 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Type) def makeTypeDef(rhs: Tree): Tree = - TypeDef(name, lambdaAbstract(tparams, rhs)).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(name, lambdaAbstract(tparams, rhs)), mods, start) in.token match { case EQUALS => in.nextToken() @@ -2355,23 +2378,23 @@ object Parsers { } def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { - val constr = classConstr(name, isCaseClass = mods is Case) + val constr = classConstr(isCaseClass = mods.is(Case)) val templ = templateOpt(constr) - TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(name, templ), mods, start) } /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses */ - def classConstr(owner: TypeName, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { + def classConstr(isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val cmods = fromWithinClassConstr(constrModsOpt()) + val vparamss = paramClauses(ofClass = true, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } /** ConstrMods ::= {Annotation} [AccessModifier] */ - def constrModsOpt(owner: Name): Modifiers = + def constrModsOpt(): Modifiers = modifiers(accessModifierTokens, annotsAsMods()) /** ObjectDef ::= id TemplateOpt @@ -2382,7 +2405,7 @@ object Parsers { def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { val template = templateOpt(emptyConstructor) - ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(ModuleDef(name, template), mods, start) } /** EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody @@ -2390,9 +2413,9 @@ object Parsers { def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): TypeDef = atPos(start, nameStart) { val modName = ident() val clsName = modName.toTypeName - val constr = classConstr(clsName) + val constr = classConstr() val impl = templateOpt(constr, isEnum = true) - TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(clsName, impl), addMod(mods, enumMod), start) } /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) @@ -2416,12 +2439,12 @@ object Parsers { val caseDef = if (in.token == LBRACKET || in.token == LPAREN || in.token == AT || isModifier) { val clsName = id.name.toTypeName - val constr = classConstr(clsName, isCaseClass = true) + val constr = classConstr(isCaseClass = true) TypeDef(clsName, caseTemplate(constr)) } else ModuleDef(id.name.toTermName, caseTemplate(emptyConstructor)) - caseDef.withMods(mods1).setComment(in.getDocComment(start)) + finalizeDef(caseDef, mods1, start) } } } @@ -2564,7 +2587,7 @@ object Parsers { case Typed(tree @ This(EmptyTypeIdent), tpt) => self = makeSelfDef(nme.WILDCARD, tpt).withPos(first.pos) case _ => - val ValDef(name, tpt, _) = convertToParam(first, expected = "self type clause") + val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause") if (name != nme.ERROR) self = makeSelfDef(name, tpt).withPos(first.pos) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index d83734dc0854..06da1afd9f4c 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -278,7 +278,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` ### Bindings and Imports @@ -319,7 +319,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 ::= id [DefTypeParamClause] DefParamClauses +DefSig ::= ‘(’ DefParam ‘)’ [nl] id + [DefTypeParamClause] DefParamClauses TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md index 8c2fb464c299..a7d0c02d2a5e 100644 --- a/docs/docs/reference/extension-methods.md +++ b/docs/docs/reference/extension-methods.md @@ -272,7 +272,7 @@ The required syntax extension just adds one clause for extension methods relativ to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). ``` DefSig ::= ... - | ‘(’ DefParam ‘)’ id [DefTypeParamClause] DefParamClauses + | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses ``` diff --git a/tests/pos/opaque-xm.scala b/tests/pos/opaque-xm.scala index ce0545890e68..2a936787219d 100644 --- a/tests/pos/opaque-xm.scala +++ b/tests/pos/opaque-xm.scala @@ -16,9 +16,9 @@ object opaquetypes { // Extension methods define opaque types' public APIs // This is the second way to unlift the logarithm type - def toDouble(this x: Logarithm): Double = math.exp(x) - def +(this x: Logarithm)(y: Logarithm) = Logarithm(math.exp(x) + math.exp(y)) - def *(this x: Logarithm)(y: Logarithm): Logarithm = Logarithm(x + y) + def (x: Logarithm) toDouble: Double = math.exp(x) + def (x: Logarithm) + (y: Logarithm) = Logarithm(math.exp(x) + math.exp(y)) + def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y) } } object usesites { From 3d4366f3089e9981d2f749f455f5d7150bbaa092 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 16:02:06 +0100 Subject: [PATCH 48/56] Handle positions in extension methods 1. Take extension methods into account when checking positions. 2. Fix position of empty type tree in method definition Without this measure, the empty type tree gets the end position of the preceding element. For an extension method with type parameters this is the extension parameter list, but it should be the type parameters. Fixing the position explicitly fixes the problem. --- compiler/src/dotty/tools/dotc/ast/Positioned.scala | 8 +++++++- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 9db97de44590..3a3bf5467010 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -4,7 +4,7 @@ package ast import util.Positions._ import core.Contexts.Context import core.Decorators._ -import core.Flags.JavaDefined +import core.Flags.{JavaDefined, Extension} import core.StdNames.nme /** A base class for things that have positions (currently: modifiers and trees) @@ -208,6 +208,12 @@ abstract class Positioned extends Product { // Leave out tparams, they are copied with wrong positions from parent class check(tree.mods) check(tree.vparamss) + case tree: DefDef if tree.mods.is(Extension) && tree.vparamss.nonEmpty => + check(tree.vparamss.head) + check(tree.tparams) + check(tree.vparamss.tail) + check(tree.tpt) + check(tree.rhs) case _ => val end = productArity var n = 0 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 738dfd3364ee..b7fb40fad394 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1022,7 +1022,7 @@ object Parsers { def typedOpt(): Tree = if (in.token == COLON) { in.nextToken(); toplevelTyp() } - else TypeTree() + else TypeTree().withPos(Position(in.lastOffset)) def typeDependingOn(location: Location.Value): Tree = if (location == Location.InParens) typ() From 4c4c1e8902dae0c7cf5f2b5cdeab0412014afb3e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 16:06:16 +0100 Subject: [PATCH 49/56] Update tests to use prefix parameter syntax for extension methods --- tests/neg/extension-methods.scala | 2 +- tests/pos/opaque-propability-xm.scala | 14 +++++----- tests/pos/opaque-xm1.scala | 34 ----------------------- tests/run/extension-methods.scala | 34 +++++++++++------------ tests/run/opaque-immutable-array-xm.scala | 4 +-- 5 files changed, 27 insertions(+), 61 deletions(-) delete mode 100644 tests/pos/opaque-xm1.scala diff --git a/tests/neg/extension-methods.scala b/tests/neg/extension-methods.scala index 28fa3662bad6..a16fa8fee751 100644 --- a/tests/neg/extension-methods.scala +++ b/tests/neg/extension-methods.scala @@ -1,7 +1,7 @@ object Test { implicit object O { - def l1(this x: String) = x.length + def (x: String) l1 = x.length def l1(x: Int) = x * x def l2(x: String) = x.length } diff --git a/tests/pos/opaque-propability-xm.scala b/tests/pos/opaque-propability-xm.scala index 5fa5ec9334b2..2025994eacbb 100644 --- a/tests/pos/opaque-propability-xm.scala +++ b/tests/pos/opaque-propability-xm.scala @@ -19,17 +19,17 @@ object prob { implicit val ordering: Ordering[Probability] = implicitly[Ordering[Double]] - def unary_~(this p1: Probability): Probability = Certain - p1 - def &(this p1: Probability)(p2: Probability): Probability = p1 * p2 - def |(this p1: Probability)(p2: Probability): Probability = p1 + p2 - (p1 * p2) + def (p1: Probability) unary_~ : Probability = Certain - p1 + def (p1: Probability) & (p2: Probability): Probability = p1 * p2 + def (p1: Probability) | (p2: Probability): Probability = p1 + p2 - (p1 * p2) - def isImpossible(this p1: Probability): Boolean = p1 == Never - def isCertain(this p1: Probability): Boolean = p1 == Certain + def (p1: Probability) isImpossible: Boolean = p1 == Never + def (p1: Probability) isCertain: Boolean = p1 == Certain import scala.util.Random - def sample(this p1: Probability)(r: Random = Random): Boolean = r.nextDouble <= p1 - def toDouble(this p1: Probability): Double = p1 + def (p1: Probability) sample (r: Random = Random): Boolean = r.nextDouble <= p1 + def (p1: Probability) toDouble: Double = p1 } val caughtTrain = Probability.unsafe(0.3) diff --git a/tests/pos/opaque-xm1.scala b/tests/pos/opaque-xm1.scala deleted file mode 100644 index c4f16a4cbed7..000000000000 --- a/tests/pos/opaque-xm1.scala +++ /dev/null @@ -1,34 +0,0 @@ -import Predef.{any2stringadd => _, _} -object opaquetypes { - type Logarithm = Double - - implicit object Logarithm { - - // These are the ways to lift to the logarithm type - def apply(d: Double): Logarithm = ??? - - def safe(d: Double): Option[Logarithm] = ??? - - // This is the first way to unlift the logarithm type - def exponent(l: Logarithm): Double = ??? - - // Extension methods define opaque types' public APIs - - // This is the second way to unlift the logarithm type - def toDouble(this x: Logarithm): Double = ??? - def +++(this x: Logarithm)(y: Logarithm) = ??? - def ***(this x: Logarithm)(y: Logarithm): Logarithm = ??? - } -} -object usesites { - import opaquetypes._ - val l = Logarithm(1.0) - val l2 = Logarithm(2.0) - val l3 = l *** l2 - val l4 = l +++ l2 // currently requires any2stringadd to be disabled because - // as a contextual implicit this takes precedence over the - // implicit scope implicit LogarithmOps. - // TODO: Remove any2stringadd - val d = l3.toDouble - val l5: Logarithm = (1.0).asInstanceOf[Logarithm] -} diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala index d7bfeec14714..5cd49719a30c 100644 --- a/tests/run/extension-methods.scala +++ b/tests/run/extension-methods.scala @@ -1,7 +1,7 @@ object Test extends App { implicit object O { - def em(this x: Int): Boolean = x > 0 + def (x: Int) em: Boolean = x > 0 } assert(1.em == O.em(1)) @@ -9,7 +9,7 @@ object Test extends App { case class Circle(x: Double, y: Double, radius: Double) implicit object CircleOps { - def circumference(this c: Circle): Double = c.radius * math.Pi * 2 + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } val circle = new Circle(1, 1, 2.0) @@ -17,7 +17,7 @@ object Test extends App { assert(circle.circumference == CircleOps.circumference(circle)) implicit object StringOps { - def longestStrings(this xs: Seq[String]): Seq[String] = { + def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } @@ -26,20 +26,20 @@ object Test extends App { assert(names.longestStrings == List("hello", "world")) implicit object SeqOps { - def second[T](this xs: Seq[T]) = xs.tail.head + def (xs: Seq[T]) second[T] = xs.tail.head } assert(names.longestStrings.second == "world") implicit object ListListOps { - def flattened[T](this xs: List[List[T]]) = xs.foldLeft[List[T]](Nil)(_ ++ _) + def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) } assert(List(names, List("!")).flattened == names :+ "!") assert(Nil.flattened == Nil) trait SemiGroup[T] { - def combine(this x: T)(y: T): T + def (x: T) combine (y: T): T } trait Monoid[T] extends SemiGroup[T] { def unit: T @@ -47,7 +47,7 @@ object Test extends App { // An instance declaration: implicit object StringMonoid extends Monoid[String] { - def combine(this x: String)(y: String): String = x.concat(y) + def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -58,20 +58,20 @@ object Test extends App { println(sum(names)) trait Ord[T] { - def compareTo(this x: T)(y: T): Int - def < (this x: T)(y: T) = x.compareTo(y) < 0 - def > (this x: T)(y: T) = x.compareTo(y) > 0 + def (x: T) compareTo (y: T): Int + def (x: T) < (y: T) = x.compareTo(y) < 0 + def (x: T) > (y: T) = x.compareTo(y) > 0 val minimum: T } implicit object IntOrd extends Ord[Int] { - def compareTo(this x: Int)(y: Int) = + def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } class ListOrd[T: Ord] extends Ord[List[T]] { - def compareTo(this xs: List[T])(ys: List[T]): Int = (xs, ys) match { + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 @@ -93,25 +93,25 @@ object Test extends App { println(max(List(1, 2, 3), List(2))) trait Functor[F[_]] { - def map[A, B](this x: F[A])(f: A => B): F[B] + def (x: F[A]) map [A, B](f: A => B): F[B] } trait Monad[F[_]] extends Functor[F] { - def flatMap[A, B](this x: F[A])(f: A => F[B]): F[B] - def map[A, B](this x: F[A])(f: A => B) = x.flatMap(f `andThen` pure) + 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 pure[A](x: A): F[A] } implicit object ListMonad extends Monad[List] { - def flatMap[A, B](this xs: List[A])(f: A => List[B]): List[B] = + def (xs: List[A]) flatMap [A, B](f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) } class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - def flatMap[A, B](this r: Ctx => A)(f: A => Ctx => B): Ctx => B = + def (r: Ctx => A) flatMap [A, B](f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x diff --git a/tests/run/opaque-immutable-array-xm.scala b/tests/run/opaque-immutable-array-xm.scala index dac7463d9bb7..ffd07c59047e 100644 --- a/tests/run/opaque-immutable-array-xm.scala +++ b/tests/run/opaque-immutable-array-xm.scala @@ -11,8 +11,8 @@ object Test extends App { // These should be inline but that does not work currently. Try again // once inliner is moved to ReifyQuotes - def length[A](this ia: IArray[A]): Int = (ia: Array[A]).length - def apply[A](this ia: IArray[A])(i: Int): A = (ia: Array[A])(i) + def (ia: IArray[A]) length[A]: Int = (ia: Array[A]).length + def (ia: IArray[A]) apply[A] (i: Int): A = (ia: Array[A])(i) // return a sorted copy of the array def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { From 5f86ca0d4a50a7c82373b9d9304964398449ef9c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Nov 2018 20:57:47 +0100 Subject: [PATCH 50/56] Handle right-associative extension operators --- .../src/dotty/tools/dotc/ast/Positioned.scala | 18 ++++++++++++++---- .../src/dotty/tools/dotc/parsing/Parsers.scala | 7 ++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 3a3bf5467010..55144d460145 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -208,10 +208,20 @@ abstract class Positioned extends Product { // Leave out tparams, they are copied with wrong positions from parent class check(tree.mods) check(tree.vparamss) - case tree: DefDef if tree.mods.is(Extension) && tree.vparamss.nonEmpty => - check(tree.vparamss.head) - check(tree.tparams) - check(tree.vparamss.tail) + 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(vparams1) + check(rest) + case vparams1 :: rest => + check(vparams1) + check(tree.tparams) + check(rest) + case _ => + check(tree.tparams) + } check(tree.tpt) check(tree.rhs) case _ => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index b7fb40fad394..122d61ea8490 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2259,7 +2259,12 @@ object Parsers { val mods1 = addFlag(mods, flags) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = leadingParamss ::: paramClauses(ofMethod = true) + val vparamss = paramClauses(ofMethod = true) match { + case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(name) => + rparams :: leadingParamss ::: rparamss + case rparamss => + leadingParamss ::: rparamss + } var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { in.nextToken() From f97c6ff094a94b6f8e2c0d68a2b866b72676659b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Nov 2018 15:19:58 +0100 Subject: [PATCH 51/56] Remove dead code in Parser --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 122d61ea8490..cc42e77d38d6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1973,7 +1973,6 @@ object Parsers { */ def paramClause(ofClass: Boolean = false, // owner is a class ofCaseClass: Boolean = false, // owner is a case class - ofMethod: Boolean = false, // owner is a method or constructor prefix: Boolean = false, // clause precedes name of an extension method firstClause: Boolean = false) // clause is the first in regular list of clauses : List[ValDef] = { @@ -2065,15 +2064,13 @@ object Parsers { * @return The parameter definitions */ def paramClauses(ofClass: Boolean = false, - ofCaseClass: Boolean = false, - ofMethod: Boolean = false): (List[List[ValDef]]) = { + ofCaseClass: Boolean = false): (List[List[ValDef]]) = { def recur(firstClause: Boolean): List[List[ValDef]] = { newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { val params = paramClause( ofClass = ofClass, ofCaseClass = ofCaseClass, - ofMethod = ofMethod, firstClause = firstClause) val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit) @@ -2253,13 +2250,13 @@ object Parsers { } else { val (leadingParamss: List[List[ValDef]], flags: FlagSet) = if (in.token == LPAREN) - (paramClause(ofMethod = true, prefix = true) :: Nil, Method | Extension) + (paramClause(prefix = true) :: Nil, Method | Extension) else (Nil, Method) val mods1 = addFlag(mods, flags) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(ofMethod = true) match { + val vparamss = paramClauses() match { case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(name) => rparams :: leadingParamss ::: rparamss case rparamss => From 7e2cd663b343b11df7cd7f9f18e142ce78498215 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Nov 2018 10:48:37 +0100 Subject: [PATCH 52/56] Avoid implicit objects that extend nothing An implicit object that only exists as an extension method container is a bit strange. Since extension methods can now also be made visible by imports it makes sense to de-emphasize extension methods in implicit values until we discuss typeclasses later in the section. --- docs/docs/reference/extension-methods.md | 45 +++++++++++------------- tests/run/extension-methods.scala | 28 +++++---------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md index a7d0c02d2a5e..e91d879b4f33 100644 --- a/docs/docs/reference/extension-methods.md +++ b/docs/docs/reference/extension-methods.md @@ -8,12 +8,10 @@ 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) -implicit object CircleOps { - def (c: Circle) circumference: Double = c.radius * math.Pi * 2 -} +def (c: Circle) circumference: Double = c.radius * math.Pi * 2 ``` -`CircleOps` adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: +Like regular methods, extension methods can be invoked with infix `.`: ```scala val circle = Circle(0, 0, 1) @@ -29,22 +27,31 @@ to the plain method, and can also be invoked as such: ```scala def circumference(c: Circle): Double = c.radius * math.Pi * 2 -assert(circle.circumference == CircleOps.circumference(circle)) +assert(circle.circumference == circumference(circle)) ``` ### Translation of Calls to Extension Methods -When is an extension method considered? There are two possibilities. The first (and recommended one) is by defining the extension method as a member of an implicit value. The method can then be used as an extension method wherever the implicit value is applicable. The second possibility is by making the extension method itself visible under a simple name, typically by importing it. As an example, consider an extension method `longestStrings` on `String`. We can either define it like this: +When is an extension method applicable? There are two possibilities. + + - An extension method is applicable if it is visible under a simple name, by being defined + or inherited or imported in a scope enclosing the application. + - An extension method is applicable if it is a member of an eligible implicit value at the point of the application. +As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`. ```scala -implicit object StringSeqOps1 { +trait StringSeqOps { def (xs: Seq[String]) longestStrings = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } ``` +We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this: +```scala +implicit object ops1 extends StringSeqOps +``` Then ```scala List("here", "is", "a", "list").longestStrings @@ -53,13 +60,8 @@ is legal everywhere `StringSeqOps1` is available as an implicit value. Alternati as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. ```scala -object StringOps2 { - def (xs: Seq[String]) longestStrings = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} -import StringSeqOps2.longestStrings +object ops2 extends StringSeqOps +import ops2.longestStrings List("here", "is", "a", "list").longestStrings ``` The precise rules for resolving a selection to an extension method are as follows. @@ -100,30 +102,23 @@ to the implementation of right binding operators as normal methods. ### Generic Extensions -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: +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: ```scala -implicit object ListOps { - def (xs: List[T]) second [T] = xs.tail.head -} +def (xs: List[T]) second [T] = xs.tail.head ``` or: ```scala -implicit object ListListOps { - def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) -} +def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) ``` or: ```scala -implicit object NumericOps { - def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) -} +def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) ``` 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. diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala index 5cd49719a30c..93df1c946483 100644 --- a/tests/run/extension-methods.scala +++ b/tests/run/extension-methods.scala @@ -1,39 +1,29 @@ object Test extends App { - implicit object O { - def (x: Int) em: Boolean = x > 0 - } + def (x: Int) em: Boolean = x > 0 - assert(1.em == O.em(1)) + assert(1.em == em(1)) case class Circle(x: Double, y: Double, radius: Double) - implicit object CircleOps { - def (c: Circle) circumference: Double = c.radius * math.Pi * 2 - } + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 val circle = new Circle(1, 1, 2.0) - assert(circle.circumference == CircleOps.circumference(circle)) + assert(circle.circumference == circumference(circle)) - implicit object StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } + def (xs: Seq[String]) longestStrings: Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) } val names = List("hi", "hello", "world") assert(names.longestStrings == List("hello", "world")) - implicit object SeqOps { - def (xs: Seq[T]) second[T] = xs.tail.head - } + def (xs: Seq[T]) second[T] = xs.tail.head assert(names.longestStrings.second == "world") - implicit object ListListOps { - def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) - } + def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) assert(List(names, List("!")).flattened == names :+ "!") assert(Nil.flattened == Nil) From cee2331f51b212d0306fec600aebde6235bb2a9a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Dec 2018 10:40:05 +0100 Subject: [PATCH 53/56] Implement reviewer suggestions --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Applications.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 874282366144..3ac4820b1385 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -524,7 +524,7 @@ object Types { } /** The member with given `name` and required and/or excluded flags */ - final def memberBasedOnFlags(name: Name, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { + final def memberBasedOnFlags(name: Name, required: FlagConjunction = EmptyFlagConjunction, excluded: FlagSet = EmptyFlags)(implicit ctx: Context): Denotation = { // We need a valid prefix for `asSeenFrom` val pre = this match { case tp: ClassInfo => tp.appliedRef diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 553a3f7d1728..9c7d5c36942d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1139,7 +1139,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * result matching `resultType`? */ def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { - val mbr = tp.member(name).suchThat(_.is(ExtensionMethod)) + val mbr = tp.memberBasedOnFlags(name, required = allOf(ExtensionMethod)) mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) } @@ -1645,7 +1645,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** The typed application * - * () or + * () or * []() * * where comes from `pt` if it is a PolyProto. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 228bd051ee09..c56a7f258312 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -147,7 +147,7 @@ object Implicits { tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms val hasExtensions = resType match { case SelectionProto(name, _, _, _) => - tpw.member(name).hasAltWith(_.symbol.is(ExtensionMethod)) + tpw.memberBasedOnFlags(name, required = allOf(ExtensionMethod)).exists case _ => false } val conversionKind = From 9d7093c5951f9d6dfcf99b8242780a28d41ae8b0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Dec 2018 11:14:20 +0100 Subject: [PATCH 54/56] Bring back xml-interpolation3 test --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 +- tests/{disabled => }/run/xml-interpolation-3/Test_2.scala | 0 tests/{disabled => }/run/xml-interpolation-3/XmlQuote_1.scala | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename tests/{disabled => }/run/xml-interpolation-3/Test_2.scala (100%) rename tests/{disabled => }/run/xml-interpolation-3/XmlQuote_1.scala (78%) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 969e8126a095..78de0d2fc60b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -47,7 +47,7 @@ object Inliner { /** Should call be inlined in this context? */ def isInlineable(tree: Tree)(implicit ctx: Context): Boolean = tree match { case Block(_, expr) => isInlineable(expr) - case _ => isInlineable(tree.symbol) + case _ => isInlineable(tree.symbol) && !tree.tpe.isInstanceOf[MethodOrPoly] } /** Try to inline a call to an inline method. Fail with error if the maximal diff --git a/tests/disabled/run/xml-interpolation-3/Test_2.scala b/tests/run/xml-interpolation-3/Test_2.scala similarity index 100% rename from tests/disabled/run/xml-interpolation-3/Test_2.scala rename to tests/run/xml-interpolation-3/Test_2.scala diff --git a/tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala b/tests/run/xml-interpolation-3/XmlQuote_1.scala similarity index 78% rename from tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala rename to tests/run/xml-interpolation-3/XmlQuote_1.scala index f99da7d01204..34402cd470b7 100644 --- a/tests/disabled/run/xml-interpolation-3/XmlQuote_1.scala +++ b/tests/run/xml-interpolation-3/XmlQuote_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ -import scala.tasty.Tasty +import scala.tasty.Reflection import scala.language.implicitConversions @@ -8,7 +8,7 @@ case class Xml(parts: String, args: List[Any]) object XmlQuote { implicit object SCOps { - inline def xml(this inline ctx: StringContext)(args: => Any*): Xml = + inline def (inline ctx: StringContext) xml (args: => Any*): Xml = ~XmlQuote.impl(ctx, '(args)) } From 3506192140d385bdf95eb237d866bf3f16f98a39 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Dec 2018 11:15:01 +0100 Subject: [PATCH 55/56] Reject ambiguities between implicit conversions and extension methods --- .../src/dotty/tools/dotc/typer/Implicits.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c56a7f258312..a2ae313476f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -939,16 +939,22 @@ trait Implicits { self: Typer => adapt(generated, pt, locked) else { val untpdGenerated = untpd.TypedSplice(generated) - if (cand.isConversion) + def tryConversion(implicit ctx: Context) = typed( untpd.Apply(untpdGenerated, untpd.TypedSplice(argument) :: Nil), pt, locked) - else { - assert(cand.isExtension) + if (cand.isExtension) { val SelectionProto(name: TermName, mbrType, _, _) = pt - extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) + val result = extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) + if (!ctx.reporter.hasErrors && cand.isConversion) { + val testCtx = ctx.fresh.setExploreTyperState() + tryConversion(testCtx) + if (testCtx.reporter.hasErrors) + ctx.error(em"ambiguous implicit: $generated is eligible both as an implicit conversion and as an extension method container") + } + result } - // TODO disambiguate if candidate can be an extension or a conversion + else tryConversion } lazy val shadowing = typedUnadapted(untpd.Ident(cand.implicitRef.implicitName) withPos pos.toSynthetic)( From 49bae02f0265878818483442800c2e240cbeba5e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Dec 2018 11:45:36 +0100 Subject: [PATCH 56/56] Reclassify test --- .../xml-interpolation-3/Test_2.scala | 0 .../xml-interpolation-3/XmlQuote_1.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{run => run-separate-compilation}/xml-interpolation-3/Test_2.scala (100%) rename tests/{run => run-separate-compilation}/xml-interpolation-3/XmlQuote_1.scala (100%) diff --git a/tests/run/xml-interpolation-3/Test_2.scala b/tests/run-separate-compilation/xml-interpolation-3/Test_2.scala similarity index 100% rename from tests/run/xml-interpolation-3/Test_2.scala rename to tests/run-separate-compilation/xml-interpolation-3/Test_2.scala diff --git a/tests/run/xml-interpolation-3/XmlQuote_1.scala b/tests/run-separate-compilation/xml-interpolation-3/XmlQuote_1.scala similarity index 100% rename from tests/run/xml-interpolation-3/XmlQuote_1.scala rename to tests/run-separate-compilation/xml-interpolation-3/XmlQuote_1.scala