From 68e2c9f30034323f5f293d60be689e9e7b6c2367 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 18:18:59 +0100 Subject: [PATCH 1/6] Show and test implicit conversions using SAM types. --- docs/docs/reference/contextual/conversions.md | 21 ++++++++----------- tests/pos/reference/instances.scala | 12 +++-------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 63da00aa2dc7..9f1d1a18c678 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -14,6 +14,10 @@ implied for Conversion[String, Token] { def apply(str: String): Token = new KeyWord(str) } ``` +Using an implied alias this can be expressed more concisely as: +```scala +implied for Conversion[String, Token] = new KeyWord(_) +``` An implicit conversion is applied automatically by the compiler in three situations: 1. If an expression `e` has type `T`, and `T` does not conform to the expression's expected type `S`. @@ -33,9 +37,8 @@ If such an instance `C` is found, the expression `e` is replaced by `C.apply(e)` primitive number types to subclasses of `java.lang.Number`. For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: ```scala -implied int2Integer for Conversion[Int, java.lang.Integer] { - def apply(x: Int) = new java.lang.Integer(x) -} +implied int2Integer for Conversion[Int, java.lang.Integer] = + new java.lang.Integer(x) ``` 2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g. @@ -56,15 +59,9 @@ object Completions { // // CompletionArg.fromStatusCode(statusCode) - implied fromString for Conversion[String, CompletionArg] { - def apply(s: String) = CompletionArg.Error(s) - } - implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] { - def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) - } - implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] { - def apply(code: Future[StatusCode]) = CompletionArg.Status(code) - } + implied fromString for Conversion[String, CompletionArg] = Error(_) + implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] = Response(_) + implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] = Status(_) } import CompletionArg._ diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index cd481cb0884c..cb9631fb8d90 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -274,15 +274,9 @@ object Completions { // // CompletionArg.from(statusCode) - implied fromString for Conversion[String, CompletionArg] { - def apply(s: String) = CompletionArg.Error(s) - } - implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] { - def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) - } - implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] { - def apply(code: Future[StatusCode]) = CompletionArg.Status(code) - } + implied fromString for Conversion[String, CompletionArg] = Error(_) + implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] = Response(_) + implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] = Status(_) } import CompletionArg._ From 44c1ec1e64fb1820cf095efd8238ebe8f3c280be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 18:21:51 +0100 Subject: [PATCH 2/6] Reformulate core/Decorators.scala with extension methods --- tests/pos-with-compiler/DecoratorsX.scala | 205 ++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 tests/pos-with-compiler/DecoratorsX.scala diff --git a/tests/pos-with-compiler/DecoratorsX.scala b/tests/pos-with-compiler/DecoratorsX.scala new file mode 100644 index 000000000000..47a77fa88810 --- /dev/null +++ b/tests/pos-with-compiler/DecoratorsX.scala @@ -0,0 +1,205 @@ +package dotty.tools.dotc +package core + +import annotation.tailrec +import Symbols._ +import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer +import util.Spans.Span, util.SourcePosition +import collection.mutable.ListBuffer +import dotty.tools.dotc.transform.MegaPhase +import ast.tpd._ +import scala.language.implicitConversions +import printing.Formatting._ + +/** This object provides useful conversions and extension methods for types defined elsewhere */ +object DecoratorsX { + + /** Turns Strings into PreNames, adding toType/TermName methods */ + class StringPreName(s: String) extends AnyVal with PreName { + def toTypeName: TypeName = typeName(s) + def toTermName: TermName = termName(s) + def toText(printer: Printer): Text = Str(s) + } + implied for Conversion[String, StringPreName] = new StringPreName(_) + + final val MaxFilterRecursions = 1000 + + implied { + def (s: String) splitWhere (f: Char => Boolean, doDropIndex: Boolean): Option[(String, String)] = { + def splitAt(idx: Int, doDropIndex: Boolean): Option[(String, String)] = + if (idx == -1) None + else Some((s.take(idx), s.drop(if (doDropIndex) idx + 1 else idx))) + + splitAt(s.indexWhere(f), doDropIndex) + } + + /** Implements a findSymbol method on iterators of Symbols that + * works like find but avoids Option, replacing None with NoSymbol. + */ + def (it: Iterator[Symbol]) findSymbol(p: Symbol => Boolean): Symbol = { + while (it.hasNext) { + val sym = it.next() + if (p(sym)) return sym + } + NoSymbol + } + + def (xs: List[T]) mapconserve [T, U] (f: T => U): List[U] = { + @tailrec + def loop(mapped: ListBuffer[U], unchanged: List[U], pending: List[T]): List[U] = + if (pending.isEmpty) { + if (mapped eq null) unchanged + else mapped.prependToList(unchanged) + } else { + val head0 = pending.head + val head1 = f(head0) + + if (head1.asInstanceOf[AnyRef] eq head0.asInstanceOf[AnyRef]) + loop(mapped, unchanged, pending.tail) + else { + val b = if (mapped eq null) new ListBuffer[U] else mapped + var xc = unchanged + while (xc ne pending) { + b += xc.head + xc = xc.tail + } + b += head1 + val tail0 = pending.tail + loop(b, tail0.asInstanceOf[List[U]], tail0) + } + } + loop(null, xs.asInstanceOf[List[U]], xs) + } + + /** Like `xs filter p` but returns list `xs` itself - instead of a copy - + * if `p` is true for all elements and `xs` is not longer + * than `MaxFilterRecursions`. + */ + def (xs: List[T]) filterConserve [T] (p: T => Boolean): List[T] = { + def loop(xs: List[T], nrec: Int): List[T] = xs match { + case Nil => xs + case x :: xs1 => + if (nrec < MaxFilterRecursions) { + val ys1 = loop(xs1, nrec + 1) + if (p(x)) + if (ys1 eq xs1) xs else x :: ys1 + else + ys1 + } else xs filter p + } + loop(xs, 0) + } + + /** Like `(xs, ys).zipped.map(f)`, but returns list `xs` itself + * - instead of a copy - if function `f` maps all elements of + * `xs` to themselves. Also, it is required that `ys` is at least + * as long as `xs`. + */ + def (xs: List[T]) zipWithConserve [T, U] (ys: List[U])(f: (T, U) => T): List[T] = + if (xs.isEmpty || ys.isEmpty) Nil + else { + val x1 = f(xs.head, ys.head) + val xs1 = xs.tail.zipWithConserve(ys.tail)(f) + if ((x1.asInstanceOf[AnyRef] eq xs.head.asInstanceOf[AnyRef]) && + (xs1 eq xs.tail)) xs + else x1 :: xs1 + } + + def (xs: List[T]) hasSameLengthAs [T, U] (ys: List[U]): Boolean = { + @tailrec def loop(xs: List[T], ys: List[U]): Boolean = + if (xs.isEmpty) ys.isEmpty + else ys.nonEmpty && loop(xs.tail, ys.tail) + loop(xs, ys) + } + + @tailrec + def (xs: List[T]) eqElements [T] (ys: List[AnyRef]): Boolean = xs match { + case x :: _ => + ys match { + case y :: _ => + x.asInstanceOf[AnyRef].eq(y) && + xs.tail.eqElements(ys.tail) + case _ => false + } + case nil => ys.isEmpty + } + + def (xs: List[T]) | [T] (ys: List[T]): List[T] = xs ::: (ys filterNot (xs contains _)) + + /** Intersection on lists seen as sets */ + def (xs: List[T]) & [T] (ys: List[T]): List[T] = xs filter (ys contains _) + + def (xss: List[List[T]]) nestedMap [T, U] (f: T => U): List[List[U]] = xss map (_ map f) + def (xss: List[List[T]]) nestedMapconserve [T, U](f: T => U): List[List[U]] = xss mapconserve (_ mapconserve f) + + def (text: Text) show given (ctx: Context): String = + text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value) + + /** Test whether a list of strings representing phases contains + * a given phase. See [[config.CompilerCommand#explainAdvanced]] for the + * exact meaning of "contains" here. + */ + def (names: List[String]) containsPhase (phase: Phase): Boolean = + names.nonEmpty && { + phase match { + case phase: MegaPhase => phase.miniPhases.exists(names.containsPhase(_)) + case _ => + names exists { name => + name == "all" || { + val strippedName = name.stripSuffix("+") + val logNextPhase = name != strippedName + phase.phaseName.startsWith(strippedName) || + (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) + } + } + } + } + + def (x: T) reporting [T] (op: T => String, printer: config.Printers.Printer = config.Printers.default): T = { + printer.println(op(x)) + x + } + + def (x: T) assertingErrorsReported [T] given Context: T = { + assert(the[Context].reporter.errorsReported) + x + } + + def (x: T) assertingErrorsReported [T] (msg: => String) given Context: T = { + assert(the[Context].reporter.errorsReported, msg) + x + } + + /** General purpose string formatting */ + def (sc: StringContext) i (args: Any*) given Context: String = + new StringFormatter(sc).assemble(args) + + /** Formatting for error messages: Like `i` but suppress follow-on + * error messages after the first one if some of their arguments are "non-sensical". + */ + def (sc: StringContext) em (args: Any*) given Context: String = + new ErrorMessageFormatter(sc).assemble(args) + + /** Formatting with added explanations: Like `em`, but add explanations to + * give more info about type variables and to disambiguate where needed. + */ + def (sc: StringContext) ex (args: Any*) given Context: String = + explained(sc.em(args: _*)) + + /** Formatter that adds syntax highlighting to all interpolated values */ + def (sc: StringContext) hl (args: Any*) given Context: String = + new SyntaxFormatter(sc).assemble(args).stripMargin + + def (arr: Array[T]) binarySearch [T <: AnyRef] (x: T): Int = + java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object]], x) + } + + /** Entrypoint for explanation string interpolator: + * + * ``` + * ex"disambiguate $tpe1 and $tpe2" + * ``` + */ + def explained(op: given Context => String) given Context: String = + printing.Formatting.explained(ctx => op given ctx) +} From c11a3fdb0728f407b0575201fa24715aa085955a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 18:23:32 +0100 Subject: [PATCH 3/6] Example from latest Cochis paper Shows how the behavior has changed in dotty. --- tests/run/cochis-example.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/run/cochis-example.scala diff --git a/tests/run/cochis-example.scala b/tests/run/cochis-example.scala new file mode 100644 index 000000000000..bb106eb2046f --- /dev/null +++ b/tests/run/cochis-example.scala @@ -0,0 +1,14 @@ + +import Predef.{$conforms => _} +trait A { + implied id[X] for (X => X) = x => x + def trans[X](x: X) given (f: X => X) = f(x) // (2) +} +object Test extends A with App{ + implied succ for (Int => Int) = x => x + 1 // (3) + def bad[X](x: X): X = trans[X](x) // (4) unstable definition ! + val v1 = bad [Int] (3) // (5) evaluates to 3 + assert(v1 == 3) + val v2 = trans [Int] (3) + assert(v2 == 4) +} \ No newline at end of file From 70dca88943325ad6cd079c63a72d4b59a52a16ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 18:24:57 +0100 Subject: [PATCH 4/6] Test case for #5966 --- tests/pos/i5966.scala | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/pos/i5966.scala diff --git a/tests/pos/i5966.scala b/tests/pos/i5966.scala new file mode 100644 index 000000000000..b8757204913c --- /dev/null +++ b/tests/pos/i5966.scala @@ -0,0 +1,8 @@ +object Test { + def foo = given (v: Int) => (x: Int) => v + x + implied myInt for Int = 4 + + foo.apply(1) + foo given 2 + foo(3) +} From 5625ca809f731e1d32325630562869f377a556a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Mar 2019 13:33:36 +0100 Subject: [PATCH 5/6] Fix typo --- docs/docs/reference/contextual/conversions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 9f1d1a18c678..720d2e56851a 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -38,7 +38,7 @@ primitive number types to subclasses of `java.lang.Number`. For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: ```scala implied int2Integer for Conversion[Int, java.lang.Integer] = - new java.lang.Integer(x) + new java.lang.Integer(_) ``` 2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g. From 4c5c3ffa2c739ee5f110779ce97755dd08f46961 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 19 Mar 2019 17:22:30 +0100 Subject: [PATCH 6/6] Update docs/docs/reference/contextual/conversions.md Co-Authored-By: odersky --- docs/docs/reference/contextual/conversions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md index 720d2e56851a..39efad0b1ccc 100644 --- a/docs/docs/reference/contextual/conversions.md +++ b/docs/docs/reference/contextual/conversions.md @@ -38,7 +38,7 @@ primitive number types to subclasses of `java.lang.Number`. For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows: ```scala implied int2Integer for Conversion[Int, java.lang.Integer] = - new java.lang.Integer(_) + java.lang.Integer.valueOf(_) ``` 2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g.