Skip to content

Refactor level checking / type healing logic #17082

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 5 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
58 changes: 0 additions & 58 deletions compiler/src/dotty/tools/dotc/core/StagingContext.scala

This file was deleted.

5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/inlines/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import staging.PCPCheckAndHeal
import transform.SymUtils.*
import config.Printers.inlining
import util.Property
import staging.StagingLevel

object PrepareInlineable {
import tpd._
Expand Down Expand Up @@ -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

Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/quoted/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +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.staging.QuoteContext.*
import dotty.tools.dotc.typer.ImportInfo.withRootImports
import dotty.tools.dotc.util.SrcPos
import dotty.tools.dotc.reporting.Message
Expand Down
99 changes: 99 additions & 0 deletions compiler/src/dotty/tools/dotc/staging/HealType.scala
Original file line number Diff line number Diff line change
@@ -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 and 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
}
}
Loading