Skip to content

Commit 4d4521d

Browse files
authored
Merge pull request #6098 from dotty-staging/add-docs-tests
Improve implicit conversion docs and some more tests
2 parents db8a1eb + 4c5c3ff commit 4d4521d

File tree

5 files changed

+239
-21
lines changed

5 files changed

+239
-21
lines changed

docs/docs/reference/contextual/conversions.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ implied for Conversion[String, Token] {
1414
def apply(str: String): Token = new KeyWord(str)
1515
}
1616
```
17+
Using an implied alias this can be expressed more concisely as:
18+
```scala
19+
implied for Conversion[String, Token] = new KeyWord(_)
20+
```
1721
An implicit conversion is applied automatically by the compiler in three situations:
1822

1923
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)`
3337
primitive number types to subclasses of `java.lang.Number`. For instance, the
3438
conversion from `Int` to `java.lang.Integer` can be defined as follows:
3539
```scala
36-
implied int2Integer for Conversion[Int, java.lang.Integer] {
37-
def apply(x: Int) = new java.lang.Integer(x)
38-
}
40+
implied int2Integer for Conversion[Int, java.lang.Integer] =
41+
java.lang.Integer.valueOf(_)
3942
```
4043

4144
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 {
5659
//
5760
// CompletionArg.fromStatusCode(statusCode)
5861

59-
implied fromString for Conversion[String, CompletionArg] {
60-
def apply(s: String) = CompletionArg.Error(s)
61-
}
62-
implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] {
63-
def apply(f: Future[HttpResponse]) = CompletionArg.Response(f)
64-
}
65-
implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] {
66-
def apply(code: Future[StatusCode]) = CompletionArg.Status(code)
67-
}
62+
implied fromString for Conversion[String, CompletionArg] = Error(_)
63+
implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] = Response(_)
64+
implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] = Status(_)
6865
}
6966
import CompletionArg._
7067

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package dotty.tools.dotc
2+
package core
3+
4+
import annotation.tailrec
5+
import Symbols._
6+
import Contexts._, Names._, Phases._, printing.Texts._, printing.Printer
7+
import util.Spans.Span, util.SourcePosition
8+
import collection.mutable.ListBuffer
9+
import dotty.tools.dotc.transform.MegaPhase
10+
import ast.tpd._
11+
import scala.language.implicitConversions
12+
import printing.Formatting._
13+
14+
/** This object provides useful conversions and extension methods for types defined elsewhere */
15+
object DecoratorsX {
16+
17+
/** Turns Strings into PreNames, adding toType/TermName methods */
18+
class StringPreName(s: String) extends AnyVal with PreName {
19+
def toTypeName: TypeName = typeName(s)
20+
def toTermName: TermName = termName(s)
21+
def toText(printer: Printer): Text = Str(s)
22+
}
23+
implied for Conversion[String, StringPreName] = new StringPreName(_)
24+
25+
final val MaxFilterRecursions = 1000
26+
27+
implied {
28+
def (s: String) splitWhere (f: Char => Boolean, doDropIndex: Boolean): Option[(String, String)] = {
29+
def splitAt(idx: Int, doDropIndex: Boolean): Option[(String, String)] =
30+
if (idx == -1) None
31+
else Some((s.take(idx), s.drop(if (doDropIndex) idx + 1 else idx)))
32+
33+
splitAt(s.indexWhere(f), doDropIndex)
34+
}
35+
36+
/** Implements a findSymbol method on iterators of Symbols that
37+
* works like find but avoids Option, replacing None with NoSymbol.
38+
*/
39+
def (it: Iterator[Symbol]) findSymbol(p: Symbol => Boolean): Symbol = {
40+
while (it.hasNext) {
41+
val sym = it.next()
42+
if (p(sym)) return sym
43+
}
44+
NoSymbol
45+
}
46+
47+
def (xs: List[T]) mapconserve [T, U] (f: T => U): List[U] = {
48+
@tailrec
49+
def loop(mapped: ListBuffer[U], unchanged: List[U], pending: List[T]): List[U] =
50+
if (pending.isEmpty) {
51+
if (mapped eq null) unchanged
52+
else mapped.prependToList(unchanged)
53+
} else {
54+
val head0 = pending.head
55+
val head1 = f(head0)
56+
57+
if (head1.asInstanceOf[AnyRef] eq head0.asInstanceOf[AnyRef])
58+
loop(mapped, unchanged, pending.tail)
59+
else {
60+
val b = if (mapped eq null) new ListBuffer[U] else mapped
61+
var xc = unchanged
62+
while (xc ne pending) {
63+
b += xc.head
64+
xc = xc.tail
65+
}
66+
b += head1
67+
val tail0 = pending.tail
68+
loop(b, tail0.asInstanceOf[List[U]], tail0)
69+
}
70+
}
71+
loop(null, xs.asInstanceOf[List[U]], xs)
72+
}
73+
74+
/** Like `xs filter p` but returns list `xs` itself - instead of a copy -
75+
* if `p` is true for all elements and `xs` is not longer
76+
* than `MaxFilterRecursions`.
77+
*/
78+
def (xs: List[T]) filterConserve [T] (p: T => Boolean): List[T] = {
79+
def loop(xs: List[T], nrec: Int): List[T] = xs match {
80+
case Nil => xs
81+
case x :: xs1 =>
82+
if (nrec < MaxFilterRecursions) {
83+
val ys1 = loop(xs1, nrec + 1)
84+
if (p(x))
85+
if (ys1 eq xs1) xs else x :: ys1
86+
else
87+
ys1
88+
} else xs filter p
89+
}
90+
loop(xs, 0)
91+
}
92+
93+
/** Like `(xs, ys).zipped.map(f)`, but returns list `xs` itself
94+
* - instead of a copy - if function `f` maps all elements of
95+
* `xs` to themselves. Also, it is required that `ys` is at least
96+
* as long as `xs`.
97+
*/
98+
def (xs: List[T]) zipWithConserve [T, U] (ys: List[U])(f: (T, U) => T): List[T] =
99+
if (xs.isEmpty || ys.isEmpty) Nil
100+
else {
101+
val x1 = f(xs.head, ys.head)
102+
val xs1 = xs.tail.zipWithConserve(ys.tail)(f)
103+
if ((x1.asInstanceOf[AnyRef] eq xs.head.asInstanceOf[AnyRef]) &&
104+
(xs1 eq xs.tail)) xs
105+
else x1 :: xs1
106+
}
107+
108+
def (xs: List[T]) hasSameLengthAs [T, U] (ys: List[U]): Boolean = {
109+
@tailrec def loop(xs: List[T], ys: List[U]): Boolean =
110+
if (xs.isEmpty) ys.isEmpty
111+
else ys.nonEmpty && loop(xs.tail, ys.tail)
112+
loop(xs, ys)
113+
}
114+
115+
@tailrec
116+
def (xs: List[T]) eqElements [T] (ys: List[AnyRef]): Boolean = xs match {
117+
case x :: _ =>
118+
ys match {
119+
case y :: _ =>
120+
x.asInstanceOf[AnyRef].eq(y) &&
121+
xs.tail.eqElements(ys.tail)
122+
case _ => false
123+
}
124+
case nil => ys.isEmpty
125+
}
126+
127+
def (xs: List[T]) | [T] (ys: List[T]): List[T] = xs ::: (ys filterNot (xs contains _))
128+
129+
/** Intersection on lists seen as sets */
130+
def (xs: List[T]) & [T] (ys: List[T]): List[T] = xs filter (ys contains _)
131+
132+
def (xss: List[List[T]]) nestedMap [T, U] (f: T => U): List[List[U]] = xss map (_ map f)
133+
def (xss: List[List[T]]) nestedMapconserve [T, U](f: T => U): List[List[U]] = xss mapconserve (_ mapconserve f)
134+
135+
def (text: Text) show given (ctx: Context): String =
136+
text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value)
137+
138+
/** Test whether a list of strings representing phases contains
139+
* a given phase. See [[config.CompilerCommand#explainAdvanced]] for the
140+
* exact meaning of "contains" here.
141+
*/
142+
def (names: List[String]) containsPhase (phase: Phase): Boolean =
143+
names.nonEmpty && {
144+
phase match {
145+
case phase: MegaPhase => phase.miniPhases.exists(names.containsPhase(_))
146+
case _ =>
147+
names exists { name =>
148+
name == "all" || {
149+
val strippedName = name.stripSuffix("+")
150+
val logNextPhase = name != strippedName
151+
phase.phaseName.startsWith(strippedName) ||
152+
(logNextPhase && phase.prev.phaseName.startsWith(strippedName))
153+
}
154+
}
155+
}
156+
}
157+
158+
def (x: T) reporting [T] (op: T => String, printer: config.Printers.Printer = config.Printers.default): T = {
159+
printer.println(op(x))
160+
x
161+
}
162+
163+
def (x: T) assertingErrorsReported [T] given Context: T = {
164+
assert(the[Context].reporter.errorsReported)
165+
x
166+
}
167+
168+
def (x: T) assertingErrorsReported [T] (msg: => String) given Context: T = {
169+
assert(the[Context].reporter.errorsReported, msg)
170+
x
171+
}
172+
173+
/** General purpose string formatting */
174+
def (sc: StringContext) i (args: Any*) given Context: String =
175+
new StringFormatter(sc).assemble(args)
176+
177+
/** Formatting for error messages: Like `i` but suppress follow-on
178+
* error messages after the first one if some of their arguments are "non-sensical".
179+
*/
180+
def (sc: StringContext) em (args: Any*) given Context: String =
181+
new ErrorMessageFormatter(sc).assemble(args)
182+
183+
/** Formatting with added explanations: Like `em`, but add explanations to
184+
* give more info about type variables and to disambiguate where needed.
185+
*/
186+
def (sc: StringContext) ex (args: Any*) given Context: String =
187+
explained(sc.em(args: _*))
188+
189+
/** Formatter that adds syntax highlighting to all interpolated values */
190+
def (sc: StringContext) hl (args: Any*) given Context: String =
191+
new SyntaxFormatter(sc).assemble(args).stripMargin
192+
193+
def (arr: Array[T]) binarySearch [T <: AnyRef] (x: T): Int =
194+
java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object]], x)
195+
}
196+
197+
/** Entrypoint for explanation string interpolator:
198+
*
199+
* ```
200+
* ex"disambiguate $tpe1 and $tpe2"
201+
* ```
202+
*/
203+
def explained(op: given Context => String) given Context: String =
204+
printing.Formatting.explained(ctx => op given ctx)
205+
}

