Skip to content

Add generalized Typeable #7394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,8 @@ class Definitions {
@tu lazy val QuotedMatchingSymClass: ClassSymbol = ctx.requiredClass("scala.quoted.matching.Sym")
@tu lazy val TastyReflectionClass: ClassSymbol = ctx.requiredClass("scala.tasty.Reflection")

@tu lazy val TypeableClass = ctx.requiredClass("scala.Typeable")

@tu lazy val Unpickler_unpickleExpr: Symbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
@tu lazy val Unpickler_unpickleType: Symbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")

Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ object StdNames {
final val Type : N = "Type"
final val TypeTree: N = "TypeTree"

final val Typeable: N = "Typeable"

// Annotation simple names, used in Namer
final val BeanPropertyAnnot: N = "BeanProperty"
final val BooleanBeanPropertyAnnot: N = "BooleanBeanProperty"
Expand Down
34 changes: 22 additions & 12 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -678,17 +678,27 @@ class Typer extends Namer
* exists, rewrite to `ctag(e)`.
* @pre We are in pattern-matching mode (Mode.Pattern)
*/
def tryWithClassTag(tree: Typed, pt: Type)(implicit ctx: Context): Tree = tree.tpt.tpe.dealias match {
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper =>
require(ctx.mode.is(Mode.Pattern))
inferImplicit(defn.ClassTagClass.typeRef.appliedTo(tref),
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
case SearchSuccess(clsTag, _, _) =>
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
case _ =>
tree
}
case _ => tree
def tryWithClassTag(tree: Typed, pt: Type)(implicit ctx: Context): Tree = {
tree.tpt.tpe.dealias match {
case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper =>
require(ctx.mode.is(Mode.Pattern))
inferImplicit(defn.TypeableClass.typeRef.appliedTo(pt, tref),
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
case SearchSuccess(clsTag, _, _) =>
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
case _ =>
inferImplicit(defn.ClassTagClass.typeRef.appliedTo(tref),
EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match {
case SearchSuccess(clsTag, _, _) =>
typed(untpd.Apply(untpd.TypedSplice(clsTag), untpd.TypedSplice(tree.expr)), pt)
case _ =>

tree
}
}

case _ => tree
}
}

def typedNamedArg(tree: untpd.NamedArg, pt: Type)(implicit ctx: Context): NamedArg = {
Expand Down Expand Up @@ -1467,7 +1477,7 @@ class Typer extends Namer
val body1 = typed(tree.body, pt1)
body1 match {
case UnApply(fn, Nil, arg :: Nil)
if fn.symbol.exists && fn.symbol.owner == defn.ClassTagClass && !body1.tpe.isError =>
if fn.symbol.exists && (fn.symbol.owner == defn.ClassTagClass || fn.symbol.owner.derivesFrom(defn.TypeableClass)) && !body1.tpe.isError =>
// A typed pattern `x @ (e: T)` with an implicit `ctag: ClassTag[T]`
// was rewritten to `x @ ctag(e)` by `tryWithClassTag`.
// Rewrite further to `ctag(x @ e)`
Expand Down
85 changes: 85 additions & 0 deletions docs/docs/reference/other-new-features/typeable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
layout: doc-page
title: "Typeable"
---

Typeable
--------

`Typeable` provides the a generalization of `ClassTag.unapply` where the type of the argument is generalized.
`Typeable.unapply` will return `Some(x.asInstanceOf[Y])` if `x` conforms to `Y`, otherwise it returns `None`.

```scala
trait Typeable[S, T <: S] extends Serializable {
def unapply(x: S): Option[T]
}
```

Just like `ClassTag` it can be used to perform type checks in patterns.

```scala
type X
type Y <: X

given Typeable[X, Y] = ...

(x: X) match {
case y: Y => ... // safe checked downcast
case _ => ...
}
```


Examples
--------

Given the following abstract definition of `Peano` numbers that provides `Typeable[Nat, Zero]` and `Typeable[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 Typeable[Nat, Zero] {
def unapply(x: Nat): Option[Zero] = matchZero(x)
}

given Typeable[Nat, Succ] {
def unapply(x: Nat): Option[Succ] = matchSucc(x)
}

protected def matchZero(x: Nat): Option[Zero]
protected def matchSucc(x: Nat): Option[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 `Typeable[Nat, Succ]` the pattern `Succ.unapply` would be unchecked.
10 changes: 10 additions & 0 deletions library/src/scala/Typeable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package scala

/** A `Typeable[S, T]` (where `T <: S`) contains the logic needed to know at runtime if a value of
* type `S` can be downcased to `T`.
*
* Unlike `ClassTag`, a `Typeable` must check the type arguments at runtime.
*/
trait Typeable[S, T <: S] extends Serializable {
def unapply(x: S): Option[T]
}
50 changes: 50 additions & 0 deletions library/src/scala/tasty/reflect/Core.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,72 +120,95 @@ trait Core {

/** Tree representing a pacakage clause in the source code */
type PackageClause = internal.PackageClause
given (given ctx: Context): Typeable[Tree, PackageClause] = internal.matchPackageClause(_)

/** Tree representing a statement in the source code */
type Statement = internal.Statement
given (given ctx: Context): Typeable[Tree, Statement] = internal.matchStatement(_)

/** Tree representing an import in the source code */
type Import = internal.Import
given (given ctx: Context): Typeable[Tree, Import] = internal.matchImport(_)

/** Tree representing a definition in the source code. It can be `PackageDef`, `ClassDef`, `TypeDef`, `DefDef` or `ValDef` */
type Definition = internal.Definition
given (given ctx: Context): Typeable[Tree, Definition] = internal.matchDefinition(_)

/** Tree representing a package definition. This includes definitions in all source files */
type PackageDef = internal.PackageDef
given (given ctx: Context): Typeable[Tree, PackageDef] = internal.matchPackageDef(_)

/** Tree representing a class definition. This includes annonymus class definitions and the class of a module object */
type ClassDef = internal.ClassDef
given (given ctx: Context): Typeable[Tree, ClassDef] = internal.matchClassDef(_)

/** Tree representing a type (paramter or member) definition in the source code */
type TypeDef = internal.TypeDef
given (given ctx: Context): Typeable[Tree, TypeDef] = internal.matchTypeDef(_)

/** Tree representing a method definition in the source code */
type DefDef = internal.DefDef
given (given ctx: Context): Typeable[Tree, DefDef] = internal.matchDefDef(_)

/** Tree representing a value definition in the source code This inclues `val`, `lazy val`, `var`, `object` and parameter defintions. */
type ValDef = internal.ValDef
given (given ctx: Context): Typeable[Tree, ValDef] = internal.matchValDef(_)

/** Tree representing an expression in the source code */
type Term = internal.Term
given (given ctx: Context): Typeable[Tree, Term] = internal.matchTerm(_)

/** Tree representing a reference to definition */
type Ref = internal.Ref
given (given ctx: Context): Typeable[Tree, Ref] = internal.matchRef(_)

/** Tree representing a reference to definition with a given name */
type Ident = internal.Ident
given (given ctx: Context): Typeable[Tree, Ident] = internal.matchIdent(_)

/** Tree representing a selection of definition with a given name on a given prefix */
type Select = internal.Select
given (given ctx: Context): Typeable[Tree, Select] = internal.matchSelect(_)

/** Tree representing a literal value in the source code */
type Literal = internal.Literal
given (given ctx: Context): Typeable[Tree, Literal] = internal.matchLiteral(_)

/** Tree representing `this` in the source code */
type This = internal.This
given (given ctx: Context): Typeable[Tree, This] = internal.matchThis(_)

/** Tree representing `new` in the source code */
type New = internal.New
given (given ctx: Context): Typeable[Tree, New] = internal.matchNew(_)

/** Tree representing an argument passed with an explicit name. Such as `arg1 = x` in `foo(arg1 = x)` */
type NamedArg = internal.NamedArg
given (given ctx: Context): Typeable[Tree, NamedArg] = internal.matchNamedArg(_)

/** Tree an application of arguments. It represents a single list of arguments, multiple argument lists will have nested `Apply`s */
type Apply = internal.Apply
given (given ctx: Context): Typeable[Tree, Apply] = internal.matchApply(_)

/** Tree an application of type arguments */
type TypeApply = internal.TypeApply
given (given ctx: Context): Typeable[Tree, TypeApply] = internal.matchTypeApply(_)

/** Tree representing `super` in the source code */
type Super = internal.Super
given (given ctx: Context): Typeable[Tree, Super] = internal.matchSuper(_)

/** Tree representing a type ascription `x: T` in the source code */
type Typed = internal.Typed
given (given ctx: Context): Typeable[Tree, Typed] = internal.matchTyped(_)

/** Tree representing an assignment `x = y` in the source code */
type Assign = internal.Assign
given (given ctx: Context): Typeable[Tree, Assign] = internal.matchAssign(_)

/** Tree representing a block `{ ... }` in the source code */
type Block = internal.Block
given (given ctx: Context): Typeable[Tree, Block] = internal.matchBlock(_)

/** A lambda `(...) => ...` in the source code is represented as
* a local method and a closure:
Expand All @@ -197,87 +220,114 @@ trait Core {
*
*/
type Closure = internal.Closure
given (given ctx: Context): Typeable[Tree, Closure] = internal.matchClosure(_)

/** Tree representing an if/then/else `if (...) ... else ...` in the source code */
type If = internal.If
given (given ctx: Context): Typeable[Tree, If] = internal.matchIf(_)

/** Tree representing a pattern match `x match { ... }` in the source code */
type Match = internal.Match
given (given ctx: Context): Typeable[Tree, Match] = internal.matchMatch(_)

/** Tree representing a pattern match `delegate match { ... }` in the source code */ // TODO: drop
type ImpliedMatch = internal.ImpliedMatch
given (given ctx: Context): Typeable[Tree, ImpliedMatch] = internal.matchImplicitMatch(_)

/** Tree representing a try catch `try x catch { ... } finally { ... }` in the source code */
type Try = internal.Try
given (given ctx: Context): Typeable[Tree, Try] = internal.matchTry(_)

/** Tree representing a `return` in the source code */
type Return = internal.Return
given (given ctx: Context): Typeable[Tree, Return] = internal.matchReturn(_)

/** Tree representing a variable argument list in the source code */
type Repeated = internal.Repeated
given (given ctx: Context): Typeable[Tree, Repeated] = internal.matchRepeated(_)

/** Tree representing the scope of an inlined tree */
type Inlined = internal.Inlined
given (given ctx: Context): Typeable[Tree, Inlined] = internal.matchInlined(_)

/** Tree representing a selection of definition with a given name on a given prefix and number of nested scopes of inlined trees */
type SelectOuter = internal.SelectOuter
given (given ctx: Context): Typeable[Tree, SelectOuter] = internal.matchSelectOuter(_)

/** Tree representing a while loop */
type While = internal.While
given (given ctx: Context): Typeable[Tree, While] = internal.matchWhile(_)

/** Type tree representing a type written in the source */
type TypeTree = internal.TypeTree
given (given ctx: Context): Typeable[Tree, TypeTree] = internal.matchTypeTree(_)

/** Type tree representing an inferred type */
type Inferred = internal.Inferred
given (given ctx: Context): Typeable[Tree, Inferred] = internal.matchInferred(_)

/** Type tree representing a reference to definition with a given name */
type TypeIdent = internal.TypeIdent
given (given ctx: Context): Typeable[Tree, TypeIdent] = internal.matchTypeIdent(_)

/** Type tree representing a selection of definition with a given name on a given term prefix */
type TypeSelect = internal.TypeSelect
given (given ctx: Context): Typeable[Tree, TypeSelect] = internal.matchTypeSelect(_)

/** Type tree representing a selection of definition with a given name on a given type prefix */
type Projection = internal.Projection
given (given ctx: Context): Typeable[Tree, Projection] = internal.matchProjection(_)

/** Type tree representing a singleton type */
type Singleton = internal.Singleton
given (given ctx: Context): Typeable[Tree, Singleton] = internal.matchSingleton(_)

/** Type tree representing a type refinement */
type Refined = internal.Refined
given (given ctx: Context): Typeable[Tree, Refined] = internal.matchRefined(_)

/** Type tree representing a type application */
type Applied = internal.Applied
given (given ctx: Context): Typeable[Tree, Applied] = internal.matchApplied(_)

/** Type tree representing an annotated type */
type Annotated = internal.Annotated
given (given ctx: Context): Typeable[Tree, Annotated] = internal.matchAnnotated(_)

/** Type tree representing a type match */
type MatchTypeTree = internal.MatchTypeTree
given (given ctx: Context): Typeable[Tree, MatchTypeTree] = internal.matchMatchTypeTree(_)

/** Type tree representing a by name parameter */
type ByName = internal.ByName
given (given ctx: Context): Typeable[Tree, ByName] = internal.matchByName(_)

/** Type tree representing a lambda abstraction type */
type LambdaTypeTree = internal.LambdaTypeTree
given (given ctx: Context): Typeable[Tree, LambdaTypeTree] = internal.matchLambdaTypeTree(_)

/** Type tree representing a type binding */
type TypeBind = internal.TypeBind
given (given ctx: Context): Typeable[Tree, TypeBind] = internal.matchTypeBind(_)

/** Type tree within a block with aliases `{ type U1 = ... ; T[U1, U2] }` */
type TypeBlock = internal.TypeBlock
given (given ctx: Context): Typeable[Tree, TypeBlock] = internal.matchTypeBlock(_)

/** Type tree representing a type bound written in the source */
type TypeBoundsTree = internal.TypeBoundsTree
given (given ctx: Context): Typeable[Tree, TypeBoundsTree] = internal.matchTypeBoundsTree(_)

/** Type tree representing wildcard type bounds written in the source.
* The wildcard type `_` (for example in in `List[_]`) will be a type tree that
* represents a type but has `TypeBound`a inside.
*/
type WildcardTypeTree = internal.WildcardTypeTree
given (given ctx: Context): Typeable[Tree, WildcardTypeTree] = internal.matchWildcardTypeTree(_)

/** Branch of a pattern match or catch clause */
type CaseDef = internal.CaseDef
given (given ctx: Context): Typeable[Tree, CaseDef] = internal.matchCaseDef(_)

/** Branch of a type pattern match */
type TypeCaseDef = internal.TypeCaseDef
Expand Down
3 changes: 1 addition & 2 deletions tests/run-macros/i5715/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ object scalatest {
import qctx.tasty.{_, given}

cond.unseal.underlyingArgument match {
case app @ Apply(sel @ Select(lhs, op), rhs :: Nil) =>
val IsSelect(select) = sel
case app @ Apply(select @ Select(lhs, op), rhs :: Nil) =>
val cond = Apply(Select.copy(select)(lhs, "exists"), rhs :: Nil).seal.cast[Boolean]
'{ scala.Predef.assert($cond) }
case _ =>
Expand Down
3 changes: 1 addition & 2 deletions tests/run-macros/reflect-select-copy/assert_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ object scalatest {
import qctx.tasty.{_, given}

cond.unseal.underlyingArgument match {
case Apply(sel @ Select(lhs, op), rhs :: Nil) =>
val IsSelect(select) = sel
case Apply(select @ Select(lhs, op), rhs :: Nil) =>
val cond = Apply(Select.copy(select)(lhs, ">"), rhs :: Nil).seal.cast[Boolean]
'{ scala.Predef.assert($cond) }
case _ =>
Expand Down
Loading