Skip to content
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

Fix #20287: Add flexible types to Quotes library #20293

Merged
merged 2 commits into from
May 6, 2024
Merged
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
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)
noti0na1 marked this conversation as resolved.
Show resolved Hide resolved
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
noti0na1 marked this conversation as resolved.
Show resolved Hide resolved

/** `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]
}

noti0na1 marked this conversation as resolved.
Show resolved Hide resolved
/** 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"))
Copy link
Member

@hamzaremmal hamzaremmal May 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmmh, I'm wondering why we don't have the java prefix here for java.lang.String.


We do have it for the second type below 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess because it is from a Java method?


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
}
}
Loading