forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix scala#7554: Implement TypeTest interface
Using tests from: https://gist.github.com/Blaisorblade/a0eebb6a4f35344e48c4c60dc2a14ce6
- Loading branch information
1 parent
6ab0e63
commit 6d04ca4
Showing
16 changed files
with
520 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
--- | ||
layout: doc-page | ||
title: "TypeTest" | ||
--- | ||
|
||
TypeTest | ||
-------- | ||
|
||
`TypeTest` provides a replacement for `ClassTag.unapply` where the type of the argument is generalized. | ||
`TypeTest.unapply` will return `Some(x.asInstanceOf[Y])` if `x` conforms to `Y`, otherwise it returns `None`. | ||
|
||
```scala | ||
trait TypeTest[-S, T] extends Serializable { | ||
def unapply(s: S): Option[s.type & T] | ||
} | ||
``` | ||
|
||
Just like `ClassTag` used to do, it can be used to perform type checks in patterns. | ||
|
||
```scala | ||
type X | ||
type Y <: X | ||
given TypeTest[X, Y] = ... | ||
(x: X) match { | ||
case y: Y => ... // safe checked downcast | ||
case _ => ... | ||
} | ||
``` | ||
|
||
|
||
Examples | ||
-------- | ||
|
||
Given the following abstract definition of `Peano` numbers that provides `TypeTest[Nat, Zero]` and `TypeTest[Nat, Succ]` | ||
|
||
```scala | ||
trait Peano { | ||
type Nat | ||
type Zero <: Nat | ||
type Succ <: Nat | ||
def safeDiv(m: Nat, n: Succ): (Nat, Nat) | ||
val Zero: Zero | ||
val Succ: SuccExtractor | ||
trait SuccExtractor { | ||
def apply(nat: Nat): Succ | ||
def unapply(nat: Succ): Option[Nat] | ||
} | ||
given TypeTest[Nat, Zero] = typeTestOfZero | ||
protected def typeTestOfZero: TypeTest[Nat, Zero] | ||
given TypeTest[Nat, Succ] | ||
protected def typeTestOfSucc: TypeTest[Nat, Succ] | ||
``` | ||
|
||
it will be possible to write the following program | ||
|
||
```scala | ||
val peano: Peano = ... | ||
import peano.{_, given} | ||
def divOpt(m: Nat, n: Nat): Option[(Nat, Nat)] = { | ||
n match { | ||
case Zero => None | ||
case s @ Succ(_) => Some(safeDiv(m, s)) | ||
} | ||
} | ||
val two = Succ(Succ(Zero)) | ||
val five = Succ(Succ(Succ(two))) | ||
println(divOpt(five, two)) | ||
``` | ||
|
||
Note that without the `TypeTest[Nat, Succ]` the pattern `Succ.unapply(nat: Succ)` would be unchecked. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package scala.reflect | ||
|
||
/** A `TypeTest[S, T] contains the logic needed to know at runtime if a value of | ||
* type `S` can be downcasted to `T`. | ||
* | ||
* If a pattern match is performed on a term of type `s: S` that is uncheckable with `s.isInstanceOf[T]` and | ||
* the pattern are of the form: | ||
* - `t: T` | ||
* - `t @ X()` where the `X.unapply` has takes an argument of type `T` | ||
* then a given instance of `TypeTest[S, T]` is summoned and used to perform the test. | ||
*/ | ||
@scala.annotation.implicitNotFound(msg = "No TypeTest available for [${S}, ${T}]") | ||
trait TypeTest[-S, T] extends Serializable { | ||
|
||
/** A TypeTest[S, T] can serve as an extractor that matches only S of type T. | ||
* | ||
* The compiler tries to turn unchecked type tests in pattern matches into checked ones | ||
* by wrapping a `(_: T)` type pattern as `tt(_: T)`, where `tt` is the `TypeTest[S, T]` instance. | ||
* Type tests necessary before calling other extractors are treated similarly. | ||
* `SomeExtractor(...)` is turned into `tt(SomeExtractor(...))` if `T` in `SomeExtractor.unapply(x: T)` | ||
* is uncheckable, but we have an instance of `TypeTest[S, T]`. | ||
*/ | ||
def unapply(x: S): Option[x.type & T] | ||
|
||
} | ||
|
||
object TypeTest { | ||
|
||
def identity[T]: TypeTest[T, T] = Some(_) | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
tests/neg-custom-args/fatal-warnings/IsInstanceOfClassTag.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import scala.reflect.ClassTag | ||
|
||
object IsInstanceOfClassTag { | ||
def safeCast[T: ClassTag](x: Any): Option[T] = { | ||
x match { | ||
case x: T => Some(x) // TODO error: deprecation waring | ||
case _ => None | ||
} | ||
} | ||
|
||
def main(args: Array[String]): Unit = { | ||
safeCast[List[String]](List[Int](1)) match { | ||
case None => | ||
case Some(xs) => | ||
xs.head.substring(0) | ||
} | ||
|
||
safeCast[List[_]](List[Int](1)) match { | ||
case None => | ||
case Some(xs) => | ||
xs.head.substring(0) // error | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
tests/neg-custom-args/fatal-warnings/IsInstanceOfClassTag2.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import scala.reflect.TypeTest | ||
|
||
object IsInstanceOfClassTag { | ||
def safeCast[T](x: Any)(using TypeTest[Any, T]): Option[T] = { | ||
x match { | ||
case x: T => Some(x) | ||
case _ => None | ||
} | ||
} | ||
|
||
def main(args: Array[String]): Unit = { | ||
safeCast[List[String]](List[Int](1)) match { // error | ||
case None => | ||
case Some(xs) => | ||
} | ||
|
||
safeCast[List[_]](List[Int](1)) match { | ||
case None => | ||
case Some(xs) => | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
tests/neg-custom-args/fatal-warnings/type-test-paths-2.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import scala.reflect.TypeTest | ||
|
||
trait R { | ||
type Nat | ||
type Succ <: Nat | ||
type Idx | ||
given TypeTest[Nat, Succ] = typeTestOfSucc | ||
protected def typeTestOfSucc: TypeTest[Nat, Succ] | ||
def n: Nat | ||
def one: Succ | ||
} | ||
|
||
object RI extends R { | ||
type Nat = Int | ||
type Succ = Int | ||
type Idx = Int | ||
protected def typeTestOfSucc: TypeTest[Nat, Succ] = new { | ||
def unapply(x: Int): Option[x.type & Succ] = | ||
if x > 0 then Some(x) else None | ||
} | ||
def n: Nat = 4 | ||
def one: Succ = 1 | ||
} | ||
|
||
object Test { | ||
val r1: R = RI | ||
val r2: R = RI | ||
|
||
r1.n match { | ||
case n: r2.Nat => // error: the type test for Test.r2.Nat cannot be checked at runtime | ||
case n: r1.Idx => // error: the type test for Test.r1.Idx cannot be checked at runtime | ||
case n: r1.Succ => // Ok | ||
case n: r1.Nat => // Ok | ||
} | ||
|
||
r1.one match { | ||
case n: r2.Nat => // error: the type test for Test.r2.Nat cannot be checked at runtime | ||
case n: r1.Idx => // error: the type test for Test.r1.Idx cannot be checked at runtime | ||
case n: r1.Nat => // Ok | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
tests/neg-custom-args/fatal-warnings/type-test-paths.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import scala.reflect.TypeTest | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
val p1: T = T1 | ||
val p2: T = T1 | ||
|
||
(p1.y: p1.X) match { | ||
case x: p2.Y => // error: unchecked | ||
case x: p1.Y => | ||
case _ => | ||
} | ||
} | ||
|
||
} | ||
|
||
trait T { | ||
type X | ||
type Y <: X | ||
def x: X | ||
def y: Y | ||
given TypeTest[X, Y] = typeTestOfY | ||
protected def typeTestOfY: TypeTest[X, Y] | ||
} | ||
|
||
object T1 extends T { | ||
type X = Boolean | ||
type Y = true | ||
def x: X = false | ||
def y: Y = true | ||
protected def typeTestOfY: TypeTest[X, Y] = new { | ||
def unapply(x: X): Option[x.type & Y] = x match | ||
case x: (true & x.type) => Some(x) | ||
case _ => None | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
tests/neg-custom-args/fatal-warnings/type-test-syntesize.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import scala.reflect.TypeTest | ||
|
||
object Test { | ||
def test[S, T](using TypeTest[S, T]): Unit = () | ||
val a: A = ??? | ||
|
||
test[Any, Any] | ||
test[Int, Int] | ||
|
||
test[Int, Any] | ||
test[String, Any] | ||
test[String, AnyRef] | ||
|
||
test[Any, Int] | ||
test[Any, String] | ||
test[Any, Some[_]] | ||
test[Any, Array[Int]] | ||
test[Seq[Int], List[Int]] | ||
|
||
test[Any, Some[Int]] // error | ||
test[Any, a.X] // error | ||
test[a.X, a.Y] // error | ||
|
||
} | ||
|
||
class A { | ||
type X | ||
type Y <: X | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import scala.reflect.TypeTest | ||
|
||
object Test { | ||
def test[S, T](using x: TypeTest[S, T]): Unit = () | ||
|
||
test[Any, AnyRef] // error | ||
test[Any, AnyVal] // error | ||
test[Any, Object] // error | ||
|
||
test[Any, Nothing] // error | ||
test[AnyRef, Nothing] // error | ||
test[AnyVal, Nothing] // error | ||
test[Null, Nothing] // error | ||
test[Unit, Nothing] // error | ||
test[Int, Nothing] // error | ||
test[8, Nothing] // error | ||
test[List[_], Nothing] // error | ||
test[Nothing, Nothing] // error | ||
} |
Oops, something went wrong.