Skip to content

Commit 90a55c0

Browse files
committed
Add Typeable class
It is a generalization of ClassTag with a customizable input type and acting on non erased types.
1 parent e1679f9 commit 90a55c0

File tree

9 files changed

+285
-12
lines changed

9 files changed

+285
-12
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,8 @@ class Definitions {
671671
@tu lazy val QuotedMatchingSymClass: ClassSymbol = ctx.requiredClass("scala.quoted.matching.Sym")
672672
@tu lazy val TastyReflectionClass: ClassSymbol = ctx.requiredClass("scala.tasty.Reflection")
673673

674+
@tu lazy val TypeableClass = ctx.requiredClass("scala.Typeable")
675+
674676
@tu lazy val Unpickler_unpickleExpr: Symbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
675677
@tu lazy val Unpickler_unpickleType: Symbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")
676678

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ object StdNames {
215215
final val Type : N = "Type"
216216
final val TypeTree: N = "TypeTree"
217217

218+
final val Typeable: N = "Typeable"
219+
218220
// Annotation simple names, used in Namer
219221
final val BeanPropertyAnnot: N = "BeanProperty"
220222
final val BooleanBeanPropertyAnnot: N = "BooleanBeanProperty"

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -678,17 +678,27 @@ class Typer extends Namer
678678
* exists, rewrite to `ctag(e)`.
679679
* @pre We are in pattern-matching mode (Mode.Pattern)
680680
*/
681-
def tryWithClassTag(tree: Typed, pt: Type)(implicit ctx: Context): Tree = tree.tpt.tpe.dealias match {
682-
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper =>
683-
require(ctx.mode.is(Mode.Pattern))
684-
inferImplicit(defn.ClassTagClass.typeRef.appliedTo(tref),
685-
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
686-
case SearchSuccess(clsTag, _, _) =>
687-
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
688-
case _ =>
689-
tree
690-
}
691-
case _ => tree
681+
def tryWithClassTag(tree: Typed, pt: Type)(implicit ctx: Context): Tree = {
682+
tree.tpt.tpe.dealias match {
683+
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper =>
684+
require(ctx.mode.is(Mode.Pattern))
685+
inferImplicit(defn.TypeableClass.typeRef.appliedTo(pt, tref),
686+
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
687+
case SearchSuccess(clsTag, _, _) =>
688+
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
689+
case _ =>
690+
inferImplicit(defn.ClassTagClass.typeRef.appliedTo(tref),
691+
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
692+
case SearchSuccess(clsTag, _, _) =>
693+
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
694+
case _ =>
695+
696+
tree
697+
}
698+
}
699+
700+
case _ => tree
701+
}
692702
}
693703

694704
def typedNamedArg(tree: untpd.NamedArg, pt: Type)(implicit ctx: Context): NamedArg = {
@@ -1467,7 +1477,7 @@ class Typer extends Namer
14671477
val body1 = typed(tree.body, pt1)
14681478
body1 match {
14691479
case UnApply(fn, Nil, arg :: Nil)
1470-
if fn.symbol.exists && fn.symbol.owner == defn.ClassTagClass && !body1.tpe.isError =>
1480+
if fn.symbol.exists && (fn.symbol.owner == defn.ClassTagClass || fn.symbol.owner.derivesFrom(defn.TypeableClass)) && !body1.tpe.isError =>
14711481
// A typed pattern `x @ (e: T)` with an implicit `ctag: ClassTag[T]`
14721482
// was rewritten to `x @ ctag(e)` by `tryWithClassTag`.
14731483
// Rewrite further to `ctag(x @ e)`
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
layout: doc-page
3+
title: "Typeable"
4+
---
5+
6+
Typeable
7+
--------
8+
9+
`Typeable` provides the a generalization of `ClassTag.unapply` where the type of the argument is generalized.
10+
`Typeable.unapply` will return `Some(x.asInstanceOf[Y])` if `x` conforms to `Y`, otherwise it returns `None`.
11+
12+
```scala
13+
trait Typeable[S, T <: S] extends Serializable {
14+
def unapply(x: S): Option[T]
15+
}
16+
```
17+
18+
Just like `ClassTag` it can be used to perform type checks in patterns.
19+
20+
```scala
21+
type X
22+
type Y <: X
23+
24+
given Typeable[X, Y] = ...
25+
26+
(x: X) match {
27+
case y: Y => ... // safe checked downcast
28+
case _ => ...
29+
}
30+
```
31+
32+
33+
Examples
34+
--------
35+
36+
Given the following abstract definition of `Peano` numbers that provides `Typeable[Nat, Zero]` and `Typeable[Nat, Succ]`
37+
38+
```scala
39+
trait Peano {
40+
type Nat
41+
type Zero <: Nat
42+
type Succ <: Nat
43+
44+
def safeDiv(m: Nat, n: Succ): (Nat, Nat)
45+
46+
val Zero: Zero
47+
48+
val Succ: SuccExtractor
49+
trait SuccExtractor {
50+
def apply(nat: Nat): Succ
51+
def unapply(nat: Succ): Option[Nat]
52+
}
53+
54+
given Typeable[Nat, Zero] {
55+
def unapply(x: Nat): Option[Zero] = matchZero(x)
56+
}
57+
58+
given Typeable[Nat, Succ] {
59+
def unapply(x: Nat): Option[Succ] = matchSucc(x)
60+
}
61+
62+
protected def matchZero(x: Nat): Option[Zero]
63+
protected def matchSucc(x: Nat): Option[Succ]
64+
}
65+
```
66+
67+
it will be possible to write the following program
68+
69+
```scala
70+
val peano: Peano = ...
71+
import peano.{_, given}
72+
73+
def divOpt(m: Nat, n: Nat): Option[(Nat, Nat)] = {
74+
n match {
75+
case Zero => None
76+
case s @ Succ(_) => Some(safeDiv(m, s))
77+
}
78+
}
79+
80+
val two = Succ(Succ(Zero))
81+
val five = Succ(Succ(Succ(two)))
82+
println(divOpt(five, two))
83+
```
84+
85+
Note that without the `Typeable[Nat, Succ]` the pattern `Succ.unapply` would be unchecked.

library/src/scala/Typeable.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package scala
2+
3+
/** A `Typeable[S, T]` (where `T <: S`) contains the logic needed to know at runtime if a value of
4+
* type `S` can be downcased to `T`.
5+
*
6+
* Unlike `ClassTag`, a `Typeable` must check the type arguments at runtime.
7+
*/
8+
trait Typeable[S, T <: S] extends Serializable {
9+
def unapply(x: S): Option[T]
10+
}

tests/run/refined-binding-nat.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Some((SuccClass(SuccClass(ZeroObject)),SuccClass(ZeroObject)))
2+
Some((ZeroObject,SuccClass(SuccClass(ZeroObject))))
3+
None
4+
Some((2,1))
5+
Some((0,2))
6+
None

tests/run/refined-binding-nat.scala

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
app(ClassNums)
5+
app(IntNums)
6+
}
7+
8+
def app(peano: Peano): Unit = {
9+
import peano.{_, given}
10+
def divOpt(m: Nat, n: Nat): Option[(Nat, Nat)] = {
11+
n match {
12+
case Zero => None
13+
case s @ Succ(_) => Some(safeDiv(m, s))
14+
}
15+
}
16+
val two = Succ(Succ(Zero))
17+
val five = Succ(Succ(Succ(two)))
18+
println(divOpt(five, two))
19+
println(divOpt(two, five))
20+
println(divOpt(two, Zero))
21+
}
22+
}
23+
24+
trait Peano {
25+
type Nat
26+
type Zero <: Nat
27+
type Succ <: Nat
28+
29+
def safeDiv(m: Nat, n: Succ): (Nat, Nat)
30+
31+
given Typeable[Nat, Zero] {
32+
def unapply(x: Nat): Option[Zero] = matchZero(x)
33+
}
34+
35+
given Typeable[Nat, Succ] {
36+
def unapply(x: Nat): Option[Succ] = matchSucc(x)
37+
}
38+
39+
// given reflect.ClassTag[Succ] = ???
40+
41+
protected def matchZero(x: Nat): Option[Zero]
42+
protected def matchSucc(x: Nat): Option[Succ]
43+
44+
implicit def succDeco(succ: Succ): SuccAPI
45+
trait SuccAPI {
46+
def pred: Nat
47+
}
48+
49+
val Zero: Zero
50+
51+
val Succ: SuccExtractor
52+
trait SuccExtractor {
53+
def apply(nat: Nat): Succ
54+
def unapply(nat: Succ): Option[Nat]
55+
}
56+
}
57+
58+
object IntNums extends Peano {
59+
type Nat = Int
60+
type Zero = Int
61+
type Succ = Int
62+
63+
protected def matchZero(x: Nat): Option[Zero] = if (x == 0) Some(0) else None
64+
protected def matchSucc(x: Nat): Option[Succ] = if (x != 0) Some(x) else None
65+
66+
def safeDiv(m: Nat, n: Succ): (Nat, Nat) = (m / n, m % n)
67+
68+
val Zero: Zero = 0
69+
70+
object Succ extends SuccExtractor {
71+
def apply(nat: Nat): Succ = nat + 1
72+
def unapply(nat: Succ) = Some(nat - 1)
73+
}
74+
def succDeco(succ: Succ): SuccAPI = new SuccAPI {
75+
def pred: Nat = succ - 1
76+
}
77+
}
78+
79+
object ClassNums extends Peano {
80+
trait NatTrait
81+
object ZeroObject extends NatTrait {
82+
override def toString: String = "ZeroObject"
83+
}
84+
case class SuccClass(predecessor: NatTrait) extends NatTrait
85+
86+
type Nat = NatTrait
87+
type Zero = ZeroObject.type
88+
type Succ = SuccClass
89+
90+
protected def matchZero(x: Nat): Option[Zero] = x match {
91+
case Zero => Some(Zero)
92+
case _ => None
93+
}
94+
protected def matchSucc(x: Nat): Option[Succ] = x match {
95+
case nat: SuccClass => Some(nat)
96+
case _ => None
97+
}
98+
99+
def safeDiv(m: Nat, n: Succ): (Nat, Nat) = {
100+
def intValue(x: Nat, acc: Int): Int = x match {
101+
case nat: SuccClass => intValue(nat.predecessor, acc + 1)
102+
case _ => acc
103+
}
104+
def natValue(x: Int): Nat =
105+
if (x == 0) ZeroObject
106+
else new SuccClass(natValue(x - 1))
107+
val i = intValue(m, 0)
108+
val j = intValue(n, 0)
109+
(natValue(i / j), natValue(i % j))
110+
}
111+
112+
val Zero: Zero = ZeroObject
113+
114+
object Succ extends SuccExtractor {
115+
def apply(nat: Nat): Succ = new SuccClass(nat)
116+
def unapply(nat: Succ) = Some(nat.predecessor)
117+
}
118+
119+
def succDeco(succ: Succ): SuccAPI = new SuccAPI {
120+
def pred: Nat = succ.predecessor
121+
}
122+
123+
}

tests/run/refined-binding.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ok
2+
9

tests/run/refined-binding.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
sealed trait Foo {
3+
4+
type X
5+
type Y <: X
6+
7+
def x: X
8+
9+
def f(y: Y) = println("ok")
10+
11+
given Typeable[X, Y] = new Typeable {
12+
def unapply(x: X): Option[Y] = Some(x.asInstanceOf[Y])
13+
}
14+
15+
object Z {
16+
def unapply(arg: Y): Option[Int] = Some(9)
17+
}
18+
}
19+
20+
object Test {
21+
def main(args: Array[String]): Unit = {
22+
test(new Foo { type X = Int; type Y = Int; def x: X = 1 })
23+
}
24+
25+
def test(foo: Foo): Unit = {
26+
import foo.given
27+
foo.x match {
28+
case x @ foo.Z(i) => // `x` is refined to type `foo.Y`
29+
foo.f(x)
30+
println(i)
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)