Skip to content

Experiment with static quote scope safety #8940

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
wants to merge 1 commit into from
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
682 changes: 341 additions & 341 deletions .github/workflows/ci.yaml

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Names._, StdNames._, NameOps._, Symbols._
import typer.ConstFold
import reporting.trace
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.transform.TypeUtils._
import Decorators._
import Constants.Constant
import scala.collection.mutable
Expand Down Expand Up @@ -395,7 +396,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case New(_) | Closure(_, _, _) =>
Pure
case TypeApply(fn, _) =>
if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_apply || fn.symbol == defn.Predef_classOf) Pure else exprPurity(fn)
if (fn.symbol.is(Erased) || fn.symbol == defn.ScopeTypeModule_apply || fn.symbol == defn.Predef_classOf) Pure else exprPurity(fn)
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
Expand Down Expand Up @@ -910,7 +911,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
* will return a type tree.
*/
def unapply(tree: tpd.Select)(using Context): Option[tpd.Tree] =
if tree.symbol.isTypeSplice then Some(tree.qualifier) else None
if tree.tpe.isTypeSplice || (tree.qualifier.tpe.widenTermRefExpr.typeSymbol == defn.ScopeTypeClass && tree.name == tpnme.spliceType) then Some(tree.qualifier)
else None
}

/** Extractor for not-null assertions.
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ object Trees {
abstract class Tree[-T >: Untyped](implicit @constructorOnly src: SourceFile)
extends Positioned, SrcPos, Product, Attachment.Container, printing.Showable {

type X <: AnyKind // FIXME used for reflection. find another way to add this type

if (Stats.enabled) ntrees += 1

/** The type constructor at the root of the tree */
Expand Down
39 changes: 17 additions & 22 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -785,22 +785,16 @@ class Definitions {
@tu lazy val ClassTagModule: Symbol = ClassTagClass.companionModule
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)

@tu lazy val QuotedExprModule: Symbol = requiredModule("scala.quoted.Expr")

@tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr")
@tu lazy val QuotedExprModule: Symbol = QuotedExprClass.companionModule

@tu lazy val QuoteContextClass: ClassSymbol = requiredClass("scala.quoted.QuoteContext")
@tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection")

@tu lazy val LiftableModule: Symbol = requiredModule("scala.quoted.Liftable")
@tu lazy val LiftableModule_BooleanLiftable: Symbol = LiftableModule.requiredMethod("BooleanLiftable")
@tu lazy val LiftableModule_ByteLiftable: Symbol = LiftableModule.requiredMethod("ByteLiftable")
@tu lazy val LiftableModule_ShortLiftable: Symbol = LiftableModule.requiredMethod("ShortLiftable")
@tu lazy val LiftableModule_IntLiftable: Symbol = LiftableModule.requiredMethod("IntLiftable")
@tu lazy val LiftableModule_LongLiftable: Symbol = LiftableModule.requiredMethod("LongLiftable")
@tu lazy val LiftableModule_FloatLiftable: Symbol = LiftableModule.requiredMethod("FloatLiftable")
@tu lazy val LiftableModule_DoubleLiftable: Symbol = LiftableModule.requiredMethod("DoubleLiftable")
@tu lazy val LiftableModule_CharLiftable: Symbol = LiftableModule.requiredMethod("CharLiftable")
@tu lazy val LiftableModule_StringLiftable: Symbol = LiftableModule.requiredMethod("StringLiftable")
@tu lazy val ScopeClass: ClassSymbol = requiredClass("scala.quoted.Scope")
@tu lazy val ScopeTypeModule: Symbol = ScopeClass.requiredMethod("Type")
@tu lazy val ScopeTypeModule_apply: Symbol = ScopeTypeModule.requiredMethod(nme.apply)
@tu lazy val ScopeExprClass: Symbol = ScopeClass.typeRef.select(tpnme.Expr).typeSymbol
@tu lazy val ScopeTypeClass: Symbol = ScopeClass.typeRef.select(tpnme.Type).typeSymbol
@tu lazy val Scope_Type_splice: Symbol = ScopeClass.typeRef.select(tpnme.Type).select(tpnme.spliceType).typeSymbol

