Skip to content

Commit

Permalink
Fix #20287: Add flexible types to Quotes library (#20293)
Browse files Browse the repository at this point in the history
Fix #20287: Add flexible types to `Quotes` library.
  • Loading branch information
noti0na1 committed May 6, 2024
2 parents 7f8afa5 + ce4d618 commit 6af2bcf
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 5 deletions.
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ object Types extends TypeUtils {
* | +- TypeVar
* | +- HKTypeLambda
* | +- MatchType
* | +- FlexibleType
* |
* +- GroundType -+- AndType
* +- OrType
Expand Down Expand Up @@ -3468,7 +3469,7 @@ object Types extends TypeUtils {
* `T | Null .. T`, so that `T | Null <: FlexibleType(T) <: T`.
* A flexible type will be erased to its original type `T`.
*/
case class FlexibleType(lo: Type, hi: Type) extends CachedProxyType with ValueType {
case class FlexibleType protected(lo: Type, hi: Type) extends CachedProxyType with ValueType {

override def underlying(using Context): Type = hi

Expand All @@ -3481,7 +3482,7 @@ object Types extends TypeUtils {
}

object FlexibleType {
def apply(tp: Type)(using Context): Type = tp match {
def apply(tp: Type)(using Context): FlexibleType = tp match {
case ft: FlexibleType => ft
case _ =>
// val tp1 = tp.stripNull()
Expand Down
23 changes: 22 additions & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2230,7 +2230,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
def isErased: Boolean = false
def isImplicit: Boolean = self.isImplicitMethod
def isContextual: Boolean = self.isContextualMethod
def methodTypeKind: MethodTypeKind =
def methodTypeKind: MethodTypeKind =
self.companion match
case Types.ContextualMethodType => MethodTypeKind.Contextual
case Types.ImplicitMethodType => MethodTypeKind.Implicit
Expand Down Expand Up @@ -2343,6 +2343,27 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
def unapply(x: NoPrefix): true = true
end NoPrefix

type FlexibleType = dotc.core.Types.FlexibleType

object FlexibleTypeTypeTest extends TypeTest[TypeRepr, FlexibleType]:
def unapply(x: TypeRepr): Option[FlexibleType & x.type] = x match
case x: (Types.FlexibleType & x.type) => Some(x)
case _ => None
end FlexibleTypeTypeTest

object FlexibleType extends FlexibleTypeModule:
def apply(tp: TypeRepr): FlexibleType = Types.FlexibleType(tp)
def unapply(x: FlexibleType): Some[TypeRepr] = Some(x.hi)
end FlexibleType

given FlexibleTypeMethods: FlexibleTypeMethods with
extension (self: FlexibleType)
def underlying: TypeRepr = self.hi
def lo: TypeRepr = self.lo
def hi: TypeRepr = self.hi
end extension
end FlexibleTypeMethods

type Constant = dotc.core.Constants.Constant

object Constant extends ConstantModule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ object Extractors {
this += "NoPrefix()"
case MatchCase(pat, rhs) =>
this += "MatchCase(" += pat += ", " += rhs += ")"
case FlexibleType(tp) =>
this += "FlexibleType(" += tp += ")"
}

def visitSignature(sig: Signature): this.type = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,11 @@ object SourceCode {
this += " => "
printType(rhs)

case FlexibleType(tp) =>
this += "("
printType(tp)
this += ")?"

case _ =>
cannotBeShownAsSource(tpe.show(using Printer.TypeReprStructure))
}
Expand Down
1 change: 1 addition & 0 deletions docs/_docs/internals/type-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Type -+- ProxyType --+- NamedType ----+--- TypeRef
| +- TypeVar
| +- HKTypeLambda
| +- MatchType
| +- FlexibleType
|
+- GroundType -+- AndType
+- OrType
Expand Down
34 changes: 32 additions & 2 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
* +- MatchCase
* +- TypeBounds
* +- NoPrefix
*
* +- FlexibleType
*
* +- MethodTypeKind -+- Contextual
* +- Implicit
* +- Plain
Expand Down Expand Up @@ -3273,7 +3274,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
def isImplicit: Boolean
/** Is this the type of parameter clause like `(using X1, ..., Xn)` or `(using x1: X1, x2: X2, ... )` */
def isContextual: Boolean
/** Returns a MethodTypeKind object representing the implicitness of the MethodType parameter clause. */
/** Returns a MethodTypeKind object representing the implicitness of the MethodType parameter clause. */
def methodTypeKind: MethodTypeKind
/** Is this the type of erased parameter clause `(erased x1: X1, ..., xn: Xn)` */
@deprecated("Use `hasErasedParams` and `erasedParams`", "3.4")
Expand Down Expand Up @@ -3428,6 +3429,35 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
def unapply(x: NoPrefix): true
}

// ----- Flexible Type --------------------------------------------

/** Flexible types for explicit nulls */
type FlexibleType <: TypeRepr

/** `TypeTest` that allows testing at runtime in a pattern match if a `TypeRepr` is a `FlexibleType` */
given FlexibleTypeTypeTest: TypeTest[TypeRepr, FlexibleType]

/** Module object of `type FlexibleType` */
val FlexibleType: FlexibleTypeModule

/** Methods of the module object `val FlexibleType` */
trait FlexibleTypeModule { this: FlexibleType.type =>
def apply(tp: TypeRepr): FlexibleType
def unapply(x: FlexibleType): Option[TypeRepr]
}

/** Makes extension methods on `FlexibleType` available without any imports */
given FlexibleTypeMethods: FlexibleTypeMethods

/** Extension methods of `FlexibleType` */
trait FlexibleTypeMethods:
extension (self: FlexibleType)
def underlying: TypeRepr
def lo: TypeRepr
def hi: TypeRepr
end extension
end FlexibleTypeMethods

///////////////
// CONSTANTS //
///////////////
Expand Down
3 changes: 3 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ object MiMaFilters {
Build.mimaPreviousDottyVersion -> // Seq.empty, // We should never break backwards compatibility
Seq(
// `ReversedMissingMethodProblem`s are acceptable. See comment in `Breaking changes since last LTS`.
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.FlexibleType"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.FlexibleTypeTypeTest"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.FlexibleTypeMethods"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.isSuperAccessor"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.MethodTypeKind"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"),
Expand Down
7 changes: 7 additions & 0 deletions tests/explicit-nulls/run/tasty-flexible-type.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Inlined(None, Nil, Literal(StringConstant("hello")))
ConstantType(StringConstant("hello"))

Inlined(None, Nil, Apply(Select(Literal(StringConstant("world")), "trim"), Nil))
FlexibleType(TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "String"))

FlexibleType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "java")), "lang"), "String"))
34 changes: 34 additions & 0 deletions tests/explicit-nulls/run/tasty-flexible-type/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import scala.quoted.*

object Macros {

inline def printTree[T](inline x: T): Unit =
${ impl('x) }

def impl[T](x: Expr[T])(using Quotes): Expr[Unit] = {
import quotes.reflect.*

val tree = x.asTerm
val treeStr = Expr(tree.show(using Printer.TreeStructure))
val treeTpeStr = Expr(tree.tpe.show(using Printer.TypeReprStructure))

'{
println(${treeStr})
println(${treeTpeStr})
println()
}
}

inline def theTestBlock: Unit = ${ theTestBlockImpl }

def theTestBlockImpl(using Quotes): Expr[Unit] = {
import quotes.reflect.*

val ft1 = FlexibleType(TypeRepr.of[String])
val ft1e = Expr(ft1.show(using Printer.TypeReprStructure))

'{
println(${ft1e})
}
}
}
10 changes: 10 additions & 0 deletions tests/explicit-nulls/run/tasty-flexible-type/quoted_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import Macros.*

object Test {
def main(args: Array[String]): Unit = {
printTree("hello")
printTree("world".trim())
theTestBlock
}
}

0 comments on commit 6af2bcf

Please sign in to comment.