diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala deleted file mode 100644 index 41e77655d5d6..000000000000 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ /dev/null @@ -1,58 +0,0 @@ -package dotty.tools.dotc.core - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.util.Property -import dotty.tools.dotc.transform.PCPCheckAndHeal - -object StagingContext { - - /** A key to be used in a context property that tracks the quotation level */ - private val QuotationLevel = new Property.Key[Int] - - /** A key to be used in a context property that tracks the quotation stack. - * Stack containing the Quotes references received by the surrounding quotes. - */ - private val QuotesStack = new Property.Key[List[tpd.Tree]] - - private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags] - - /** All enclosing calls that are currently inlined, from innermost to outermost. */ - def level(using Context): Int = - ctx.property(QuotationLevel).getOrElse(0) - - /** Context with an incremented quotation level. */ - def quoteContext(using Context): Context = - ctx.fresh.setProperty(QuotationLevel, level + 1) - - /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ - def pushQuotes(qctxRef: tpd.Tree)(using Context): Context = - val old = ctx.property(QuotesStack).getOrElse(List.empty) - ctx.fresh.setProperty(QuotationLevel, level + 1) - .setProperty(QuotesStack, qctxRef :: old) - - /** Context with a decremented quotation level. */ - def spliceContext(using Context): Context = - ctx.fresh.setProperty(QuotationLevel, level - 1) - - def contextWithQuoteTypeTags(taggedTypes: PCPCheckAndHeal.QuoteTypeTags)(using Context) = - ctx.fresh.setProperty(TaggedTypes, taggedTypes) - - 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. - * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. - */ - def popQuotes()(using Context): (Option[tpd.Tree], Context) = - val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1) - val head = - ctx.property(QuotesStack) match - case Some(x :: xs) => - ctx1.setProperty(QuotesStack, xs) - Some(x) - case _ => - None // Splice at level 0 or lower - (head, ctx1) -} diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index f2a0d5f03e38..872dc7793ff4 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -23,6 +23,7 @@ import util.Spans.Span import dotty.tools.dotc.transform.Splicer import dotty.tools.dotc.transform.BetaReduce import quoted.QuoteUtils +import staging.StagingLevel import scala.annotation.constructorOnly /** General support for inlining */ @@ -814,7 +815,7 @@ class Inliner(val call: tpd.Tree)(using Context): val locked = ctx.typerState.ownedVars val res = cancelQuotes(constToLiteral(BetaReduce(super.typedApply(tree, pt)))) match { case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice - && StagingContext.level == 0 + && StagingLevel.level == 0 && !hasInliningErrors => val expanded = expandMacro(res.args.head, tree.srcPos) transform.TreeChecker.checkMacroGeneratedTree(res, expanded) @@ -1026,7 +1027,7 @@ class Inliner(val call: tpd.Tree)(using Context): } private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { - assert(StagingContext.level == 0) + assert(StagingLevel.level == 0) val inlinedFrom = enclosingInlineds.last val dependencies = macroDependencies(body) val suspendable = ctx.compilationUnit.isSuspendable diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 8110fd2de195..f504a4034631 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -14,6 +14,7 @@ import ErrorReporting.errorTree import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} +import staging.StagingLevel import collection.mutable import reporting.trace @@ -56,7 +57,7 @@ object Inlines: case _ => isInlineable(tree.symbol) && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] - && StagingContext.level == 0 + && StagingLevel.level == 0 && ( ctx.phase == Phases.inliningPhase || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 85293d4a82d7..eb1a97ab93c0 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -17,11 +17,12 @@ import NameKinds.{InlineAccessorName, UniqueInlineName} import inlines.Inlines import NameOps._ import Annotations._ -import transform.{AccessProxies, PCPCheckAndHeal, Splicer} +import transform.{AccessProxies, Splicer} +import staging.PCPCheckAndHeal import transform.SymUtils.* import config.Printers.inlining import util.Property -import dotty.tools.dotc.transform.TreeMapWithStages._ +import staging.StagingLevel object PrepareInlineable { import tpd._ @@ -73,7 +74,7 @@ object PrepareInlineable { !sym.isContainedIn(inlineSym) && !(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) && !sym.isInlineMethod && - (Inlines.inInlineMethod || StagingContext.level > 0) + (Inlines.inInlineMethod || StagingLevel.level > 0) def preTransform(tree: Tree)(using Context): Tree @@ -90,8 +91,8 @@ object PrepareInlineable { } private def stagingContext(tree: Tree)(using Context): Context = tree match - case tree: Apply if tree.symbol.isQuote => StagingContext.quoteContext - case tree: Apply if tree.symbol.isExprSplice => StagingContext.spliceContext + case tree: Apply if tree.symbol.isQuote => StagingLevel.quoteContext + case tree: Apply if tree.symbol.isExprSplice => StagingLevel.spliceContext case _ => ctx } @@ -293,7 +294,7 @@ object PrepareInlineable { if (code.symbol.flags.is(Inline)) report.error("Macro cannot be implemented with an `inline` method", code.srcPos) Splicer.checkValidMacroBody(code) - new PCPCheckAndHeal(freshStagingContext).transform(body) // Ignore output, only check PCP + (new PCPCheckAndHeal).transform(body) // Ignore output, only check PCP case Block(List(stat), Literal(Constants.Constant(()))) => checkMacro(stat) case Block(Nil, expr) => checkMacro(expr) case Typed(expr, _) => checkMacro(expr) diff --git a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala index aadfedd2417c..a98284f4078d 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala @@ -19,13 +19,12 @@ import dotty.tools.dotc.core.Denotations.staticRef import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds.FlatName import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.TypeErasure import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.typer.ImportInfo.withRootImports import dotty.tools.dotc.util.SrcPos import dotty.tools.dotc.reporting.Message diff --git a/compiler/src/dotty/tools/dotc/staging/HealType.scala b/compiler/src/dotty/tools/dotc/staging/HealType.scala new file mode 100644 index 000000000000..44843dfa91ca --- /dev/null +++ b/compiler/src/dotty/tools/dotc/staging/HealType.scala @@ -0,0 +1,99 @@ +package dotty.tools.dotc +package staging + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.staging.QuoteTypeTags.* +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.Implicits.SearchFailureType +import dotty.tools.dotc.util.SrcPos + +class HealType(pos: SrcPos)(using Context) extends TypeMap { + + /** If the type refers to a locally defined symbol (either directly, or in a pickled type), + * check that its staging level matches the current level. + * - Static types and term are allowed at any level. + * - If a type reference is used a higher level, then it is inconsistent. + * Will attempt to heal before failing. + * - If a term reference is used a higher level, then it is inconsistent. + * It cannot be healed because the term will not exist in any future stage. + * + * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with + * a type tag of type `quoted.Type[T]`. + * The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit + * or indirectly by `tryHeal`. + */ + def apply(tp: Type): Type = + tp match + case tp: TypeRef => + healTypeRef(tp) + case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) => + levelError(tp.symbol, tp, pos) + case tp: AnnotatedType => + derivedAnnotatedType(tp, apply(tp.parent), tp.annot) + case _ => + mapOver(tp) + + private def healTypeRef(tp: TypeRef): Type = + tp.prefix match + case prefix: TermRef if tp.symbol.isTypeSplice => + checkNotWildcardSplice(tp) + if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix) + case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) => + dealiasAndTryHeal(prefix.symbol, tp, pos) + case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + dealiasAndTryHeal(tp.symbol, tp, pos) + case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic => + dealiasAndTryHeal(tp.symbol, tp, pos) + case _ => + mapOver(tp) + + private def checkNotWildcardSplice(splice: TypeRef): Unit = + splice.prefix.termSymbol.info.argInfos match + case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos) + case _ => + + private def dealiasAndTryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos): Type = + val tp1 = tp.dealias + if tp1 != tp then apply(tp1) + else tryHeal(tp.symbol, tp, pos) + + /** Try to heal reference to type `T` used in a higher level than its definition. + * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a + * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`. + * Emits an error if `T` cannot be healed and returns `T`. + */ + protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos): TypeRef = { + val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp) + val tag = ctx.typer.inferImplicitArg(reqType, pos.span) + tag.tpe match + case tp: TermRef => + ctx.typer.checkStable(tp, pos, "type witness") + getQuoteTypeTags.getTagRef(tp) + case _: SearchFailureType => + report.error( + ctx.typer.missingArgMsg(tag, reqType, "") + .prepend(i"Reference to $tp within quotes requires a given $reqType in scope.\n") + .append("\n"), + pos) + tp + case _ => + report.error(em"""Reference to $tp within quotes requires a given $reqType in scope. + | + |""", pos) + tp + } + + private def levelError(sym: Symbol, tp: Type, pos: SrcPos): tp.type = { + report.error( + em"""access to $sym from wrong staging level: + | - the definition is at level ${levelOf(sym)}, + | - but the access is at level $level""", pos) + tp + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/staging/PCPCheckAndHeal.scala similarity index 56% rename from compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala rename to compiler/src/dotty/tools/dotc/staging/PCPCheckAndHeal.scala index 6d3b62357d7f..2a1371d8eb0d 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/staging/PCPCheckAndHeal.scala @@ -1,26 +1,21 @@ package dotty.tools.dotc -package transform +package staging import dotty.tools.dotc.ast.{tpd, untpd} -import dotty.tools.dotc.core.Annotations.BodyAnnotation +import dotty.tools.dotc.core.Annotations._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.util.SrcPos -import dotty.tools.dotc.util.Spans._ -import dotty.tools.dotc.transform.SymUtils._ -import dotty.tools.dotc.typer.Checking -import dotty.tools.dotc.typer.Implicits.SearchFailureType -import dotty.tools.dotc.core.Annotations._ - +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.staging.QuoteTypeTags.* import dotty.tools.dotc.util.Property - -import scala.annotation.constructorOnly +import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.util.SrcPos /** Checks that the Phase Consistency Principle (PCP) holds and heals types. * @@ -48,7 +43,7 @@ import scala.annotation.constructorOnly * } * */ -class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) with Checking { +class PCPCheckAndHeal extends TreeMapWithStages { import tpd._ private val InAnnotation = Property.Key[Unit]() @@ -60,25 +55,31 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( checkAnnotations(tree) super.transform(tree) else tree match { - - case _: TypeTree | _: RefTree if tree.isType => + case _: TypeTree => + val tp1 = transformTypeAnnotationSplices(tree.tpe) + val healedType = healType(tree.srcPos)(tp1) + if healedType == tree.tpe then tree + else TypeTree(healedType).withSpan(tree.span) + case _: RefTree if tree.isType => val healedType = healType(tree.srcPos)(tree.tpe) if healedType == tree.tpe then tree else TypeTree(healedType).withSpan(tree.span) + case tree: Ident if isWildcardArg(tree) => + tree.withType(healType(tree.srcPos)(tree.tpe)) + case tree: Ident => // this is a term Ident + checkLevelConsistency(tree) + tree + case tree: This => + checkLevelConsistency(tree) + tree case _: AppliedTypeTree => super.transform(tree) match case tree1: AppliedTypeTree if tree1 ne tree => // propagate healed types tree1.withType(tree1.tpt.tpe.appliedTo(tree1.args.map(_.tpe))) case tree1 => tree1 - - case _: Ident | _: This => - tree.withType(healTypeOfTerm(tree.srcPos)(tree.tpe)) - - // Remove inline defs in quoted code. Already fully inlined. case tree: DefDef if tree.symbol.is(Inline) && level > 0 => - EmptyTree - + EmptyTree // Remove inline defs in quoted code. Already fully inlined. case tree: ValOrDefDef => checkAnnotations(tree) healInfo(tree, tree.tpt.srcPos) @@ -88,7 +89,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( healInfo(tree, tree.srcPos) super.transform(tree) case tree: UnApply => - super.transform(tree).withType(healTypeOfTerm(tree.srcPos)(tree.tpe)) + super.transform(tree).withType(healType(tree.srcPos)(tree.tpe)) case tree: TypeDef if tree.symbol.is(Case) && level > 0 => report.error(reporting.CaseClassInInlinedCode(tree), tree) super.transform(tree) @@ -98,11 +99,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( /** Transform quoted trees while maintaining phase correctness */ override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = { - val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span) + val taggedTypes = new QuoteTypeTags(quote.span) if (ctx.property(InAnnotation).isDefined) report.error("Cannot have a quote in an annotation", quote.srcPos) + val stripAnnotsDeep: TypeMap = new TypeMap: + def apply(tp: Type): Type = mapOver(tp.stripAnnots) + val contextWithQuote = if level == 0 then contextWithQuoteTypeTags(taggedTypes)(using quoteContext) else quoteContext @@ -115,7 +119,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( if body.isTerm then // `quoted.runtime.Expr.quote[T]()` --> `quoted.runtime.Expr.quote[T2]()` val TypeApply(fun, targs) = quote.fun: @unchecked - val targs2 = targs.map(targ => TypeTree(healTypeOfTerm(quote.fun.srcPos)(targ.tpe))) + val targs2 = targs.map(targ => TypeTree(healType(quote.fun.srcPos)(stripAnnotsDeep(targ.tpe)))) cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, targs2), body2 :: Nil) else val quotes = quote.args.mapConserve(transform) @@ -142,11 +146,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( // `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)` val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr) cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil) - case f @ Apply(fun @ TypeApply(_, _), qctx :: Nil) => + case f @ Apply(fun @ TypeApply(_, _), quotes :: Nil) => // Type of the splice itself must also be healed // `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)` val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr) - cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), qctx :: Nil), body1 :: Nil) + cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), quotes :: Nil), body1 :: Nil) } } @@ -159,6 +163,15 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( ref(tagRef).withSpan(splice.span) } + def transformTypeAnnotationSplices(tp: Type)(using Context) = new TypeMap { + def apply(tp: Type): Type = tp match + case tp: AnnotatedType => + val newAnnotTree = transform(tp.annot.tree) + derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree)) + case _ => + mapOver(tp) + }.apply(tp) + /** Check that annotations do not contain quotes and and that splices are valid */ private def checkAnnotations(tree: Tree)(using Context): Unit = tree match @@ -186,91 +199,25 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( * The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit * or indirectly by `tryHeal`. */ - private def healType(pos: SrcPos)(using Context) = new TypeMap { - def apply(tp: Type): Type = - tp match - case tp: TypeRef => - tp.prefix match - case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - tryHealTypeOfType(tp.symbol, tp, pos) - case prefix: ThisType if !tp.symbol.isStatic && level > levelOf(prefix.cls) => - tryHealTypeOfType(tp.symbol, tp, pos) - case prefix: TermRef if tp.symbol.isTypeSplice => - prefix.symbol.info.argInfos match - case (tb: TypeBounds) :: _ => - report.error(em"Cannot splice $tp because it is a wildcard type", pos) - case _ => - // Heal explicit type splice in the code - if level > 0 then getQuoteTypeTags.getTagRef(prefix) else tp - case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) => - tryHealTypeOfType(prefix.symbol, tp, pos) - case _ => - mapOver(tp) - case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) => - levelError(tp.symbol, tp, pos) - case tp: ThisType if level != -1 && level != levelOf(tp.cls) => - levelError(tp.cls, tp, pos) - case tp: AnnotatedType => - val newAnnotTree = transform(tp.annot.tree) - derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree)) - case _ => - mapOver(tp) - - /** Try to dealias or heal reference to type `T` used in a higher level than its definition. - * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a - * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`. - * Emits and error if `T` cannot be healed and returns `T`. - */ - private def tryHealTypeOfType(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): Type = { - val tp1 = tp.dealias - if tp1 != tp then apply(tp1) - else tryHeal(tp.symbol, tp, pos) - } - } - - /** Check phase consistency of terms and heal inconsistent type references. */ - private def healTypeOfTerm(pos: SrcPos)(using Context) = new TypeMap { - def apply(tp: Type): Type = - tp match - case tp @ TypeRef(NoPrefix, _) if level > levelOf(tp.symbol) => - tryHeal(tp.symbol, tp, pos) - case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) => - levelError(tp.symbol, tp, pos) - case tp: ThisType if level != -1 && level != levelOf(tp.cls) => - levelError(tp.cls, tp, pos) - case tp: AnnotatedType => - derivedAnnotatedType(tp, apply(tp.parent), tp.annot) - case _ => - if tp.typeSymbol.is(Package) then tp - else mapOver(tp) - } - - /** Try to heal reference to type `T` used in a higher level than its definition. - * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a - * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`. - * Emits and error if `T` cannot be healed and returns `T`. - */ - protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): TypeRef = { - val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp) - val tag = ctx.typer.inferImplicitArg(reqType, pos.span) - tag.tpe match - - case tp: TermRef => - checkStable(tp, pos, "type witness") - getQuoteTypeTags.getTagRef(tp) - case _: SearchFailureType => - report.error( - ctx.typer.missingArgMsg(tag, reqType, "") - .prepend(i"Reference to $tp within quotes requires a given $reqType in scope.\n") - .append("\n"), - pos) - tp - case _ => - report.error(em"""Reference to $tp within quotes requires a given $reqType in scope. - | - |""", pos) - tp - } + protected def healType(pos: SrcPos)(using Context) = + new HealType(pos) + + /** Check level consistency of terms references */ + private def checkLevelConsistency(tree: Ident | This)(using Context): Unit = + new TypeTraverser { + def traverse(tp: Type): Unit = + tp match + case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) => + levelError(tp.symbol, tp, tree.srcPos) + case tp: ThisType if level != -1 && level != levelOf(tp.cls) => + levelError(tp.cls, tp, tree.srcPos) + case tp: AnnotatedType => + traverse(tp.parent) + case _ if tp.typeSymbol.is(Package) => + // OK + case _ => + traverseChildren(tp) + }.traverse(tree.tpe) private def levelError(sym: Symbol, tp: Type, pos: SrcPos)(using Context): tp.type = { def symStr = @@ -289,37 +236,4 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( | - but the access is at level $level.$hint""", pos) tp } - -} - -object PCPCheckAndHeal { - import tpd._ - - class QuoteTypeTags(span: Span)(using Context) { - - private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef] - - def getTagRef(spliced: TermRef): TypeRef = { - val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced)) - typeDef.symbol.typeRef - } - - def getTypeTags: List[TypeDef] = tags.valuesIterator.toList - - private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { - val splicedTree = tpd.ref(spliced).withSpan(span) - val rhs = splicedTree.select(tpnme.Underlying).withSpan(span) - val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) - val local = newSymbol( - owner = ctx.owner, - name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, - flags = Synthetic, - info = TypeAlias(splicedTree.tpe.select(tpnme.Underlying)), - coord = span).asType - local.addAnnotation(Annotation(defn.QuotedRuntime_SplicedTypeAnnot, span)) - ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) - } - - } - } diff --git a/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala b/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala new file mode 100644 index 000000000000..542e8ecc2b9f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala @@ -0,0 +1,35 @@ +package dotty.tools.dotc.staging + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.staging.PCPCheckAndHeal +import dotty.tools.dotc.staging.StagingLevel.* + +object QuoteContext { + + /** A key to be used in a context property that tracks the quotation stack. + * Stack containing the Quotes references received by the surrounding quotes. + */ + private val QuotesStack = new Property.Key[List[tpd.Tree]] + + /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ + def pushQuotes(quotes: tpd.Tree)(using Context): Context = + val old = ctx.property(QuotesStack).getOrElse(List.empty) + quoteContext.setProperty(QuotesStack, quotes :: old) + + /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. + * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. + */ + def popQuotes()(using Context): (Option[tpd.Tree], Context) = + val ctx1 = spliceContext + val head = + ctx.property(QuotesStack) match + case Some(x :: xs) => + ctx1.setProperty(QuotesStack, xs) + Some(x) + case _ => + None // Splice at level 0 or lower + (head, ctx1) +} diff --git a/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala b/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala new file mode 100644 index 000000000000..c4c9c611be3a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala @@ -0,0 +1,52 @@ +package dotty.tools.dotc.staging + +import dotty.tools.dotc.ast.{tpd, untpd} +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.util.Spans._ + +object QuoteTypeTags { + + private val TaggedTypes = new Property.Key[QuoteTypeTags] + + def contextWithQuoteTypeTags(taggedTypes: QuoteTypeTags)(using Context) = + ctx.fresh.setProperty(TaggedTypes, taggedTypes) + + def getQuoteTypeTags(using Context): QuoteTypeTags = + ctx.property(TaggedTypes).get +} + +class QuoteTypeTags(span: Span)(using Context) { + import tpd.* + + private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef] + + def getTagRef(spliced: TermRef): TypeRef = { + val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced)) + typeDef.symbol.typeRef + } + + def getTypeTags: List[TypeDef] = tags.valuesIterator.toList + + private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { + val splicedTree = tpd.ref(spliced).withSpan(span) + val rhs = splicedTree.select(tpnme.Underlying).withSpan(span) + val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) + val local = newSymbol( + owner = ctx.owner, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, + flags = Synthetic, + info = TypeAlias(splicedTree.tpe.select(tpnme.Underlying)), + coord = span).asType + local.addAnnotation(Annotation(defn.QuotedRuntime_SplicedTypeAnnot, span)) + ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) + } +} diff --git a/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala b/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala new file mode 100644 index 000000000000..4704501e38ff --- /dev/null +++ b/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package staging + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.util.SrcPos + +import scala.collection.mutable + +object StagingLevel { + + /** A key to be used in a context property that tracks the staging level */ + private val LevelKey = new Property.Key[Int] + + /** A key to be used in a context property that caches the `levelOf` mapping */ + private val LevelOfKey = new Property.Key[Map[Symbol, Int]] + + /** All enclosing calls that are currently inlined, from innermost to outermost. */ + def level(using Context): Int = + ctx.property(LevelKey).getOrElse(0) + + /** Context with an incremented staging level. */ + def quoteContext(using Context): FreshContext = + ctx.fresh.setProperty(LevelKey, level + 1) + + /** Context with a decremented staging level. */ + def spliceContext(using Context): FreshContext = + ctx.fresh.setProperty(LevelKey, level - 1) + + /** The quotation level of the definition of the locally defined symbol */ + def levelOf(sym: Symbol)(using Context): Int = + ctx.property(LevelOfKey) match + case Some(map) => map.getOrElse(sym, 0) + case None => 0 + + /** Context with the current staging level set for the symbols */ + def symbolsInCurrentLevel(syms: List[Symbol])(using Context): Context = + if level == 0 then ctx + else + val levelOfMap = ctx.property(LevelOfKey).getOrElse(Map.empty) + val syms1 = syms//.filter(sym => !levelOfMap.contains(sym)) + val newMap = syms1.foldLeft(levelOfMap)((acc, sym) => acc.updated(sym, level)) + ctx.fresh.setProperty(LevelOfKey, newMap) +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala similarity index 56% rename from compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala rename to compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala index b514b8a7bf11..adaacafa764a 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala @@ -1,58 +1,26 @@ package dotty.tools.dotc -package transform +package staging import dotty.tools.dotc.ast.{TreeMapWithImplicits, tpd} import dotty.tools.dotc.config.Printers.staging import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.util.Property +import dotty.tools.dotc.staging.StagingLevel.* import scala.collection.mutable -import scala.annotation.constructorOnly - -/** The main transformer class - * @param level the current level, where quotes add one and splices subtract one level. - * The initial level is 0, a level `l` where `l > 0` implies code has been quoted `l` times - * and `l == -1` is code inside a top level splice (in an inline method). - * @param levels a stacked map from symbols to the levels in which they were defined - */ -abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMapWithImplicits { +/** TreeMap that keeps track of staging levels using StagingLevel. */ +abstract class TreeMapWithStages extends TreeMapWithImplicits { import tpd._ - import TreeMapWithStages._ - - /** A map from locally defined symbols to their definition quotation level */ - private[this] val levelOfMap: mutable.HashMap[Symbol, Int] = ictx.property(LevelOfKey).get - - /** A stack of entered symbols, to be unwound after scope exit */ - private[this] var enteredSyms: List[Symbol] = Nil /** If we are inside a quote or a splice */ private[this] var inQuoteOrSplice = false - /** The quotation level of the definition of the locally defined symbol */ - protected def levelOf(sym: Symbol): Int = levelOfMap.getOrElse(sym, 0) - - /** Locally defined symbols seen so far by `StagingTransformer.transform` */ - protected def localSymbols: List[Symbol] = enteredSyms - /** If we are inside a quote or a splice */ protected def isInQuoteOrSplice: Boolean = inQuoteOrSplice - /** Enter staging level of symbol defined by `tree` */ - private def markSymbol(sym: Symbol)(using Context): Unit = - if level != 0 && !levelOfMap.contains(sym) then - levelOfMap(sym) = level - enteredSyms = sym :: enteredSyms - - /** Enter staging level of symbol defined by `tree`, if applicable. */ - private def markDef(tree: Tree)(using Context): Unit = tree match { - case tree: DefTree => markSymbol(tree.symbol) - case _ => - } - /** Transform the quote `quote` which contains the quoted `body`. * * - `quoted.runtime.Expr.quote[T]()` --> `quoted.runtime.Expr.quote[T]()` @@ -75,14 +43,6 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap if (tree.source != ctx.source && tree.source.exists) transform(tree)(using ctx.withSource(tree.source)) else reporting.trace(i"StagingTransformer.transform $tree at $level", staging, show = true) { - def mapOverTree(lastEntered: List[Symbol]) = - try super.transform(tree) - finally - while (enteredSyms ne lastEntered) { - levelOfMap -= enteredSyms.head - enteredSyms = enteredSyms.tail - } - def dropEmptyBlocks(tree: Tree): Tree = tree match { case Block(Nil, expr) => dropEmptyBlocks(expr) case _ => tree @@ -127,38 +87,30 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap finally inQuoteOrSplice = old case Block(stats, _) => - val last = enteredSyms - stats.foreach(markDef) - mapOverTree(last) + val defSyms = stats.collect { case defTree: DefTree => defTree.symbol } + super.transform(tree)(using symbolsInCurrentLevel(defSyms)) case CaseDef(pat, guard, body) => - val last = enteredSyms - tpd.patVars(pat).foreach(markSymbol) - mapOverTree(last) + super.transform(tree)(using symbolsInCurrentLevel(tpd.patVars(pat))) case (_:Import | _:Export) => tree case _: Template => - val last = enteredSyms - tree.symbol.owner.info.decls.foreach(markSymbol) - mapOverTree(last) + val decls = tree.symbol.owner.info.decls.toList + super.transform(tree)(using symbolsInCurrentLevel(decls)) + + case LambdaTypeTree(tparams, body) => + super.transform(tree)(using symbolsInCurrentLevel(tparams.map(_.symbol))) + + case tree: DefTree => + val paramSyms = tree match + case tree: DefDef => tree.paramss.flatten.map(_.symbol) + case _ => Nil + super.transform(tree)(using symbolsInCurrentLevel(tree.symbol :: paramSyms)) case _ => - markDef(tree) - mapOverTree(enteredSyms) + super.transform(tree) } } } - - -object TreeMapWithStages { - - /** A key to be used in a context property that caches the `levelOf` mapping */ - private val LevelOfKey = new Property.Key[mutable.HashMap[Symbol, Int]] - - /** Initial context for a StagingTransformer transformation. */ - def freshStagingContext(using Context): Context = - ctx.fresh.setProperty(LevelOfKey, new mutable.HashMap[Symbol, Int]) - -} diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index f0ed7026ee91..d6b7f3141b96 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -9,10 +9,11 @@ import SymUtils._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.ast.TreeMapWithImplicits import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel import scala.collection.mutable.ListBuffer @@ -46,10 +47,10 @@ class Inlining extends MacroTransform { def traverse(tree: Tree)(using Context): Unit = tree match case _: GenericApply if tree.symbol.isQuote => - traverseChildren(tree)(using StagingContext.quoteContext) + traverseChildren(tree)(using StagingLevel.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => - traverseChildren(tree)(using StagingContext.spliceContext) - case tree: RefTree if !Inlines.inInlineMethod && StagingContext.level == 0 => + traverseChildren(tree)(using StagingLevel.spliceContext) + case tree: RefTree if !Inlines.inInlineMethod && StagingLevel.level == 0 => assert(!tree.symbol.isInlineMethod, tree.show) case _ => traverseChildren(tree) @@ -76,7 +77,7 @@ class Inlining extends MacroTransform { else if tree.symbol.is(Param) then super.transform(tree) else if !tree.symbol.isPrimaryConstructor - && StagingContext.level == 0 + && StagingLevel.level == 0 && MacroAnnotations.hasMacroAnnotation(tree.symbol) then val trees = (new MacroAnnotations).expandAnnotations(tree) @@ -98,9 +99,9 @@ class Inlining extends MacroTransform { if tree1.tpe.isError then tree1 else Inlines.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => - super.transform(tree)(using StagingContext.quoteContext) + super.transform(tree)(using StagingLevel.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => - super.transform(tree)(using StagingContext.spliceContext) + super.transform(tree)(using StagingLevel.spliceContext) case _: PackageDef => super.transform(tree) match case tree1: PackageDef => diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 87c6e294c104..728ee9552c81 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -19,7 +19,6 @@ import scala.collection.mutable import dotty.tools.dotc.core.Annotations._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.inlines.Inlines import scala.annotation.constructorOnly @@ -93,7 +92,7 @@ class PickleQuotes extends MacroTransform { case _ => override def run(using Context): Unit = - if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala index e462f82b1dad..b2059195b8e4 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -17,7 +17,6 @@ import dotty.tools.dotc.core.Annotations._ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.transform.TreeMapWithStages._ import scala.annotation.constructorOnly diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 393fe46b8438..2b35188425fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -14,15 +14,17 @@ import util.Spans._ import SymUtils._ import NameKinds._ import dotty.tools.dotc.ast.tpd -import StagingContext._ import scala.collection.mutable import dotty.tools.dotc.core.Annotations._ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.config.ScalaRelease.* +import dotty.tools.dotc.staging.PCPCheckAndHeal +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.staging.QuoteTypeTags import scala.annotation.constructorOnly @@ -77,7 +79,7 @@ class Splicing extends MacroTransform: override def run(using Context): Unit = if ctx.compilationUnit.needsStaging then - super.run(using freshStagingContext) + super.run protected def newTransformer(using Context): Transformer = Level0QuoteTransformer @@ -190,7 +192,7 @@ class Splicing extends MacroTransform: private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] /** Reference to the `Quotes` instance of the current level 1 splice */ private var quotes: Tree | Null = null // TODO: add to the context - private var healedTypes: PCPCheckAndHeal.QuoteTypeTags | Null = null // TODO: add to the context + private var healedTypes: QuoteTypeTags | Null = null // TODO: add to the context def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = assert(level == 0) @@ -246,7 +248,7 @@ class Splicing extends MacroTransform: if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => val newContent = capturedPartTypes(tpt) newContent match - case block: Block => + case block: Block => inContext(ctx.withSource(tree.source)) { Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span) } @@ -260,7 +262,7 @@ class Splicing extends MacroTransform: private def transformLevel0QuoteContent(tree: Tree)(using Context): Tree = // transform and collect new healed types val old = healedTypes - healedTypes = new PCPCheckAndHeal.QuoteTypeTags(tree.span) + healedTypes = new QuoteTypeTags(tree.span) val tree1 = transform(tree) val newHealedTypes = healedTypes.nn.getTypeTags healedTypes = old @@ -342,7 +344,7 @@ class Splicing extends MacroTransform: val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 ref(bindingSym) - private def newQuotedTypeClassBinding(tpe: Type)(using Context) = + private def newQuotedTypeClassBinding(tpe: Type)(using Context) = newSymbol( spliceOwner, UniqueName.fresh(nme.Type).toTermName, @@ -358,7 +360,7 @@ class Splicing extends MacroTransform: private def capturedPartTypes(tpt: Tree)(using Context): Tree = val old = healedTypes - healedTypes = PCPCheckAndHeal.QuoteTypeTags(tpt.span) + healedTypes = QuoteTypeTags(tpt.span) val capturePartTypes = new TypeMap { def apply(tp: Type) = tp match { case typeRef @ TypeRef(prefix, _) if isCaptured(prefix.typeSymbol) || isCaptured(prefix.termSymbol) => @@ -376,7 +378,7 @@ class Splicing extends MacroTransform: tpt match case block: Block => cpy.Block(block)(newHealedTypes ::: block.stats, TypeTree(captured)) - case _ => + case _ => if newHealedTypes.nonEmpty then cpy.Block(tpt)(newHealedTypes, TypeTree(captured)) else diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 1de050a9a6c1..5ebfa25eeacd 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -6,14 +6,14 @@ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Phases._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.util.SrcPos import dotty.tools.dotc.transform.SymUtils._ -import dotty.tools.dotc.transform.TreeMapWithStages._ - - +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel.* +import dotty.tools.dotc.staging.PCPCheckAndHeal +import dotty.tools.dotc.staging.HealType /** Checks that the Phase Consistency Principle (PCP) holds and heals types. * @@ -35,20 +35,22 @@ class Staging extends MacroTransform { // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => - val checker = new PCPCheckAndHeal(freshStagingContext) { - override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): TypeRef = { - def symStr = - if (sym.is(ModuleClass)) sym.sourceModule.show - else i"${sym.name}.this" - val errMsg = s"\nin ${ctx.owner.fullName}" - assert( - ctx.owner.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) || - (sym.isType && levelOf(sym) > 0), - em"""access to $symStr from wrong staging level: - | - the definition is at level ${levelOf(sym)}, - | - but the access is at level $level.$errMsg""") + val checker = new PCPCheckAndHeal { + override protected def healType(pos: SrcPos)(using Context) = new HealType(pos) { + override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos): TypeRef = { + def symStr = + if (sym.is(ModuleClass)) sym.sourceModule.show + else i"${sym.name}.this" + val errMsg = s"\nin ${ctx.owner.fullName}" + assert( + ctx.owner.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) || + (sym.isType && levelOf(sym) > 0), + em"""access to $symStr from wrong staging level: + | - the definition is at level ${levelOf(sym)}, + | - but the access is at level $level.$errMsg""") - tp + tp + } } } checker.transform(tree) @@ -66,11 +68,11 @@ class Staging extends MacroTransform { } override def run(using Context): Unit = - if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = - new PCPCheckAndHeal(ctx).transform(tree) + (new PCPCheckAndHeal).transform(tree) } } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 52942ea719f9..309c61f5c7d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -678,7 +678,7 @@ object TreeChecker { defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) val expectedContentType = defn.FunctionOf(argQuotedTypes, contextualResult) - assert(content.typeOpt =:= expectedContentType) + assert(content.typeOpt =:= expectedContentType, i"expected content of the hole to be ${expectedContentType} but got ${content.typeOpt}") tree1 } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 65d8abfdf6a7..5d04f27a1305 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -11,11 +11,12 @@ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds.PatMatGivenVarName import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.inlines.PrepareInlineable +import dotty.tools.dotc.staging.QuoteContext.* +import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.typer.Implicits._ import dotty.tools.dotc.typer.Inferencing._ @@ -91,7 +92,7 @@ trait QuotesAndSplices { tree.withType(UnspecifiedErrorType) } else { - if (StagingContext.level == 0) { + if (level == 0) { // Mark the first inline method from the context as a macro def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 96149caf215d..8e8c8b2da1ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -44,6 +44,7 @@ import config.Feature import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion._ import rewrites.Rewrites.patch +import staging.StagingLevel import transform.SymUtils._ import transform.TypeUtils._ import reporting._ @@ -2410,7 +2411,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx)) if sym.isInlineMethod then - if StagingContext.level > 0 then + if StagingLevel.level > 0 then report.error("inline def cannot be within quotes", sym.sourcePos) if sym.is(Given) && untpd.stripBlock(untpd.unsplice(ddef.rhs)).isInstanceOf[untpd.Function]