@tu lazy val InternalQuotedModule: Symbol = requiredModule("scala.internal.quoted.CompileTime")
@tu lazy val InternalQuoted_exprQuote : Symbol = InternalQuotedModule.requiredMethod("exprQuote")
Expand All @@ -819,18 +813,19 @@ class Definitions {
@tu lazy val InternalQuotedExpr_unapply: Symbol = InternalQuotedExprModule.requiredMethod(nme.unapply)
@tu lazy val InternalQuotedExpr_null: Symbol = InternalQuotedExprModule.requiredMethod(nme.null_)
@tu lazy val InternalQuotedExpr_unit: Symbol = InternalQuotedExprModule.requiredMethod(nme.Unit)
@tu lazy val InternalQuotedExpr_liftBoolean: Symbol = InternalQuotedExprModule.requiredMethod("liftBoolean")
@tu lazy val InternalQuotedExpr_liftByte: Symbol = InternalQuotedExprModule.requiredMethod("liftByte")
@tu lazy val InternalQuotedExpr_liftShort: Symbol = InternalQuotedExprModule.requiredMethod("liftShort")
@tu lazy val InternalQuotedExpr_liftInt: Symbol = InternalQuotedExprModule.requiredMethod("liftInt")
@tu lazy val InternalQuotedExpr_liftLong: Symbol = InternalQuotedExprModule.requiredMethod("liftLong")
@tu lazy val InternalQuotedExpr_liftFloat: Symbol = InternalQuotedExprModule.requiredMethod("liftFloat")
@tu lazy val InternalQuotedExpr_liftDouble: Symbol = InternalQuotedExprModule.requiredMethod("liftDouble")
@tu lazy val InternalQuotedExpr_liftChar: Symbol = InternalQuotedExprModule.requiredMethod("liftChar")
@tu lazy val InternalQuotedExpr_liftString: Symbol = InternalQuotedExprModule.requiredMethod("liftString")

@tu lazy val InternalQuotedTypeModule: Symbol = requiredModule("scala.internal.quoted.Type")
@tu lazy val InternalQuotedType_unapply: Symbol = InternalQuotedTypeModule.requiredMethod(nme.unapply)

@tu lazy val QuotedTypeClass: ClassSymbol = requiredClass("scala.quoted.Type")
@tu lazy val QuotedType_splice: Symbol = QuotedTypeClass.requiredType(tpnme.spliceType)

@tu lazy val QuotedTypeModule: Symbol = QuotedTypeClass.companionModule
@tu lazy val QuotedTypeModule_apply: Symbol = QuotedTypeModule.requiredMethod("apply")

@tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection")

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

Expand Down
26 changes: 16 additions & 10 deletions compiler/src/dotty/tools/dotc/core/StagingContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ object StagingContext {
private val QuotationLevel = new Property.Key[Int]

/** A key to be used in a context property that tracks the quoteation stack.
* Stack containing the QuoteContext references recieved by the surrounding quotes.
* Stack containing the Scope references recieved by the surrounding quotes.
*/
private val QuoteContextStack = new Property.Key[List[tpd.Tree]]
private val ScopeStack = new Property.Key[List[tpd.Tree]]

private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags]

Expand All @@ -31,11 +31,11 @@ object StagingContext {
def quoteContext(using Context): Context =
ctx.fresh.setProperty(QuotationLevel, level + 1)

/** Context with an incremented quotation level and pushes a refecence to a QuoteContext on the quote context stack */
def pushQuoteContext(qctxRef: tpd.Tree)(using Context): Context =
val old = ctx.property(QuoteContextStack).getOrElse(List.empty)
/** Context with an incremented quotation level and pushes a refecence to a Scope on the quote scope stack */
def pushScope(scopeRef: tpd.Tree)(using Context): Context =
val old = ctx.property(ScopeStack).getOrElse(List.empty)
ctx.fresh.setProperty(QuotationLevel, level + 1)
.setProperty(QuoteContextStack, qctxRef :: old)
.setProperty(ScopeStack, scopeRef :: old)

/** Context with a decremented quotation level. */
def spliceContext(using Context): Context =
Expand All @@ -47,17 +47,23 @@ object StagingContext {
def getQuoteTypeTags(using Context): PCPCheckAndHeal.QuoteTypeTags =
ctx.property(TaggedTypes).get

/** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty.
/** Context with a decremented quotation level and pops the Some of top of the quote scope stack or None if the stack is empty.
* The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice.
*/
def popQuoteContext()(using Context): (Option[tpd.Tree], Context) =
def popScope()(using Context): (Option[tpd.Tree], Context) =
val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1)
val head =
ctx.property(QuoteContextStack) match
ctx.property(ScopeStack) match
case Some(x :: xs) =>
ctx1.setProperty(QuoteContextStack, xs)
ctx1.setProperty(ScopeStack, xs)
Some(x)
case _ =>
None // Splice at level 0 or lower
(head, ctx1)

def peekScope()(implicit ctx: Context): Option[tpd.Tree] =
ctx.property(ScopeStack) match
case Some(x :: xs) => Some(x)
case _ => None // Splice at level 0 or lower

}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ object StdNames {
val setSymbol: N = "setSymbol"
val setType: N = "setType"
val setTypeSignature: N = "setTypeSignature"
val spliceType: N = "T"
val spliceType: N = "X"
val standardInterpolator: N = "standardInterpolator"
val staticClass : N = "staticClass"
val staticModule : N = "staticModule"
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ object Types {
*/
abstract class Type extends Hashable with printing.Showable {

type X <: AnyKind // FIXME used for reflection. find another way to add this type

// ----- Tests -----------------------------------------------------

// // debug only: a unique identifier for a type
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ class TreePickler(pickler: TastyPickler) {

def pickle(trees: List[Tree])(using Context): Unit = {
trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree))
def missing = forwardSymRefs.keysIterator.map(sym => sym.showLocated + "(line " + sym.srcPos.line + ")").toList
def missing = forwardSymRefs.keysIterator.map(sym => sym.showLocated + (if sym.sourcePos.exists then "(line " + sym.srcPos.line + ")" else "")).toList
assert(forwardSymRefs.isEmpty, i"unresolved symbols: $missing%, % when pickling ${ctx.source}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import scala.io.Codec
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.tasty.TastyPrinter
import dotty.tools.dotc.quoted.QuoteContextImpl
import dotty.tools.dotc.quoted.ScopeImpl
import dotty.tools.io.File

/** Phase that prints the trees in all loaded compilation units.
Expand Down Expand Up @@ -44,7 +44,7 @@ class DecompilationPrinter extends Phase {
else {
val unitFile = unit.source.toString.replace("\\", "/").replace(".class", ".tasty")
out.println(s"/** Decompiled from $unitFile */")
out.println(QuoteContextImpl.showTree(unit.tpdTree))
out.println(ScopeImpl.showTree(unit.tpdTree))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core._
import dotty.tools.dotc.core.tasty.TastyHTMLPrinter
import dotty.tools.dotc.reporting._
import dotty.tools.dotc.quoted.QuoteContextImpl
import dotty.tools.dotc.quoted.ScopeImpl

/**
* Decompiler to be used with IDEs
Expand Down Expand Up @@ -34,7 +34,7 @@ class IDEDecompilerDriver(val settings: List[String]) extends dotc.Driver {
run.printSummary()
val unit = ctx.run.units.head

val decompiled = QuoteContextImpl.showTree(unit.tpdTree)
val decompiled = ScopeImpl.showTree(unit.tpdTree)
val tree = new TastyHTMLPrinter(unit.pickled.head._2()).printContents()

reporter.removeBufferedMessages.foreach(message => System.err.println(message))
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
else if (name.isTypeName) typeText(txt)
else txt
case tree @ Select(qual, name) =>
if (!printDebug && tree.hasType && tree.symbol.isTypeSplice) typeText("${") ~ toTextLocal(qual) ~ typeText("}")
if (!printDebug && tree.hasType && tree.tpe.asInstanceOf[Type].isTypeSplice) typeText("${") ~ toTextLocal(qual) ~ typeText("}")
else if (qual.isType) toTextLocal(qual) ~ "#" ~ typeText(toText(name))
else toTextLocal(qual) ~ ("." ~ nameIdText(tree) provided (name != nme.CONSTRUCTOR || printDebug))
case tree: This =>
Expand Down Expand Up @@ -632,11 +632,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}
case Number(digits, kind) =>
digits
case Quote(tree) if tree.isTerm =>
case Quote(tree) if !printDebug && tree.isTerm =>
keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
case Splice(tree) =>
case Splice(tree) if !printDebug =>
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
case TypSplice(tree) =>
case TypSplice(tree) if !printDebug =>
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
case tree: Applications.IntegratedTypeArgs =>
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
Expand Down
38 changes: 13 additions & 25 deletions compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ import dotty.tools.dotc.core.tasty.TreePickler.Hole
import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter }
import dotty.tools.dotc.core.tasty.DottyUnpickler
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
import dotty.tools.dotc.quoted.ScopeImpl
import dotty.tools.dotc.report

import dotty.tools.tasty.TastyString

import scala.reflect.ClassTag

import scala.internal.quoted.Unpickler._
import scala.quoted.QuoteContext
import scala.collection.mutable
import scala.quoted.Scope

object PickledQuotes {
import tpd._
Expand All @@ -32,25 +33,12 @@ object PickledQuotes {
def pickleQuote(tree: Tree)(using Context): PickledQuote =
if (ctx.reporter.hasErrors) Nil
else {
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
// FIXME re-enable
// assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
val pickled = pickle(tree)
TastyString.pickle(pickled)
}

/** Transform the expression into its fully spliced Tree */
def quotedExprToTree[T](expr: quoted.Expr[T])(using Context): Tree = {
val expr1 = expr.asInstanceOf[scala.internal.quoted.Expr[Tree]]
QuoteContextImpl.checkScopeId(expr1.scopeId)
healOwner(expr1.tree)
}

/** Transform the expression into its fully spliced TypeTree */
def quotedTypeToTree(tpe: quoted.Type[?])(using Context): Tree = {
val tpe1 = tpe.asInstanceOf[scala.internal.quoted.Type[Tree]]
QuoteContextImpl.checkScopeId(tpe1.scopeId)
healOwner(tpe1.typeTree)
}

/** Unpickle the tree contained in the TastyExpr */
def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(using Context): Tree = {
val tastyBytes = TastyString.unpickle(tasty)
Expand All @@ -77,13 +65,13 @@ object PickledQuotes {
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
case Hole(isTerm, idx, args) =>
val reifiedArgs = args.map { arg =>
if (arg.isTerm) (using qctx: QuoteContext) => new scala.internal.quoted.Expr(arg, QuoteContextImpl.scopeId)
else new scala.internal.quoted.Type(arg, QuoteContextImpl.scopeId)
if (arg.isTerm) (using scope: Scope) => arg
else arg
}
if isTerm then
val splice1 = splices(idx).asInstanceOf[Seq[Any] => QuoteContext ?=> quoted.Expr[?]]
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContextImpl())
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
val splice1 = splices(idx).asInstanceOf[Seq[Any] => scala.quoted.Scope ?=> Tree]
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.ScopeImpl())
val filled = PickledQuotes.healOwner(quotedExpr)

// We need to make sure a hole is created with the source file of the surrounding context, even if
// it filled with contents a different source file.
Expand All @@ -92,8 +80,8 @@ object PickledQuotes {
else
// Replaces type holes generated by ReifyQuotes (non-spliced types).
// These are types defined in a quote and used at the same level in a nested quote.
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs)
PickledQuotes.quotedTypeToTree(quotedType)
val quotedType = splices(idx).asInstanceOf[Seq[Any] => Tree](reifiedArgs)
PickledQuotes.healOwner(quotedType)
case tree: Select =>
// Retain selected members
val qual = transform(tree.qualifier)
Expand Down Expand Up @@ -135,8 +123,8 @@ object PickledQuotes {
assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
val tree = tdef.rhs match
case TypeBoundsTree(_, Hole(_, idx, args), _) =>
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args)
PickledQuotes.quotedTypeToTree(quotedType)
val quotedType = splices(idx).asInstanceOf[Seq[Any] => Tree](args)
PickledQuotes.healOwner(quotedType)
case TypeBoundsTree(_, tpt, _) =>
tpt
(tdef.symbol, tree.tpe)
Expand Down
Loading