Skip to content

Commit 0ac9326

Browse files
committed
Add SIP 23 ValueOf
Added an implementation of SIP 23 ValueOf and copied over the corresponding Scala 2 tests.
1 parent c444f1a commit 0ac9326

File tree

6 files changed

+153
-1
lines changed

6 files changed

+153
-1
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ class Definitions {
715715

716716
def Not_value(implicit ctx: Context): TermSymbol = NotModule.requiredMethod(nme.value)
717717

718+
lazy val ValueOfType: TypeRef = ctx.requiredClassRef("scala.ValueOf")
719+
def ValueOfClass(implicit ctx: Context): ClassSymbol = ValueOfType.symbol.asClass
720+
718721
lazy val XMLTopScopeModuleRef: TermRef = ctx.requiredModuleRef("scala.xml.TopScope")
719722

720723
lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,29 @@ trait Implicits { self: Typer =>
690690
}
691691
}
692692

693+
/** Creates a tree that will produce a ValueOf instance for the requested type.
694+
* An EmptyTree is returned if materialization fails.
695+
*/
696+
def synthesizedValueOf(formal: Type)(implicit ctx: Context): Tree = {
697+
def success(t: Tree) = New(defn.ValueOfClass.typeRef.appliedTo(t.tpe), t :: Nil).withPos(pos)
698+
699+
formal.argTypes match {
700+
case arg :: Nil =>
701+
fullyDefinedType(arg.dealias, "ValueOf argument", pos) match {
702+
case ConstantType(c: Constant) =>
703+
success(Literal(c))
704+
case TypeRef(_, sym) if sym == defn.UnitClass =>
705+
success(Literal(Constant(())))
706+
case n: NamedType =>
707+
success(ref(n))
708+
case tp =>
709+
EmptyTree
710+
}
711+
case _ =>
712+
EmptyTree
713+
}
714+
}
715+
693716
def hasEq(tp: Type): Boolean =
694717
inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isSuccess
695718

@@ -714,7 +737,8 @@ trait Implicits { self: Typer =>
714737
trySpecialCase(defn.ClassTagClass, synthesizedClassTag,
715738
trySpecialCase(defn.QuotedTypeClass, synthesizedTypeTag,
716739
trySpecialCase(defn.TastyReflectionClass, synthesizedTastyContext,
717-
trySpecialCase(defn.EqClass, synthesizedEq, failed))))
740+
trySpecialCase(defn.EqClass, synthesizedEq,
741+
trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed)))))
718742
}
719743
}
720744

library/src/dotty/DottyPredef.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,19 @@ object DottyPredef {
4040
@forceInline final def implicitly[T](implicit ev: T): T = ev
4141

4242
@forceInline def locally[T](body: => T): T = body
43+
44+
/**
45+
* Retrieve the single value of a type with a unique inhabitant.
46+
*
47+
* @example {{{
48+
* object Foo
49+
* val foo = valueOf[Foo.type]
50+
* // foo is Foo.type = Foo
51+
*
52+
* val bar = valueOf[23]
53+
* // bar is 23.type = 23
54+
* }}}
55+
* @group utilities
56+
*/
57+
@forceInline def valueOf[T](implicit vt: ValueOf[T]): T = vt.value
4358
}

library/src/scala/ValueOf.scala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala
14+
15+
/**
16+
* `ValueOf[T]` provides the unique value of the type `T` where `T` is a type which has a
17+
* single inhabitant. Eligible types are singleton types of the form `stablePath.type`,
18+
* Unit and singleton types corresponding to value literals.
19+
*
20+
* Instances of `ValueOf[T]` are provided implicitly for all eligible types. Typically
21+
* an instance would be required where a runtime value corresponding to a type level
22+
* computation is needed.
23+
24+
* For example, we might define a type `Residue[M <: Int]` corresponding to the group of
25+
* integers modulo `M`. We could then mandate that residues can be summed only when they
26+
* are parameterized by the same modulus,
27+
*
28+
* {{{
29+
* case class Residue[M <: Int](n: Int) extends AnyVal {
30+
* def +(rhs: Residue[M])(implicit m: ValueOf[M]): Residue[M] =
31+
* Residue((this.n + rhs.n) % valueOf[M])
32+
* }
33+
*
34+
* val fiveModTen = Residue[10](5)
35+
* val nineModTen = Residue[10](9)
36+
*
37+
* fiveModTen + nineModTen // OK == Residue[10](4)
38+
*
39+
* val fourModEleven = Residue[11](4)
40+
*
41+
* fiveModTen + fourModEleven // compiler error: type mismatch;
42+
* // found : Residue[11]
43+
* // required: Residue[10]
44+
* }}}
45+
*
46+
* Notice that here the modulus is encoded in the type of the values and so does not
47+
* incur any additional per-value storage cost. When a runtime value of the modulus
48+
* is required in the implementation of `+` it is provided at the call site via the
49+
* implicit argument `m` of type `ValueOf[M]`.
50+
*/
51+
@scala.annotation.implicitNotFound(msg = "No singleton value available for ${T}.")
52+
final class ValueOf[T](val value: T) extends AnyVal

tests/pos/sip23-aliasing.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
object Test {
2+
trait Foo0 {
3+
type T0
4+
}
5+
6+
object Foo0 {
7+
type Aux[T] = Foo0 {type T0 = T}
8+
implicit def apply[T](implicit v: ValueOf[T]): Aux[T] = new Foo0 {
9+
type T0 = T
10+
}
11+
}
12+
13+
type Foo[T] = Foo0 { type T0 = T }
14+
val Foo = Foo0
15+
16+
Foo[5]
17+
implicitly[Foo.Aux[5]]
18+
implicitly[Foo[5]]
19+
20+
21+
val three: 3 = 3
22+
type Three = three.type
23+
Foo[Three]
24+
implicitly[Foo.Aux[Three]]
25+
implicitly[Foo[Three]]
26+
27+
final object bar
28+
type Bar = bar.type
29+
Foo[Bar]
30+
implicitly[Foo.Aux[Bar]]
31+
implicitly[Foo[Bar]]
32+
}

tests/run/sip23-valueof.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
object Test extends App {
2+
object Foo
3+
val foo = "foo"
4+
5+
implicitly[ValueOf[1]]
6+
implicitly[ValueOf[1L]]
7+
implicitly[ValueOf[1.0]]
8+
implicitly[ValueOf[1.0F]]
9+
implicitly[ValueOf[true]]
10+
implicitly[ValueOf['f']]
11+
implicitly[ValueOf["foo"]]
12+
implicitly[ValueOf[Unit]]
13+
implicitly[ValueOf[Foo.type]]
14+
implicitly[ValueOf[foo.type]]
15+
16+
assert((valueOf[1]: 1) == 1)
17+
assert((valueOf[1L]: 1L) == 1L)
18+
assert((valueOf[1.0]: 1.0) == 1.0)
19+
assert((valueOf[1.0F]: 1.0F) == 1.0F)
20+
assert((valueOf[true]: true) == true)
21+
assert((valueOf['f']: 'f') == 'f')
22+
assert((valueOf["foo"]: "foo") == "foo")
23+
assert((valueOf[Unit]: Unit) == ((): Any))
24+
assert((valueOf[Foo.type]: Foo.type) eq Foo)
25+
assert((valueOf[foo.type]: foo.type) eq foo)
26+
}

0 commit comments

Comments
 (0)