From 000dce1a448f5eb972549cd7a8efb9a1e9d32c35 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 11:01:24 +0200 Subject: [PATCH 01/25] Implicit By Name Parameters section --- .../reference/implicit-by-name-parameters.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/docs/reference/implicit-by-name-parameters.md diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md new file mode 100644 index 000000000000..f096d1d27211 --- /dev/null +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -0,0 +1,42 @@ +# Implicit By-Name Parameters + +Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. + +```scala +implicit def serializeOption[T](implicit ev: => Serializable[T]): Serializable[Option[T]] = + new Serializable[Option[T]] { + def write(x: Option[T], out: DataOutputStream) = t match { + case Some(x) => ev.write(x) + case None => + } + } + + val s = serializeOption[Option[Int]] + s.write(Some(33)) + s.write(None) +``` +As is the case for a normal by-name parameter, the argument for the implicit parameter `ev` +is evaluated on demand. In the example above, if the option value `x` is `None`, it is +not evaluated at all. + +The synthesized argument for an implicit parameter is backed by a lazy +val, which means that the parameter is evaluated at most once. The +lazy val is also available as a value for implicit search, which can +be useful to avoid an otherwise diverging expansion. + +The precise steps for constructing an implicit argument for a by-name parameter of type `=> T` are: + + 1. Create a new implicit value with a fresh name _lv_, which has the signature of the following definition: + + implicit lazy val lv: T + + The current implementation uses the prefix `$lazy_implicit$` followed by a unique integer for _lv_. + + 1. This lazy val is not immediately available as candidate for implicit search (making it immediately available would result in a looping implicit computation). But it becomes available in all nested contexts that look again for an implicit argument to a by-name parameter. + + 1. If this implicit search succeeds with expression `E`, and `E` contains references to the lazy implicit value _lv_, replace `E` by + + { implicit lazy val lv: T = E; lv } + + Otherwise, return `E` unchanged. + From 554bbb714f8267ab1dbee66a63a00d951802dbcf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 11:26:14 +0200 Subject: [PATCH 02/25] Add test code and fix doc accordingly. --- .../reference/implicit-by-name-parameters.md | 28 +++++++++++++------ .../implicict-by-name-parameters.scala | 22 +++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 tests/pos/reference/implicict-by-name-parameters.scala diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md index f096d1d27211..8eed2f794b89 100644 --- a/docs/docs/reference/implicit-by-name-parameters.md +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -3,28 +3,33 @@ Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. ```scala +trait Serializable[T] { + def write(x: T): Unit +} + +implicit def serializeInt: Serializable[Int] = ??? + implicit def serializeOption[T](implicit ev: => Serializable[T]): Serializable[Option[T]] = new Serializable[Option[T]] { - def write(x: Option[T], out: DataOutputStream) = t match { + def write(xo: Option[T]) = xo match { case Some(x) => ev.write(x) case None => } } - val s = serializeOption[Option[Int]] - s.write(Some(33)) - s.write(None) +val s = implicitly[Serializable[Option[Int]]] + +s.write(Some(33)) +s.write(None) ``` As is the case for a normal by-name parameter, the argument for the implicit parameter `ev` is evaluated on demand. In the example above, if the option value `x` is `None`, it is not evaluated at all. The synthesized argument for an implicit parameter is backed by a lazy -val, which means that the parameter is evaluated at most once. The -lazy val is also available as a value for implicit search, which can -be useful to avoid an otherwise diverging expansion. +val if this is necessary to prevent an otherwise diverging expansion. -The precise steps for constructing an implicit argument for a by-name parameter of type `=> T` are: +The precise steps for constructing an implicit argument for a by-name parameter of type `=> T` are as follows. 1. Create a new implicit value with a fresh name _lv_, which has the signature of the following definition: @@ -40,3 +45,10 @@ The precise steps for constructing an implicit argument for a by-name parameter Otherwise, return `E` unchanged. +In the example above, the definition of `s` would be expanded as follows. + + val s = implicitly[Test.Serializable[Option[Int]]]( + serializeOption[Int](serializeInt) + ) + +No lazy val was generated because the synthesized argument is not recursive. diff --git a/tests/pos/reference/implicict-by-name-parameters.scala b/tests/pos/reference/implicict-by-name-parameters.scala new file mode 100644 index 000000000000..03258fa4adc8 --- /dev/null +++ b/tests/pos/reference/implicict-by-name-parameters.scala @@ -0,0 +1,22 @@ +object Test { + +trait Serializable[T] { + def write(x: T): Unit +} + +implicit def serializeInt: Serializable[Int] = ??? + +implicit def serializeOption[T](implicit ev: => Serializable[T]): Serializable[Option[T]] = + new Serializable[Option[T]] { + def write(xo: Option[T]) = xo match { + case Some(x) => ev.write(x) + case None => + } + } + +val s = implicitly[Serializable[Option[Int]]] + +s.write(Some(33)) +s.write(None) + +} From 9954e815e9e7016c59112abc13af007aeaf423c4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 11:29:37 +0200 Subject: [PATCH 03/25] Fix indentation --- docs/docs/reference/implicit-by-name-parameters.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md index 8eed2f794b89..466bbb496e21 100644 --- a/docs/docs/reference/implicit-by-name-parameters.md +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -47,8 +47,7 @@ The precise steps for constructing an implicit argument for a by-name parameter In the example above, the definition of `s` would be expanded as follows. - val s = implicitly[Test.Serializable[Option[Int]]]( - serializeOption[Int](serializeInt) - ) + val s = implicitly[Test.Serializable[Option[Int]]]( + serializeOption[Int](serializeInt)) No lazy val was generated because the synthesized argument is not recursive. From 834674f0eb0273fa42f1b64f33f4a9430202e1ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 11:33:39 +0200 Subject: [PATCH 04/25] Add scala syntax highlighting --- .../docs/reference/implicit-by-name-parameters.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md index 466bbb496e21..5b24e419fda2 100644 --- a/docs/docs/reference/implicit-by-name-parameters.md +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -33,7 +33,9 @@ The precise steps for constructing an implicit argument for a by-name parameter 1. Create a new implicit value with a fresh name _lv_, which has the signature of the following definition: - implicit lazy val lv: T + ```scala + implicit lazy val lv: T + ``` The current implementation uses the prefix `$lazy_implicit$` followed by a unique integer for _lv_. @@ -41,13 +43,18 @@ The precise steps for constructing an implicit argument for a by-name parameter 1. If this implicit search succeeds with expression `E`, and `E` contains references to the lazy implicit value _lv_, replace `E` by - { implicit lazy val lv: T = E; lv } + + ```scala + { implicit lazy val lv: T = E; lv } + ``` Otherwise, return `E` unchanged. In the example above, the definition of `s` would be expanded as follows. - val s = implicitly[Test.Serializable[Option[Int]]]( - serializeOption[Int](serializeInt)) +```scala +val s = implicitly[Test.Serializable[Option[Int]]]( + serializeOption[Int](serializeInt)) +``` No lazy val was generated because the synthesized argument is not recursive. From 7f64c5787ff678155c147e04521676accf1313e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 14:17:40 +0200 Subject: [PATCH 05/25] Better type inference for new For an expression of the form `new { ... }`, infer the parent type if possible from the expected type. Fallback to `Object` if the expected type is undefined or is illegal for a class parent. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 +++++++-- .../docs/reference/implicit-by-name-parameters.md | 15 ++++++++------- tests/pos/news.scala | 10 ++++++++++ 3 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tests/pos/news.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 644bcd7cbe0a..763de5c53503 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -443,13 +443,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree.tpt match { case templ: untpd.Template => import untpd._ - templ.parents foreach { + var templ1 = templ + if (templ1.parents.isEmpty && + isFullyDefined(pt, ForceDegree.noBottom) && + pt.underlyingClassRef(refinementOK = false).exists) + templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) + templ1.parents foreach { case parent: RefTree => typedAheadImpl(parent, tree => inferTypeParams(typedType(tree), pt)) case _ => } val x = tpnme.ANON_CLASS - val clsDef = TypeDef(x, templ).withFlags(Final) + val clsDef = TypeDef(x, templ1).withFlags(Final) typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt) case _ => var tpt1 = typedType(tree.tpt) diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md index 5b24e419fda2..e49011b9cd29 100644 --- a/docs/docs/reference/implicit-by-name-parameters.md +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -3,21 +3,22 @@ Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. ```scala -trait Serializable[T] { +trait Codec[T] { def write(x: T): Unit } -implicit def serializeInt: Serializable[Int] = ??? +implicit def intCodec: Codec[Int] = ??? -implicit def serializeOption[T](implicit ev: => Serializable[T]): Serializable[Option[T]] = - new Serializable[Option[T]] { +implicit def optionCodec[T] + (implicit ev: => Codec[T]): Codec[Option[T]] = + new { def write(xo: Option[T]) = xo match { case Some(x) => ev.write(x) case None => } } -val s = implicitly[Serializable[Option[Int]]] +val s = implicitly[Codec[Option[Int]]] s.write(Some(33)) s.write(None) @@ -53,8 +54,8 @@ The precise steps for constructing an implicit argument for a by-name parameter In the example above, the definition of `s` would be expanded as follows. ```scala -val s = implicitly[Test.Serializable[Option[Int]]]( - serializeOption[Int](serializeInt)) +val s = implicitly[Test.Codec[Option[Int]]]( + optionCodec[Int](intCodec)) ``` No lazy val was generated because the synthesized argument is not recursive. diff --git a/tests/pos/news.scala b/tests/pos/news.scala new file mode 100644 index 000000000000..2825044546e1 --- /dev/null +++ b/tests/pos/news.scala @@ -0,0 +1,10 @@ +object Test { + + abstract class C[T] { def f: T } + + val x: C[Int] = new { def f = 2 } + + val y = new { val name = "Bob" } + + +} From abe87a8dd07d85f9887f12ae351a09388796607b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 15:06:41 +0200 Subject: [PATCH 06/25] Don't infer final classes as parents for anonymous new Needed to make run/t6154.scala compile. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 763de5c53503..7df0f0dc4437 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -444,9 +444,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case templ: untpd.Template => import untpd._ var templ1 = templ + def isEligible(tp: Type) = tp.exists && !tp.typeSymbol.is(Final) if (templ1.parents.isEmpty && isFullyDefined(pt, ForceDegree.noBottom) && - pt.underlyingClassRef(refinementOK = false).exists) + isEligible(pt.underlyingClassRef(refinementOK = false))) templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) templ1.parents foreach { case parent: RefTree => From a36b052a9e86d691c1a9c988d39d97bc5ac03f74 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 16:26:55 +0200 Subject: [PATCH 07/25] Section on enumerations --- docs/docs/reference/enums.md | 122 ++++++++++++++++++++++++++++++++ tests/pos/reference/enums.scala | 64 +++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 docs/docs/reference/enums.md create mode 100644 tests/pos/reference/enums.scala diff --git a/docs/docs/reference/enums.md b/docs/docs/reference/enums.md new file mode 100644 index 000000000000..407410e81fcd --- /dev/null +++ b/docs/docs/reference/enums.md @@ -0,0 +1,122 @@ +# Enums + +The most basic use of enums is as enumerations of simple values: + +```scala +enum Color { + case Red, Green, Blue +} +``` + +This defines a new class, `Color`, with three values, `Color.Red`, +`Color.Green`, `Color.Blue`. The color values are members of `Color`s +companion object. The `Color` definition above is equivalent to the +following more explicit definition of an _enum class_ and a companion +object: + +```scala +enum class Color +object Color { + case Red + case Green + case Blue +} +``` + +## Parameterized enums + +Enum classes can be parameterized. + +```scala +enum Color(val rgb: Int) { + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +} +``` + +As the example shows, you can define the parameter value by using an +explicit extends clause. + +## Methods defined for enums + +The values of an enum correspond to unique integers. The integer +associated with an enum value is returned by its `enumTag` method: + +```scala +scala> val red = Color.Red +val red: Color = Red +scala> red.enumTag +val res0: Int = 0 +``` + +The companion object of an enum class also defines three utility methods. +The `enumValue` and `enumValueNamed` methods obtain an enum value +by its tag or its name. The `enumValues` method returns all enum values +defined in an enumeration in an `Iterable`. + +```scala +scala> Color.enumValue(1) +val res1: Color = Green +scala> Color.enumValueNamed("Blue") +val res2: Color = Blue +scala> Color.enumValues +val res3: collection.Iterable[Color] = MapLike(Red, Green, Blue) +``` + +## User-defined members of enums + +It is possible to add your own definitions to an enum class or its +companion object. To make clear what goes where you need to use the +longer syntax which defines an enum class alongside its companion +object explicitly. In the following example, we define some methods in +class `Planet` and a `main` method in its companion object. + +```scala +enum class Planet(mass: Double, radius: Double) { + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity +} + +object Planet { + case MERCURY extends Planet(3.303e+23, 2.4397e6) + case VENUS extends Planet(4.869e+24, 6.0518e6) + case EARTH extends Planet(5.976e+24, 6.37814e6) + case MARS extends Planet(6.421e+23, 3.3972e6) + case JUPITER extends Planet(1.9e+27, 7.1492e7) + case SATURN extends Planet(5.688e+26, 6.0268e7) + case URANUS extends Planet(8.686e+25, 2.5559e7) + case NEPTUNE extends Planet(1.024e+26, 2.4746e7) + + def main(args: Array[String]) = { + val earthWeight = args(0).toDouble + val mass = earthWeight/EARTH.surfaceGravity + for (p <- enumValues) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + } +} +``` + +## Implementation + +Enum values with `extends` clauses get expanded to anonymus class instances. +For instance, the `VENUS` value above would be defined like this: + +```scala +val VENUS: Planet = + new Planet(4.869E24, 6051800.0) { + def enumTag: Int = 1 + override def toString: String = "VENUS" + // internal code to register value + } +``` + +Enum values without `extends` clauses all share a single implementation +that can be instantiated using a private method that takes a tag and a name as arguments. +For instance, the first +definition of value `Color.Red` above would expand to: + +```scala +val Red: Color = $new(0, "Red") +``` diff --git a/tests/pos/reference/enums.scala b/tests/pos/reference/enums.scala new file mode 100644 index 000000000000..8cad672d9a34 --- /dev/null +++ b/tests/pos/reference/enums.scala @@ -0,0 +1,64 @@ +object t1 { + +enum Color { + case Red, Green, Blue +} + +} + +object t2 { + +enum class Color +object Color { + case Red + case Green + case Blue +} + +} + +object t3 { + +enum class Color(val rgb: Int) +object Color { + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +} + +} + +object t4 { + +enum Color(val rgb: Int) { + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) +} +} + + +enum class Planet(mass: Double, radius: Double) { + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity +} + +object Planet { + case MERCURY extends Planet(3.303e+23, 2.4397e6) + case VENUS extends Planet(4.869e+24, 6.0518e6) + case EARTH extends Planet(5.976e+24, 6.37814e6) + case MARS extends Planet(6.421e+23, 3.3972e6) + case JUPITER extends Planet(1.9e+27, 7.1492e7) + case SATURN extends Planet(5.688e+26, 6.0268e7) + case URANUS extends Planet(8.686e+25, 2.5559e7) + case NEPTUNE extends Planet(1.024e+26, 2.4746e7) + + def main(args: Array[String]) = { + val earthWeight = args(0).toDouble + val mass = earthWeight/EARTH.surfaceGravity + for (p <- enumValues) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + } +} + From a15570a10149720ba7c6118d4aa73123d61a55f1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 17:13:57 +0200 Subject: [PATCH 08/25] More info on enum implementation --- docs/docs/reference/enums.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/enums.md b/docs/docs/reference/enums.md index 407410e81fcd..e46120855fa5 100644 --- a/docs/docs/reference/enums.md +++ b/docs/docs/reference/enums.md @@ -8,7 +8,7 @@ enum Color { } ``` -This defines a new class, `Color`, with three values, `Color.Red`, +This defines a new `sealed` class, `Color`, with three values, `Color.Red`, `Color.Green`, `Color.Blue`. The color values are members of `Color`s companion object. The `Color` definition above is equivalent to the following more explicit definition of an _enum class_ and a companion @@ -100,6 +100,20 @@ object Planet { ## Implementation +Enum classes are represented as `sealed` classes that extend the `scala.Enum` trait. +This trait defines a single method, `enumTag`: + +```scala +package scala + +/** A base trait of all enum classes */ +trait Enum { + + /** A number uniquely identifying a case of an enum */ + def enumTag: Int +} +``` + Enum values with `extends` clauses get expanded to anonymus class instances. For instance, the `VENUS` value above would be defined like this: From 1b81d0026314349dbf804432975ae154d5bb1db2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 17:47:24 +0200 Subject: [PATCH 09/25] Fix problem with parameterized enums The logic for generating the result type of apply was wrong if value parameters were passed to the enum class. Test case in objXfun.scala. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 9 ++++++++- tests/pos/objXfun.scala | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/pos/objXfun.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index cb137d46476a..b7de824d2952 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -431,11 +431,18 @@ object desugar { // For all other classes, the parent is AnyRef. val companions = if (isCaseClass) { + def extractType(t: Tree): Tree = t match { + case Apply(t1, _) => extractType(t1) + case TypeApply(t1, ts) => AppliedTypeTree(extractType(t1), ts) + case Select(t1, nme.CONSTRUCTOR) => extractType(t1) + case New(t1) => t1 + case t1 => t1 + } // The return type of the `apply` method val applyResultTpt = if (isEnumCase) if (parents.isEmpty) enumClassTypeRef - else parents.reduceLeft(AndTypeTree) + else parents.map(extractType).reduceLeft(AndTypeTree) else TypeTree() val parent = diff --git a/tests/pos/objXfun.scala b/tests/pos/objXfun.scala new file mode 100644 index 000000000000..c05a08d9ba41 --- /dev/null +++ b/tests/pos/objXfun.scala @@ -0,0 +1,8 @@ +object Foo extends (Int => Int) { // OK + def apply(x: Int) = x +} + +enum class E(x: Int) // used to generate Int => new E(x) as the parent of object E --> crash +object E { + case C(x: Int) extends E(x) +} From aa292d7cfa6d001f81f40c940ba08e70adc83fd7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 17:55:25 +0200 Subject: [PATCH 10/25] Add test file --- .../implicit-byname-parameters.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/pos/reference/implicit-byname-parameters.scala diff --git a/tests/pos/reference/implicit-byname-parameters.scala b/tests/pos/reference/implicit-byname-parameters.scala new file mode 100644 index 000000000000..abc78649413e --- /dev/null +++ b/tests/pos/reference/implicit-byname-parameters.scala @@ -0,0 +1,22 @@ +object Test { + +trait Codec[T] { + def write(x: T): Unit +} + +implicit def intCodec: Codec[Int] = ??? + +implicit def optionCodec[T] + (implicit ev: => Codec[T]): Codec[Option[T]] = + new { + def write(xo: Option[T]) = xo match { + case Some(x) => ev.write(x) + case None => + } + } + +val s = implicitly[Codec[Option[Int]]] + +s.write(Some(33)) +s.write(None) +} From 5244d768be8ba660db8ae97ef032b7d77ef6df41 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 19:20:22 +0200 Subject: [PATCH 11/25] Section on ADTs --- docs/docs/reference/adts.md | 61 ++++++++++++++++++++++++++++++++++ tests/pos/reference/adts.scala | 26 +++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 docs/docs/reference/adts.md create mode 100644 tests/pos/reference/adts.scala diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md new file mode 100644 index 000000000000..72d3ae79f9a0 --- /dev/null +++ b/docs/docs/reference/adts.md @@ -0,0 +1,61 @@ +# Algebraic Data Types + +The `enum` concept is general enough to also support algebraic data +types (ADTs) and their generalized version (GADTs). Here's an example +how an `Option` type can be represented as an ADT: + +```scala +enum Option[+T] { + case Some[+T](x: T) + case None +} +``` + +This example introduces `Option` enum class with a covariant type +parameter `T`, together with two cases, `Some` and `None`. `Some` is +parameterized with a type parameter `T` and a value parameter `x`. It +is a shorthand for writing a case class that extends `Option`. Since +`None` is not parameterized it is treated as a normal enum value. + +The `extends` clauses that were omitted in the example above can also +be given explicitly: + +```scala +enum Option[+T] { + case Some[T](x: T) extends Option[T] + case None extends Option[Nothing] +} +``` + +Note that the parent type of `None` is inferred as +`List[Nothing]`. Generally, all covariant type parameters of the enum +class are minimized in a compiler-generated extends clause whereas all +contravariant type parameters are maximized. If `Option` was non-variant, +you'd need to give the extends clause of `None` explicitly. + +As for normal enum values, the cases of an `enum` are all defined in +the `enum`s companion object. So it's `Option.Some` and `Option.None` +unless the definitions are "pulled out" with an import: + +```scala +scala> Option.Some("hello") +val res1: t2.Option[String] = Some(hello) +scala> Option.None +val res2: t2.Option[Nothing] = None +``` + +Note that the type of the expressions above is always `Option`. That +is, the implementation case classes are not visible in the result +types of their `apply` methods. This is a subtle difference with +respect to normal case classes. The classes making up the cases do +exist, and can be unvealed by constructing them directly with a `new`. + +```scala +val res3: t2.Option.Some[Int] = Some(2) +scala> scala> new Option.Some(2) +``` + + + + + diff --git a/tests/pos/reference/adts.scala b/tests/pos/reference/adts.scala new file mode 100644 index 000000000000..3c8ecfba1786 --- /dev/null +++ b/tests/pos/reference/adts.scala @@ -0,0 +1,26 @@ +object t1 { + +enum Option[+T] { + case Some[T](x: T) + case None +} + +} + +object t2 { + +enum Option[+T] { + case Some[T](x: T) extends Option[T] + case None extends Option[Nothing] +} + + +} + +enum Color(val rgb: Int) { + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +} + From def3bcf2976dfb73ca34b9ff52a36e3dd643433e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 19:28:35 +0200 Subject: [PATCH 12/25] More ADT material --- docs/docs/reference/adts.md | 38 ++++++++++++++++++++++++++++++++++ tests/pos/reference/adts.scala | 18 ++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md index 72d3ae79f9a0..e1334ba4c8d6 100644 --- a/docs/docs/reference/adts.md +++ b/docs/docs/reference/adts.md @@ -55,6 +55,44 @@ val res3: t2.Option.Some[Int] = Some(2) scala> scala> new Option.Some(2) ``` +As all other enums, ADTs can have methods on both the enum class and +its companion object. For instance, here is `Option` again, with an +`isDefined` method and an `Option(...)` constructor. + +```scala +enum class Option[+T] { + def isDefined: Boolean +} +object Option { + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) + + case Some[+T](x: T) { + def isDefined = true + } + case None { + def isDefined = false + } +} +``` + +Enumerations and ADTs have been presented as two different +concepts. But since they share the same syntactic construct, they can +be seen simply as two ends of a spectrum and it is perfectly possible +to conctruct hybrids. For instance, the code below gives an +implementation of `Color` either with three enum values or with a +parameterized case that takes an RGB value. + +```scala +enum Color(val rgb: Int) { + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + case Mix(mix: Int) extends Color(mix) +} +``` + + diff --git a/tests/pos/reference/adts.scala b/tests/pos/reference/adts.scala index 3c8ecfba1786..e8aec58f4de7 100644 --- a/tests/pos/reference/adts.scala +++ b/tests/pos/reference/adts.scala @@ -24,3 +24,21 @@ enum Color(val rgb: Int) { case Mix(mix: Int) extends Color(mix) } +object t3 { + +enum class Option[+T] { + def isDefined: Boolean +} +object Option { + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) + + case Some[+T](x: T) { + def isDefined = true + } + case None { + def isDefined = false + } +} + +} From eda79d9ef539e763b4ee4bde7b22743948e5721a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 19:54:02 +0200 Subject: [PATCH 13/25] Fix reference tests Need to account for the fact that they all compile together --- tests/pos/reference/adts.scala | 1 + tests/pos/reference/enums.scala | 2 ++ .../implicict-by-name-parameters.scala | 22 ------------------- .../implicit-byname-parameters.scala | 2 ++ 4 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 tests/pos/reference/implicict-by-name-parameters.scala diff --git a/tests/pos/reference/adts.scala b/tests/pos/reference/adts.scala index e8aec58f4de7..6fc670ada71b 100644 --- a/tests/pos/reference/adts.scala +++ b/tests/pos/reference/adts.scala @@ -1,3 +1,4 @@ +package adts object t1 { enum Option[+T] { diff --git a/tests/pos/reference/enums.scala b/tests/pos/reference/enums.scala index 8cad672d9a34..1db1b055d9b0 100644 --- a/tests/pos/reference/enums.scala +++ b/tests/pos/reference/enums.scala @@ -1,3 +1,5 @@ +package enums + object t1 { enum Color { diff --git a/tests/pos/reference/implicict-by-name-parameters.scala b/tests/pos/reference/implicict-by-name-parameters.scala deleted file mode 100644 index 03258fa4adc8..000000000000 --- a/tests/pos/reference/implicict-by-name-parameters.scala +++ /dev/null @@ -1,22 +0,0 @@ -object Test { - -trait Serializable[T] { - def write(x: T): Unit -} - -implicit def serializeInt: Serializable[Int] = ??? - -implicit def serializeOption[T](implicit ev: => Serializable[T]): Serializable[Option[T]] = - new Serializable[Option[T]] { - def write(xo: Option[T]) = xo match { - case Some(x) => ev.write(x) - case None => - } - } - -val s = implicitly[Serializable[Option[Int]]] - -s.write(Some(33)) -s.write(None) - -} diff --git a/tests/pos/reference/implicit-byname-parameters.scala b/tests/pos/reference/implicit-byname-parameters.scala index abc78649413e..8b82d2b22c9e 100644 --- a/tests/pos/reference/implicit-byname-parameters.scala +++ b/tests/pos/reference/implicit-byname-parameters.scala @@ -1,3 +1,5 @@ +package implicitByName + object Test { trait Codec[T] { From 15418c55a68e5a56a3edfc94a415022a2235d46d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 20:03:51 +0200 Subject: [PATCH 14/25] Add section on enum translation --- docs/docs/reference/desugarEnums.md | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/docs/reference/desugarEnums.md diff --git a/docs/docs/reference/desugarEnums.md b/docs/docs/reference/desugarEnums.md new file mode 100644 index 000000000000..4d842fc8e914 --- /dev/null +++ b/docs/docs/reference/desugarEnums.md @@ -0,0 +1,143 @@ +# Translation of Enums and ADTs + +The compiler expands enum classes and cases to code that only uses +Scala's other language features. As such, enums in Scala are +convenient _syntactic sugar_, but they are not essential to understand +Scala's core. + +We now explain the expansion of enums is explained in detail. First, +some terminology and notational conventions: + + - We use `E` as a name of an enum class, and `C` as a name of an enum case that appears in the companion object of `E`. + - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance `` represents either the body of a case between `{...}` or nothing at all. + + - Enum cases fall into three categories: + + - _Class cases_ are those cases that are parameterized, either with a type parameter section `[...]` or with one or more (possibly empty) parameter sections `(...)`. + - _Simple cases_ are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only. + - _Value cases_ are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body. + + Simple cases and value cases are called collectively _singleton cases_. + +The desugaring rules imply that class cases are mapped to case classes, and singleton cases are mapped to `val` definitions. + +There are eight desugaring rules. Rules (1) and (2) desugar enums and +enum classes. Rules (3) and (4) define extends clauses for cases that +are missing them. Rules (5 - 7) define how such expanded cases map +into case classes, case objects or vals. Finally, rule (8) expands +comma separated simple cases into a sequence of cases. + +1. An `enum` definition + + enum E ... { } + + expands to an enum class and a companion object + + enum class E ... + object E { } + +2. An enum class definition + + enum class E ... extends ... + + expands to a `sealed` `abstract` class that extends the `scala.Enum` trait: + + sealed abstract class E ... extends with scala.Enum ... + +3. If `E` is an enum class without type parameters, then a case in its companion object without an extends clause + + case C + + expands to + + case C extends E + +4. If `E` is an enum class with type parameters `Ts`, then a case in its companion object without an extends clause + + case C + + expands according to two alternatives, depending whether `C` has type parameters or not. If `C` has type parameters, they must have the same names and appear in the same order as the enum type parameters `Ts` (variances may be different, however). In this case + + case C [Ts] + + expands to + + case C[Ts] extends E[Ts] + + For the case where `C` does not have type parameters, assume `E`'s type parameters are + + V1 T1 > L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) + + where each of the variances `Vi` is either `'+'` or `'-'`. Then the case expands to + + case C extends E[B1, ..., Bn] + + where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. It is an error if `Bi` refers to some other type parameter `Tj (j = 0,..,n-1)`. It is also an error if `E` has type parameters that are non-variant. + +5. A class case + + case C ... + + expands analogous to a case class: + + final case class C ... + + However, unlike for a regular case class, the return type of the associated `apply` method is a fully parameterized type instance of the enum class `E` itself instead of `C`. Also the enum case defines an `enumTag` method of the form + + def enumTag = n + + where `n` is the ordinal number of the case in the companion object, starting from 0. + +6. A value case + + case C extends + + expands to a value definition + + val C = new { ; def enumTag = n; $values.register(this) } + + where `n` is the ordinal number of the case in the companion object, starting from 0. + The statement `$values.register(this)` registers the value as one of the `enumValues` of the + enumeration (see below). `$values` is a compiler-defined private value in + the companion object. + +7. A simple case + + case C + + of an enum class `E` that does not take type parameters expands to + + val C = $new(n, "C") + + Here, `$new` is a private method that creates an instance of of `E` (see below). + +8. A simple case consisting of a comma-separated list of enum names + + case C_1, ..., C_n + + expands to + + case C_1; ...; case C_n + + Any modifiers or annotations on the original case extend to all expanded cases. + +## Translation of Enumerations + +Non-generic enum classes `E` that define one or more singleton cases +are called _enumerations_. Companion objects of enumerations define +the following additional members. + + - A method `enumValue` of type `scala.collection.immutable.Map[Int, E]`. `enumValue(n)` returns the singleton case value with ordinal number `n`. + - A method `enumValueNamed` of type `scala.collection.immutable.Map[String, E]`. `enumValueNamed(s)` returns the singleton case value whose `toString` representation is `s`. + - A method `enumValues` which returns an `Iterable[E]` of all singleton case values in `E`, in the order of their definitions. + +Companion objects that contain at least one simple case define in addition: + + - A private method `$new` which defines a new simple case value with given ordinal number and name. This method can be thought as being defined as follows. + + def $new(tag: Int, name: String): ET = new E { + def enumTag = tag + def toString = name + $values.register(this) // register enum value so that `valueOf` and `values` can return it. + } + From a3f0b227eda4a5a70f7bc16d84d31ca9892e5dd9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 May 2017 20:52:30 +0200 Subject: [PATCH 15/25] Add syntax section for adts and enums --- docs/docs/reference/adts.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md index e1334ba4c8d6..8e3f0c350fcb 100644 --- a/docs/docs/reference/adts.md +++ b/docs/docs/reference/adts.md @@ -92,6 +92,22 @@ enum Color(val rgb: Int) { } ``` +## Syntax of Enums + + 1. Enum definitions and enum classes are defined as follows: + + TmplDef ::= `enum' `class’ ClassDef + | `enum' EnumDef + EnumDef ::= id ClassConstr [`extends' [ConstrApps]] + [nl] `{’ EnumCaseStat {semi EnumCaseStat} `}’ + +2. Cases of enums are defined as follows: + + EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase + EnumCase ::= `case' (EnumClassDef | ObjectDef | ids) + EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] + ClsParamClauses TemplateOpt + TemplateStat ::= ... | EnumCaseStat From 7aba3c596949f5081e6d7a44cbb07d7072c90fc0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 09:24:05 +0200 Subject: [PATCH 16/25] Address reviewer comments --- docs/docs/reference/adts.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md index 8e3f0c350fcb..5f5fb05a9fd4 100644 --- a/docs/docs/reference/adts.md +++ b/docs/docs/reference/adts.md @@ -22,8 +22,8 @@ be given explicitly: ```scala enum Option[+T] { - case Some[T](x: T) extends Option[T] - case None extends Option[Nothing] + case Some[+T](x: T) extends Option[T] + case None extends Option[Nothing] } ``` @@ -94,6 +94,9 @@ enum Color(val rgb: Int) { ## Syntax of Enums +Changes to the syntax fall in two categories: enum classes and cases inside enums. +The changes are specified below as deltas with respect to the Scala syntax given [here](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md) + 1. Enum definitions and enum classes are defined as follows: TmplDef ::= `enum' `class’ ClassDef From bf34e8507ead9bb0f2bfe337ff0f06e0d7b30e99 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 10:06:57 +0200 Subject: [PATCH 17/25] Section on trait parameters --- docs/docs/reference/trait-parameters.md | 55 ++++++++++++++++++++++ tests/pos/reference/trait-parameters.scala | 19 ++++++++ 2 files changed, 74 insertions(+) create mode 100644 docs/docs/reference/trait-parameters.md create mode 100644 tests/pos/reference/trait-parameters.scala diff --git a/docs/docs/reference/trait-parameters.md b/docs/docs/reference/trait-parameters.md new file mode 100644 index 000000000000..717f4396db2e --- /dev/null +++ b/docs/docs/reference/trait-parameters.md @@ -0,0 +1,55 @@ +# Trait Parameters + +Dotty allows traits to have parameters, just like classes have parameters. + +```scala +trait Greeting(val name: String) { + def msg = s"How are you, $name" +} + +class C extends Greeting("Bob") { + println(msg) +} +``` + +Arguments to a trait are evaluated immediately before the trait is initialized. + +One potential issue with trait parameters is how to prevent +ambiguities. For instance, you might try to extend `Greeting` twice, +with different parameters. + +```scala +/*!*/ class D extends C with Greeting("Bill") // error: parameters passed twice +``` + +Should this print "Bob" or "Bill"? In fact this program is illegal, +because it violates one of the following rules for trait parameters: + + 1. If a class `C` extends a parameterized trait `T`, and its superclass does not, `C` _must_ pass arguments to `T`. + + 2. If a class `C` extends a parameterized trait `T`, and its superclass does as well, `C` _may not` pass arguments to `T`. + + 3. Traits may never pass arguments to parent traits. + +Here's a trait extending the parameterized trait `Greeting`. + +```scala +trait FormalGreeting extends Greeting { + override def msg = s"How do you do, $name" +} +``` +As is required, no arguments are passed to `Greeting`. However, this poses an issue +when defining a class that extends `FormalGreeting`: + +```scala +/*!*/ class E extends FormalGreeting // error: missing arguments for `Greeting`. +``` + +The correct way to write `E` is to extend both `Greeting` and +`FormalGreeting` (in either order): + +```scala +class E extends Greeting("Bob") with FormalGreeting +``` + + diff --git a/tests/pos/reference/trait-parameters.scala b/tests/pos/reference/trait-parameters.scala new file mode 100644 index 000000000000..8b043a4279d6 --- /dev/null +++ b/tests/pos/reference/trait-parameters.scala @@ -0,0 +1,19 @@ +trait Greeting(val name: String) { + def msg = s"How are you, $name" +} + +trait FormalGreeting extends Greeting { + override def msg = s"How do you do, $name" +} + +class C extends Greeting("Bob") { + println(msg) +} + +class D extends C with Greeting + +class E extends Greeting("Bob") with FormalGreeting + +// class D2 extends C with Greeting("Bill") // error + + From 76bc34bad63a4ef6aa4415417880a0c8831f238c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 10:09:22 +0200 Subject: [PATCH 18/25] Fix typo --- docs/docs/reference/trait-parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/trait-parameters.md b/docs/docs/reference/trait-parameters.md index 717f4396db2e..af72752eed7b 100644 --- a/docs/docs/reference/trait-parameters.md +++ b/docs/docs/reference/trait-parameters.md @@ -27,7 +27,7 @@ because it violates one of the following rules for trait parameters: 1. If a class `C` extends a parameterized trait `T`, and its superclass does not, `C` _must_ pass arguments to `T`. - 2. If a class `C` extends a parameterized trait `T`, and its superclass does as well, `C` _may not` pass arguments to `T`. + 2. If a class `C` extends a parameterized trait `T`, and its superclass does as well, `C` _may not_ pass arguments to `T`. 3. Traits may never pass arguments to parent traits. From c6b458b71114fff6ef83274317ba73adad738dcc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 11:09:19 +0200 Subject: [PATCH 19/25] Add reference to sidebar --- docs/docs/reference/enums.md | 4 ++-- docs/sidebar.yml | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/enums.md b/docs/docs/reference/enums.md index e46120855fa5..2d60d32f13cd 100644 --- a/docs/docs/reference/enums.md +++ b/docs/docs/reference/enums.md @@ -1,6 +1,6 @@ -# Enums +# Enumerations -The most basic use of enums is as enumerations of simple values: +An enumeration is used to define a type consisting of a set of named values. ```scala enum Color { diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 90e950134636..dbea873c902a 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -1,6 +1,18 @@ sidebar: - title: Blog url: blog/index.html + - title: Reference + subsection: + - title: Trait Parameters + url: docs/reference/trait-parameters.html + - title: Enumerations + url: docs/reference/enums.html + - title: Algebraic Data Types + url: docs/reference/adts.html + - title: Enum Translation + url: docs/reference/desugarEnums.html + - title: By-Name Implicits + url: docs/reference/implicit-by-name-parameters.html - title: Usage subsection: - title: cbt-projects From 77db6bde374f55b31180f5c8aca2a4c098a5be3d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 13:04:50 +0200 Subject: [PATCH 20/25] Add titles --- docs/docs/reference/adts.md | 6 +++++- docs/docs/reference/desugarEnums.md | 5 ++++- docs/docs/reference/enums.md | 5 ++++- docs/docs/reference/implicit-by-name-parameters.md | 5 ++++- docs/docs/reference/trait-parameters.md | 5 ++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md index 5f5fb05a9fd4..84abbcd04446 100644 --- a/docs/docs/reference/adts.md +++ b/docs/docs/reference/adts.md @@ -1,4 +1,8 @@ -# Algebraic Data Types +--- +layout: doc-page +title: "Algebraic Data Types" +--- + The `enum` concept is general enough to also support algebraic data types (ADTs) and their generalized version (GADTs). Here's an example diff --git a/docs/docs/reference/desugarEnums.md b/docs/docs/reference/desugarEnums.md index 4d842fc8e914..8dc062b6ca57 100644 --- a/docs/docs/reference/desugarEnums.md +++ b/docs/docs/reference/desugarEnums.md @@ -1,4 +1,7 @@ -# Translation of Enums and ADTs +--- +layout: doc-page +title: "Translation of Enums and ADTs" +--- The compiler expands enum classes and cases to code that only uses Scala's other language features. As such, enums in Scala are diff --git a/docs/docs/reference/enums.md b/docs/docs/reference/enums.md index 2d60d32f13cd..795c0d6210a1 100644 --- a/docs/docs/reference/enums.md +++ b/docs/docs/reference/enums.md @@ -1,4 +1,7 @@ -# Enumerations +--- +layout: doc-page +title: "Enumerations" +--- An enumeration is used to define a type consisting of a set of named values. diff --git a/docs/docs/reference/implicit-by-name-parameters.md b/docs/docs/reference/implicit-by-name-parameters.md index e49011b9cd29..af341fb982ee 100644 --- a/docs/docs/reference/implicit-by-name-parameters.md +++ b/docs/docs/reference/implicit-by-name-parameters.md @@ -1,4 +1,7 @@ -# Implicit By-Name Parameters +--- +layout: doc-page +title: "Implicit By-Name Parameters" +--- Call-by-name implicit parameters can be used to avoid a divergent implicit expansion. diff --git a/docs/docs/reference/trait-parameters.md b/docs/docs/reference/trait-parameters.md index af72752eed7b..62415e57f800 100644 --- a/docs/docs/reference/trait-parameters.md +++ b/docs/docs/reference/trait-parameters.md @@ -1,4 +1,7 @@ -# Trait Parameters +--- +layout: doc-page +title: "Trait Parameters" +--- Dotty allows traits to have parameters, just like classes have parameters. From c6c27f6ad7e8b20614dcb734a88ef65a68b667b0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 14:25:48 +0200 Subject: [PATCH 21/25] Add section in intersection types --- docs/docs/reference/intersection-types.md | 66 ++++++++++++++++++++ tests/pos/reference/intersection-types.scala | 35 +++++++++++ 2 files changed, 101 insertions(+) create mode 100644 docs/docs/reference/intersection-types.md create mode 100644 tests/pos/reference/intersection-types.scala diff --git a/docs/docs/reference/intersection-types.md b/docs/docs/reference/intersection-types.md new file mode 100644 index 000000000000..3cb4c39397c9 --- /dev/null +++ b/docs/docs/reference/intersection-types.md @@ -0,0 +1,66 @@ +--- +layout: doc-page +title: "Intersection Types" +--- + +Used on types, the `&` operator creates an intersection type. + +```scala +trait Resetable { + def reset(): this.type +} +trait Growable[T] { + def add(x: T): this.type +} +def f(x: Resetable & Growable[String]) = { + x.reset() + x.add("first") +} +``` + +The value `x` is required to be _both_ a `Resetable` and a +`Growable[String]`. Intersection types `A & B` replace compound types +`A with B` in Scala 2 (for the moment, `A with B` is still allowed, but +it will be deprecated and removed in the future). + +Unlike `with` types, `&` is _commutative_: `A & B` is the same type as +`B & A`. + +The members of an intersection type `A & B` are all the members of `A` +and all the members of `B`. For instance `Resetable & Growable[String]` +has member methods `reset` and `add`. + +If a member appears in both `A` and `B`, its type in `A & B` is the +intersection of its type in `A` and its type in `B`. For instance, assume the definitions: + +```scala +trait A { + def children: List[A] +} +trait B { + def children: List[B] +} +val x: A & B = new C +val ys: List[A & B] = x.children +``` + +The type of `children` in `A & B` is the intersection of `children`'s +type in `A` and its type in `B`, which is `List[A] & List[B]`. This +can be further simplified to `List[A & B]` because `List` is +covariant. + +One might wonder how the compiler could come up with a definition for +`children` of type `List[A & B]` since all its is given are `children` +definitions of type `List[A]` and `List[B]`. The answer is it does not +need to. `A & B` is just a type that represents a set of requirements for +values of the type. At the point where a value is _constructed_, one +must make sure that all inherited members are correctly defined. +So if one defines a class `C` that inherits `A` and `B`, one needs +to give at that point a definition of a `children` method with the required type. + +```scala +class C extends A with B { + def children: List[A & B] = ??? +} +``` + diff --git a/tests/pos/reference/intersection-types.scala b/tests/pos/reference/intersection-types.scala new file mode 100644 index 000000000000..1d6a14ffa3b1 --- /dev/null +++ b/tests/pos/reference/intersection-types.scala @@ -0,0 +1,35 @@ +package intersectionTypes + +object t1 { + +trait Resetable { + def reset(): this.type +} +trait Growable[T] { + def add(x: T): this.type +} +def f(x: Resetable & Growable[String]) = { + x.reset() + x.add("first") +} + +} + +object t2 { + +trait A { + def children: List[A] +} +trait B { + def children: List[B] +} +val x: A & B = new C +val ys: List[A & B] = x.children + + +class C extends A with B { + def children: List[A & B] = ??? +} + + +} From 1a901ecbcf9ef609090413b49ba5453acd36df76 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 May 2017 21:25:09 +0200 Subject: [PATCH 22/25] Add sectionon union types --- docs/docs/reference/intersection-types.md | 1 - docs/docs/reference/union-types.md | 57 +++++++++++++++++++++++ docs/sidebar.yml | 4 ++ tests/pos/intersection.scala | 6 +++ tests/pos/reference/union-types.scala | 37 +++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 docs/docs/reference/union-types.md create mode 100644 tests/pos/reference/union-types.scala diff --git a/docs/docs/reference/intersection-types.md b/docs/docs/reference/intersection-types.md index 3cb4c39397c9..256fefa9742c 100644 --- a/docs/docs/reference/intersection-types.md +++ b/docs/docs/reference/intersection-types.md @@ -63,4 +63,3 @@ class C extends A with B { def children: List[A & B] = ??? } ``` - diff --git a/docs/docs/reference/union-types.md b/docs/docs/reference/union-types.md new file mode 100644 index 000000000000..5566b1d39bc8 --- /dev/null +++ b/docs/docs/reference/union-types.md @@ -0,0 +1,57 @@ +--- +layout: doc-page +title: "Union Types" +--- + +Used on types, the `|` operator creates a union type. + +```scala +case class UserName(name: String) { + def lookup(admin: Admin): UserData +} +case class Password(hash: Hash) { + def lookup(admin: Admin): UserData +} + +def help(id: UserName | PassWord) = { + val user = id match { + case UserName(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + } + // ... +} +``` + +Union types are dual of intersection types. Values of type `A | B` are +all values of type `A` and all values of type `B`. `|` is _commutative_: +`A | B` is the same type as `B | A`. + +The compiler will assign an expression a union type only if such a +type is explicitly given. +This can be seen in the folling REPL +transcript: + +```scala +scala> val password = Password(123) +val password: Password = Password(123) +scala> val name = UserName("Eve") +val name: UserName = UserName(Eve) +scala> if (true) name else password +val res2: Object & Product = UserName(Eve) +scala> val either: Password | UserName = if (true) name else password +val either: Password | UserName = UserName(Eve) +``` + +The type of `res2` is `Object | Product`, which is a supertype of +`UserName` and `Product, but not the least supertype `Password | +UserName`. If we want the least supertype, we have to give it +explicitely, as is done for the type of `Either`. More precisely, the +typechecker will _widen_ a union type to a non-union type when +inferring the type of ` `val` or `var`, or the result type of a `def`, +or the argument to pass for a type parameter. The widened type of `A +| B` is usually the intersection of all class or trait types that are +supertypes of both `A` and `B`; it does not include any refinements. +Union types are in that sense analogous to singleton types `x.type` +which are also widened to their underlying type unless explicitly +specified. + diff --git a/docs/sidebar.yml b/docs/sidebar.yml index dbea873c902a..bf7e48153190 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -3,6 +3,10 @@ sidebar: url: blog/index.html - title: Reference subsection: + - title: Intersection types + url: docs/reference/intersection-types.html + - title: Union types + url: docs/reference/union-types.html - title: Trait Parameters url: docs/reference/trait-parameters.html - title: Enumerations diff --git a/tests/pos/intersection.scala b/tests/pos/intersection.scala index d2e445dbafb0..faf9af401d58 100644 --- a/tests/pos/intersection.scala +++ b/tests/pos/intersection.scala @@ -15,4 +15,10 @@ object intersection { type needsA = A => Nothing type needsB = B => Nothing + + + class C[-T] + def f: C[A] & C[B] = ??? + def g: C[A | B] = f + def h: C[A] & C[B] = g } diff --git a/tests/pos/reference/union-types.scala b/tests/pos/reference/union-types.scala new file mode 100644 index 000000000000..7366305ad1a1 --- /dev/null +++ b/tests/pos/reference/union-types.scala @@ -0,0 +1,37 @@ +package unionTypes + +object t1 { + +type Hash = Int + +case class UserName(name: String) +case class Password(hash: Hash) + +def help(id: UserName | Password) = { + val user = id match { + case UserName(name) => lookupName(name) + case Password(hash) => lookupPassword(hash) + } +} + +def lookupName(name: String) = ??? +def lookupPassword(hash: Hash) = ??? + +} + +object t2 { + import t1._ + +trait Admin +trait UserData + +trait L { def lookup(admin: Admin): Object } + +case class UserName(name: String) extends L { + def lookup(admin: Admin): UserData = ??? +} +case class Password(hash: Hash) extends L { + def lookup(admin: Admin): UserData = ??? +} + +} From 20e681bab81957c49151d32525c0f2e44f92f607 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 May 2017 17:30:44 +0200 Subject: [PATCH 23/25] Fix typo --- docs/docs/reference/adts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/adts.md b/docs/docs/reference/adts.md index 84abbcd04446..9c4f83a6db93 100644 --- a/docs/docs/reference/adts.md +++ b/docs/docs/reference/adts.md @@ -32,7 +32,7 @@ enum Option[+T] { ``` Note that the parent type of `None` is inferred as -`List[Nothing]`. Generally, all covariant type parameters of the enum +`Option[Nothing]`. Generally, all covariant type parameters of the enum class are minimized in a compiler-generated extends clause whereas all contravariant type parameters are maximized. If `Option` was non-variant, you'd need to give the extends clause of `None` explicitly. From 8d750a19a2fc735ae2a1ca91a491aa1a5cdb94ac Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 26 May 2017 20:52:46 +0200 Subject: [PATCH 24/25] Add reference to index --- docs/docs/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/index.md b/docs/docs/index.md index d4661a497503..b2c1f1b5dfc0 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -14,6 +14,14 @@ the code, setup Dotty with your favorite IDE and more! Contents ------- +* Dotty Language Reference + - [Intersection Types](reference/intersection-types.md) + - [Union Types](reference/union-types.md) + - [Trait Parameters](reference/trait-parameters.md) + - [Enumerations](reference/enums.md) + - [Algebraic Data Types](reference/adts.md) + - [Enum Translation](reference/desugarEnums.md) + - [By-Name Implicits](reference/implicit-by-name-parameters.md) * Usage - [Migrating from Scala 2](usage/migrating.md): migration information - [Dotty projects with cbt](usage/cbt-projects.md): using cbt From 8013ea4b916f6c10971268cae6c3c410d8b87f13 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 26 May 2017 20:52:56 +0200 Subject: [PATCH 25/25] Fixup markup in union-types --- docs/docs/reference/desugarEnums.md | 107 ++++++++++++++++------------ docs/docs/reference/union-types.md | 4 +- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/docs/docs/reference/desugarEnums.md b/docs/docs/reference/desugarEnums.md index 8dc062b6ca57..c07d08d37d56 100644 --- a/docs/docs/reference/desugarEnums.md +++ b/docs/docs/reference/desugarEnums.md @@ -41,7 +41,7 @@ comma separated simple cases into a sequence of cases. 2. An enum class definition - enum class E ... extends ... + enum class E ... extends ... expands to a `sealed` `abstract` class that extends the `scala.Enum` trait: @@ -49,80 +49,94 @@ comma separated simple cases into a sequence of cases. 3. If `E` is an enum class without type parameters, then a case in its companion object without an extends clause - case C + case C expands to - case C extends E + case C extends E -4. If `E` is an enum class with type parameters `Ts`, then a case in its companion object without an extends clause +4. If `E` is an enum class with type parameters `Ts`, then a case in its + companion object without an extends clause - case C + case C - expands according to two alternatives, depending whether `C` has type parameters or not. If `C` has type parameters, they must have the same names and appear in the same order as the enum type parameters `Ts` (variances may be different, however). In this case + expands according to two alternatives, depending whether `C` has type + parameters or not. If `C` has type parameters, they must have the same + names and appear in the same order as the enum type parameters `Ts` + (variances may be different, however). In this case - case C [Ts] + case C [Ts] - expands to + expands to - case C[Ts] extends E[Ts] + case C[Ts] extends E[Ts] - For the case where `C` does not have type parameters, assume `E`'s type parameters are + For the case where `C` does not have type parameters, assume `E`'s type + parameters are - V1 T1 > L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) + V1 T1 > L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) - where each of the variances `Vi` is either `'+'` or `'-'`. Then the case expands to + where each of the variances `Vi` is either `'+'` or `'-'`. Then the case + expands to - case C extends E[B1, ..., Bn] + case C extends E[B1, ..., Bn] - where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. It is an error if `Bi` refers to some other type parameter `Tj (j = 0,..,n-1)`. It is also an error if `E` has type parameters that are non-variant. + where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. It is an error if + `Bi` refers to some other type parameter `Tj (j = 0,..,n-1)`. It is also + an error if `E` has type parameters that are non-variant. 5. A class case - case C ... + case C ... - expands analogous to a case class: + expands analogous to a case class: - final case class C ... + final case class C ... - However, unlike for a regular case class, the return type of the associated `apply` method is a fully parameterized type instance of the enum class `E` itself instead of `C`. Also the enum case defines an `enumTag` method of the form + However, unlike for a regular case class, the return type of the associated + `apply` method is a fully parameterized type instance of the enum class `E` + itself instead of `C`. Also the enum case defines an `enumTag` method of + the form - def enumTag = n + def enumTag = n - where `n` is the ordinal number of the case in the companion object, starting from 0. + where `n` is the ordinal number of the case in the companion object, + starting from 0. 6. A value case - case C extends + case C extends - expands to a value definition + expands to a value definition - val C = new { ; def enumTag = n; $values.register(this) } + val C = new { ; def enumTag = n; $values.register(this) } - where `n` is the ordinal number of the case in the companion object, starting from 0. - The statement `$values.register(this)` registers the value as one of the `enumValues` of the - enumeration (see below). `$values` is a compiler-defined private value in - the companion object. + where `n` is the ordinal number of the case in the companion object, + starting from 0. The statement `$values.register(this)` registers the value + as one of the `enumValues` of the enumeration (see below). `$values` is a + compiler-defined private value in the companion object. 7. A simple case - case C + case C - of an enum class `E` that does not take type parameters expands to + of an enum class `E` that does not take type parameters expands to - val C = $new(n, "C") + val C = $new(n, "C") - Here, `$new` is a private method that creates an instance of of `E` (see below). + Here, `$new` is a private method that creates an instance of of `E` (see + below). 8. A simple case consisting of a comma-separated list of enum names case C_1, ..., C_n - expands to + expands to - case C_1; ...; case C_n + case C_1; ...; case C_n - Any modifiers or annotations on the original case extend to all expanded cases. + Any modifiers or annotations on the original case extend to all expanded + cases. ## Translation of Enumerations @@ -130,17 +144,22 @@ Non-generic enum classes `E` that define one or more singleton cases are called _enumerations_. Companion objects of enumerations define the following additional members. - - A method `enumValue` of type `scala.collection.immutable.Map[Int, E]`. `enumValue(n)` returns the singleton case value with ordinal number `n`. - - A method `enumValueNamed` of type `scala.collection.immutable.Map[String, E]`. `enumValueNamed(s)` returns the singleton case value whose `toString` representation is `s`. - - A method `enumValues` which returns an `Iterable[E]` of all singleton case values in `E`, in the order of their definitions. + - A method `enumValue` of type `scala.collection.immutable.Map[Int, E]`. + `enumValue(n)` returns the singleton case value with ordinal number `n`. + - A method `enumValueNamed` of type `scala.collection.immutable.Map[String, E]`. + `enumValueNamed(s)` returns the singleton case value whose `toString` + representation is `s`. + - A method `enumValues` which returns an `Iterable[E]` of all singleton case + values in `E`, in the order of their definitions. Companion objects that contain at least one simple case define in addition: - - A private method `$new` which defines a new simple case value with given ordinal number and name. This method can be thought as being defined as follows. - - def $new(tag: Int, name: String): ET = new E { - def enumTag = tag - def toString = name - $values.register(this) // register enum value so that `valueOf` and `values` can return it. - } + - A private method `$new` which defines a new simple case value with given + ordinal number and name. This method can be thought as being defined as + follows. + def $new(tag: Int, name: String): ET = new E { + def enumTag = tag + def toString = name + $values.register(this) // register enum value so that `valueOf` and `values` can return it. + } diff --git a/docs/docs/reference/union-types.md b/docs/docs/reference/union-types.md index 5566b1d39bc8..14890d514331 100644 --- a/docs/docs/reference/union-types.md +++ b/docs/docs/reference/union-types.md @@ -43,11 +43,11 @@ val either: Password | UserName = UserName(Eve) ``` The type of `res2` is `Object | Product`, which is a supertype of -`UserName` and `Product, but not the least supertype `Password | +`UserName` and `Product`, but not the least supertype `Password | UserName`. If we want the least supertype, we have to give it explicitely, as is done for the type of `Either`. More precisely, the typechecker will _widen_ a union type to a non-union type when -inferring the type of ` `val` or `var`, or the result type of a `def`, +inferring the type of `val` or `var`, or the result type of a `def`, or the argument to pass for a type parameter. The widened type of `A | B` is usually the intersection of all class or trait types that are supertypes of both `A` and `B`; it does not include any refinements.