Skip to content

Keep QuoteContext found in typer for quoted.Type #8949

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

Merged
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
40 changes: 16 additions & 24 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,32 +131,20 @@ class ReifyQuotes extends MacroTransform {
val body1 = nested(isQuote = true).transform(body)(quoteContext)
super.transformQuotation(body1, quote)
}
else body match {
case body: RefTree if isCaptured(body.symbol, level + 1) =>
// Optimization: avoid the full conversion when capturing `x`
// in '{ x } to '{ ${x$1} } and go directly to `x$1`
capturers(body.symbol)(body)
case _ =>
val (body1, splices) = nested(isQuote = true).splitQuote(body)(quoteContext)
if (level == 0) {
val body2 =
if (body1.isType) body1
else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1)
pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span)
}
else
body
else {
val (body1, splices) = nested(isQuote = true).splitQuote(body)(quoteContext)
if (level == 0) {
val body2 =
if (body1.isType) body1
else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1)
pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span)
}
else
body
}
}

private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(implicit ctx: Context) = {
def qctx: Tree = {
val qctx = ctx.typer.inferImplicitArg(defn.QuoteContextClass.typeRef, body.span)
if (qctx.tpe.isInstanceOf[SearchFailureType])
ctx.error(ctx.typer.missingArgMsg(qctx, defn.QuoteContextClass.typeRef, ""), ctx.source.atSpan(body.span))
qctx
}

def pickleAsLiteral(lit: Literal) = {
def liftedValue(lifter: Symbol) =
ref(lifter).appliedToType(originalTp).select(nme.toExpr).appliedTo(lit)
Expand Down Expand Up @@ -185,9 +173,9 @@ class ReifyQuotes extends MacroTransform {
}

if (isType) {
def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName).appliedTo(qctx)
def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName)
if (splices.isEmpty && body.symbol.isPrimitiveValueClass) tag(s"${body.symbol.name}Tag")
else pickleAsTasty().select(nme.apply).appliedTo(qctx)
else pickleAsTasty()
}
else getLiteral(body) match {
case Some(lit) => pickleAsLiteral(lit)
Expand Down Expand Up @@ -362,6 +350,10 @@ class ReifyQuotes extends MacroTransform {
transform(tree)(ctx.withSource(tree.source))
else reporting.trace(i"Reifier.transform $tree at $level", show = true) {
tree match {
case Apply(Select(TypeApply(fn, (body: RefTree) :: Nil), _), _) if fn.symbol == defn.InternalQuoted_typeQuote && isCaptured(body.symbol, level + 1) =>
// Optimization: avoid the full conversion when capturing `x`
// in '{ x } to '{ ${x$1} } and go directly to `x$1`
capturers(body.symbol)(body)
case tree: RefTree if isCaptured(tree.symbol, level) =>
val body = capturers(tree.symbol).apply(tree)
val splice: Tree =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ object Splicer {
}
interpretQuote(quoted1)

case TypeApply(fn, quoted :: Nil) if fn.symbol == defn.InternalQuoted_typeQuote =>
case Apply(Select(TypeApply(fn, quoted :: Nil), _), _) if fn.symbol == defn.InternalQuoted_typeQuote =>
interpretTypeQuote(quoted)

case Literal(Constant(value)) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
}

tree match {
case Apply(Select(Quoted(quotedTree), _), _) if quotedTree.isType =>
Copy link
Contributor

Choose a reason for hiding this comment

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

The pattern does not seem to have enough guards, could it match unintended trees by accident?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The guard are inside the Quoted extractor. Only the compiler generates these trees and the rest of the shape is know. I will do a refactor of the Quoted/Spliced extractor at some point, but I would prefer to do it on it's own PR.

dropEmptyBlocks(quotedTree) match
case Spliced(t) =>
// '[ x.$splice ] --> x
transform(t)
case _ =>
super.transform(tree)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the branch catch the case where the splice is in a big type, such as '{ e: F[$x]}?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes


case Quoted(quotedTree) =>
val old = inQuoteOrSplice
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ trait QuotesAndSplices {
if ctx.mode.is(Mode.Pattern) then
typedQuotePattern(tree, pt, qctx)
else if (tree.quoted.isType)
typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(using quoteContext)
typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(using quoteContext).select(nme.apply).appliedTo(qctx)
else
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(using pushQuoteContext(qctx)).select(nme.apply).appliedTo(qctx)
tree1.withSpan(tree.span)
Expand Down Expand Up @@ -401,7 +401,7 @@ trait QuotesAndSplices {
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
val quotedPattern =
if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx)
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTree(shape)
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTree(shape).select(nme.apply).appliedTo(qctx)
UnApply(
fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil,
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
def quotedType(t: Type) =
if StagingContext.level == 0 then
ctx.compilationUnit.needsStaging = true // We will need to run ReifyQuotes
ref(defn.InternalQuoted_typeQuote).appliedToType(t)
val qctx = ctx.typer.inferImplicitArg(defn.QuoteContextClass.typeRef, span)
qctx.tpe match
case tpe: Implicits.SearchFailureType => ctx.error(tpe.msg, ctx.source.atSpan(span))
case _ =>
ref(defn.InternalQuoted_typeQuote).appliedToType(t).select(nme.apply).appliedTo(qctx)
formal.argInfos match
case arg :: Nil =>
val deepDealias = new TypeMap:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object CompileTime {

/** A type quote is desugared by the compiler into a call to this method */
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.typeQuote`")
def typeQuote[T <: AnyKind]: Type[T] = ???
def typeQuote[T <: AnyKind]: QuoteContext ?=> Type[T] = ???

/** A splice in a quoted pattern is desugared by the compiler into a call to this method */
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternHole`")
Expand Down
18 changes: 9 additions & 9 deletions library/src-bootstrapped/scala/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,31 @@ class Type[T <: AnyKind] private[scala] {
/** Some basic type tags, currently incomplete */
object Type {

given UnitTag(using qctx: QuoteContext) as Type[Unit] =
def UnitTag: QuoteContext ?=> Type[Unit] =
qctx.tasty.defn.UnitType.seal.asInstanceOf[quoted.Type[Unit]]

given BooleanTag(using qctx: QuoteContext) as Type[Boolean] =
def BooleanTag: QuoteContext ?=> Type[Boolean] =
qctx.tasty.defn.BooleanType.seal.asInstanceOf[quoted.Type[Boolean]]

given ByteTag(using qctx: QuoteContext) as Type[Byte] =
def ByteTag: QuoteContext ?=> Type[Byte] =
qctx.tasty.defn.ByteType.seal.asInstanceOf[quoted.Type[Byte]]

given CharTag(using qctx: QuoteContext) as Type[Char] =
def CharTag: QuoteContext ?=> Type[Char] =
qctx.tasty.defn.CharType.seal.asInstanceOf[quoted.Type[Char]]

given ShortTag(using qctx: QuoteContext) as Type[Short] =
def ShortTag: QuoteContext ?=> Type[Short] =
qctx.tasty.defn.ShortType.seal.asInstanceOf[quoted.Type[Short]]

given IntTag(using qctx: QuoteContext) as Type[Int] =
def IntTag: QuoteContext ?=> Type[Int] =
qctx.tasty.defn.IntType.seal.asInstanceOf[quoted.Type[Int]]

given LongTag(using qctx: QuoteContext) as Type[Long] =
def LongTag: QuoteContext ?=> Type[Long] =
qctx.tasty.defn.LongType.seal.asInstanceOf[quoted.Type[Long]]

given FloatTag(using qctx: QuoteContext) as Type[Float] =
def FloatTag: QuoteContext ?=> Type[Float] =
qctx.tasty.defn.FloatType.seal.asInstanceOf[quoted.Type[Float]]

given DoubleTag(using qctx: QuoteContext) as Type[Double] =
def DoubleTag: QuoteContext ?=> Type[Double] =
qctx.tasty.defn.DoubleType.seal.asInstanceOf[quoted.Type[Double]]

}
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/pos-macros/i4023/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
object Test {
def test(using quoted.QuoteContext) = {
Macro.ff(3)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear why using quoted.QuoteContext is necessary for macro usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test is awkward. The inline method is the one that requires the quoted.Type as an implicit. This might only be used to write a macro that is used in a macro implementation, hence the QuoteContext.

Previously it worked because we never used the Type and by chance it stayed encoded as a QuoteContext ?=> Type[T] which was never called. That is why we missed the need for a QuoteContext.

The current approach homogeneously requires the quote context for all Type quotes as we do with Expr quotes. This helps to find out missing QuoteContext early in the pipeline.

1 change: 1 addition & 0 deletions tests/pos-macros/i4023b/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
object Test {
given quoted.QuoteContext = ???
Macro.ff[Int]
}
2 changes: 1 addition & 1 deletion tests/pos/i6588.scala → tests/pos-macros/i6588.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import scala.quoted._

inline def foo[T:Type]: Int = 10

def main = {
def main(using QuoteContext) = {
type S = Int
foo[S]
foo[Int]
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/pos/i8302.scala → tests/pos-macros/i8302.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.quoted._
def foo[T](using qctx: QuoteContext, tpe: Type[T]): Expr[Any] =
'{
'{ (using qctx: QuoteContext) =>
type TT = T
val t = '[TT]
???
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import scala.quoted._


inline def isFunctionType[T:Type]: Boolean = ${ isFunctionTypeImpl('[T]) }
inline def isFunctionType[T]: Boolean = ${ isFunctionTypeImpl('[T]) }
Copy link
Contributor

Choose a reason for hiding this comment

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

This is nice, improves meta-programming experience a lot 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This proves the point made in the previous comment. Here the calls failed to compile and I noticed at once that we had an extra Type that was not needed.


def isFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.tasty._
Expr(tp.unseal.tpe.isFunctionType)
}


inline def isContextFunctionType[T:Type]: Boolean = ${ isContextFunctionTypeImpl('[T]) }
inline def isContextFunctionType[T]: Boolean = ${ isContextFunctionTypeImpl('[T]) }

def isContextFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.tasty._
Expr(tp.unseal.tpe.isContextFunctionType)
}


inline def isErasedFunctionType[T:Type]: Boolean = ${ isErasedFunctionTypeImpl('[T]) }
inline def isErasedFunctionType[T]: Boolean = ${ isErasedFunctionTypeImpl('[T]) }

def isErasedFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.tasty._
Expr(tp.unseal.tpe.isErasedFunctionType)
}

inline def isDependentFunctionType[T:Type]: Boolean = ${ isDependentFunctionTypeImpl('[T]) }
inline def isDependentFunctionType[T]: Boolean = ${ isDependentFunctionTypeImpl('[T]) }

def isDependentFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.tasty._
Expand Down
6 changes: 3 additions & 3 deletions tests/run-staging/quote-nested-4.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
val t: scala.quoted.Type[scala.Predef.String] = scala.internal.quoted.CompileTime.typeQuote[scala.Predef.String]
((qctx: scala.quoted.QuoteContext) ?=> {
val t: scala.quoted.Type[scala.Predef.String] = scala.internal.quoted.CompileTime.typeQuote[scala.Predef.String].apply(using qctx)

(t: scala.quoted.Type[scala.Predef.String])
}
})
2 changes: 1 addition & 1 deletion tests/run-staging/quote-nested-4.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object Test {
given Toolbox = Toolbox.make(getClass.getClassLoader)
def main(args: Array[String]): Unit = withQuoteContext {

val q = '{
val q = '{ (using qctx: QuoteContext) =>
val t = '[String]
t
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-staging/staged-tuples/Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ object Test {
def main(args: Array[String]): Unit = {
implicit val toolbox: scala.quoted.staging.Toolbox = scala.quoted.staging.Toolbox.make(getClass.getClassLoader)

assert(run(fromArrayStaged('{ Array.empty[Object] }, Some(0))).==(()))
assert(run(fromArrayStaged[Unit]('{ Array.empty[Object] }, Some(0))).==(()))
assert(run(fromArrayStaged[Tuple1[String]]('{ Array[Object]("a") }, Some(1))) == Tuple1("a"))
assert(run(fromArrayStaged[(String, String)]('{ Array[Object]("a", "b") }, Some(2))) == ("a", "b"))
assert(run(fromArrayStaged[(String, String, String)]('{ Array[Object]("a", "b", "c") }, Some(3))) == ("a", "b", "c"))
Expand Down