tests/pos/i5966.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
def foo = given (v: Int) => (x: Int) => v + x
3+
implied myInt for Int = 4
4+
5+
foo.apply(1)
6+
foo given 2
7+
foo(3)
8+
}

tests/pos/reference/instances.scala

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,9 @@ object Completions {
274274
//
275275
// CompletionArg.from(statusCode)
276276

277-
implied fromString for Conversion[String, CompletionArg] {
278-
def apply(s: String) = CompletionArg.Error(s)
279-
}
280-
implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] {
281-
def apply(f: Future[HttpResponse]) = CompletionArg.Response(f)
282-
}
283-
implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] {
284-
def apply(code: Future[StatusCode]) = CompletionArg.Status(code)
285-
}
277+
implied fromString for Conversion[String, CompletionArg] = Error(_)
278+
implied fromFuture for Conversion[Future[HttpResponse], CompletionArg] = Response(_)
279+
implied fromStatusCode for Conversion[Future[StatusCode], CompletionArg] = Status(_)
286280
}
287281
import CompletionArg._
288282

tests/run/cochis-example.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import Predef.{$conforms => _}
3+
trait A {
4+
implied id[X] for (X => X) = x => x
5+
def trans[X](x: X) given (f: X => X) = f(x) // (2)
6+
}
7+
object Test extends A with App{
8+
implied succ for (Int => Int) = x => x + 1 // (3)
9+
def bad[X](x: X): X = trans[X](x) // (4) unstable definition !
10+
val v1 = bad [Int] (3) // (5) evaluates to 3
11+
assert(v1 == 3)
12+
val v2 = trans [Int] (3)
13+
assert(v2 == 4)
14+
}

0 commit comments

Comments
 (0)