diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3dcbf2536509..a6136a20cf32 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -77,6 +77,7 @@ object Types extends TypeUtils { * | +- TypeVar * | +- HKTypeLambda * | +- MatchType + * | +- FlexibleType * | * +- GroundType -+- AndType * +- OrType @@ -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 @@ -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() diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 5d51c6e274f7..eb300a0512b5 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -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 @@ -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 diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index eac85244d97b..acf66fcf2009 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -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 = { diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 9aec7fc17ed7..9503177ff738 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1247,6 +1247,11 @@ object SourceCode { this += " => " printType(rhs) + case FlexibleType(tp) => + this += "(" + printType(tp) + this += ")?" + case _ => cannotBeShownAsSource(tpe.show(using Printer.TypeReprStructure)) } diff --git a/docs/_docs/internals/type-system.md b/docs/_docs/internals/type-system.md index d2c0cd869e61..e3f02654953e 100644 --- a/docs/_docs/internals/type-system.md +++ b/docs/_docs/internals/type-system.md @@ -36,6 +36,7 @@ Type -+- ProxyType --+- NamedType ----+--- TypeRef | +- TypeVar | +- HKTypeLambda | +- MatchType + | +- FlexibleType | +- GroundType -+- AndType +- OrType diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index fa194f99143a..d048d8d728d5 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -211,7 +211,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * +- MatchCase * +- TypeBounds * +- NoPrefix - * + * +- FlexibleType + * * +- MethodTypeKind -+- Contextual * +- Implicit * +- Plain @@ -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") @@ -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 // /////////////// diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 5ccb70ad6fdf..85dfc467e3e5 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -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"), diff --git a/tests/explicit-nulls/run/tasty-flexible-type.check b/tests/explicit-nulls/run/tasty-flexible-type.check new file mode 100644 index 000000000000..dcb4db1129d0 --- /dev/null +++ b/tests/explicit-nulls/run/tasty-flexible-type.check @@ -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")) diff --git a/tests/explicit-nulls/run/tasty-flexible-type/quoted_1.scala b/tests/explicit-nulls/run/tasty-flexible-type/quoted_1.scala new file mode 100644 index 000000000000..782eec53602a --- /dev/null +++ b/tests/explicit-nulls/run/tasty-flexible-type/quoted_1.scala @@ -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}) + } + } +} diff --git a/tests/explicit-nulls/run/tasty-flexible-type/quoted_2.scala b/tests/explicit-nulls/run/tasty-flexible-type/quoted_2.scala new file mode 100644 index 000000000000..7b22c541ee58 --- /dev/null +++ b/tests/explicit-nulls/run/tasty-flexible-type/quoted_2.scala @@ -0,0 +1,10 @@ + +import Macros.* + +object Test { + def main(args: Array[String]): Unit = { + printTree("hello") + printTree("world".trim()) + theTestBlock + } +}