Skip to content

Commit f1bc0fb

Browse files
Refactor level checking / type healing logic (#17082)
Disentangle level checking from type healing.
2 parents f28d708 + 61165cc commit f1bc0fb

19 files changed

+369
-320
lines changed

compiler/src/dotty/tools/dotc/core/StagingContext.scala

Lines changed: 0 additions & 58 deletions
This file was deleted.

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import util.Spans.Span
2323
import dotty.tools.dotc.transform.Splicer
2424
import dotty.tools.dotc.transform.BetaReduce
2525
import quoted.QuoteUtils
26+
import staging.StagingLevel
2627
import scala.annotation.constructorOnly
2728

2829
/** General support for inlining */
@@ -814,7 +815,7 @@ class Inliner(val call: tpd.Tree)(using Context):
814815
val locked = ctx.typerState.ownedVars
815816
val res = cancelQuotes(constToLiteral(BetaReduce(super.typedApply(tree, pt)))) match {
816817
case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice
817-
&& StagingContext.level == 0
818+
&& StagingLevel.level == 0
818819
&& !hasInliningErrors =>
819820
val expanded = expandMacro(res.args.head, tree.srcPos)
820821
transform.TreeChecker.checkMacroGeneratedTree(res, expanded)
@@ -1026,7 +1027,7 @@ class Inliner(val call: tpd.Tree)(using Context):
10261027
}
10271028

10281029
private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = {
1029-
assert(StagingContext.level == 0)
1030+
assert(StagingLevel.level == 0)
10301031
val inlinedFrom = enclosingInlineds.last
10311032
val dependencies = macroDependencies(body)
10321033
val suspendable = ctx.compilationUnit.isSuspendable

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ErrorReporting.errorTree
1414
import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos}
1515
import parsing.Parsers.Parser
1616
import transform.{PostTyper, Inlining, CrossVersionChecks}
17+
import staging.StagingLevel
1718

1819
import collection.mutable
1920
import reporting.trace
@@ -56,7 +57,7 @@ object Inlines:
5657
case _ =>
5758
isInlineable(tree.symbol)
5859
&& !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly]
59-
&& StagingContext.level == 0
60+
&& StagingLevel.level == 0
6061
&& (
6162
ctx.phase == Phases.inliningPhase
6263
|| (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree))

compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ import NameKinds.{InlineAccessorName, UniqueInlineName}
1717
import inlines.Inlines
1818
import NameOps._
1919
import Annotations._
20-
import transform.{AccessProxies, PCPCheckAndHeal, Splicer}
20+
import transform.{AccessProxies, Splicer}
21+
import staging.PCPCheckAndHeal
2122
import transform.SymUtils.*
2223
import config.Printers.inlining
2324
import util.Property
24-
import dotty.tools.dotc.transform.TreeMapWithStages._
25+
import staging.StagingLevel
2526

2627
object PrepareInlineable {
2728
import tpd._
@@ -73,7 +74,7 @@ object PrepareInlineable {
7374
!sym.isContainedIn(inlineSym) &&
7475
!(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) &&
7576
!sym.isInlineMethod &&
76-
(Inlines.inInlineMethod || StagingContext.level > 0)
77+
(Inlines.inInlineMethod || StagingLevel.level > 0)
7778

7879
def preTransform(tree: Tree)(using Context): Tree
7980

@@ -90,8 +91,8 @@ object PrepareInlineable {
9091
}
9192

9293
private def stagingContext(tree: Tree)(using Context): Context = tree match
93-
case tree: Apply if tree.symbol.isQuote => StagingContext.quoteContext
94-
case tree: Apply if tree.symbol.isExprSplice => StagingContext.spliceContext
94+
case tree: Apply if tree.symbol.isQuote => StagingLevel.quoteContext
95+
case tree: Apply if tree.symbol.isExprSplice => StagingLevel.spliceContext
9596
case _ => ctx
9697
}
9798

@@ -293,7 +294,7 @@ object PrepareInlineable {
293294
if (code.symbol.flags.is(Inline))
294295
report.error("Macro cannot be implemented with an `inline` method", code.srcPos)
295296
Splicer.checkValidMacroBody(code)
296-
new PCPCheckAndHeal(freshStagingContext).transform(body) // Ignore output, only check PCP
297+
(new PCPCheckAndHeal).transform(body) // Ignore output, only check PCP
297298
case Block(List(stat), Literal(Constants.Constant(()))) => checkMacro(stat)
298299
case Block(Nil, expr) => checkMacro(expr)
299300
case Typed(expr, _) => checkMacro(expr)

compiler/src/dotty/tools/dotc/quoted/Interpreter.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ import dotty.tools.dotc.core.Denotations.staticRef
1919
import dotty.tools.dotc.core.Flags._
2020
import dotty.tools.dotc.core.NameKinds.FlatName
2121
import dotty.tools.dotc.core.Names._
22-
import dotty.tools.dotc.core.StagingContext._
2322
import dotty.tools.dotc.core.StdNames._
2423
import dotty.tools.dotc.core.Symbols._
2524
import dotty.tools.dotc.core.TypeErasure
2625
import dotty.tools.dotc.core.Types._
2726
import dotty.tools.dotc.quoted._
28-
import dotty.tools.dotc.transform.TreeMapWithStages._
27+
import dotty.tools.dotc.staging.QuoteContext.*
2928
import dotty.tools.dotc.typer.ImportInfo.withRootImports
3029
import dotty.tools.dotc.util.SrcPos
3130
import dotty.tools.dotc.reporting.Message
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package dotty.tools.dotc
2+
package staging
3+
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.core.Decorators._
6+
import dotty.tools.dotc.core.Flags._
7+
import dotty.tools.dotc.core.Symbols._
8+
import dotty.tools.dotc.core.Types._
9+
import dotty.tools.dotc.staging.QuoteContext.*
10+
import dotty.tools.dotc.staging.StagingLevel.*
11+
import dotty.tools.dotc.staging.QuoteTypeTags.*
12+
import dotty.tools.dotc.transform.SymUtils._
13+
import dotty.tools.dotc.typer.Implicits.SearchFailureType
14+
import dotty.tools.dotc.util.SrcPos
15+
16+
class HealType(pos: SrcPos)(using Context) extends TypeMap {
17+
18+
/** If the type refers to a locally defined symbol (either directly, or in a pickled type),
19+
* check that its staging level matches the current level.
20+
* - Static types and term are allowed at any level.
21+
* - If a type reference is used a higher level, then it is inconsistent.
22+
* Will attempt to heal before failing.
23+
* - If a term reference is used a higher level, then it is inconsistent.
24+
* It cannot be healed because the term will not exist in any future stage.
25+
*
26+
* If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
27+
* a type tag of type `quoted.Type[T]`.
28+
* The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit
29+
* or indirectly by `tryHeal`.
30+
*/
31+
def apply(tp: Type): Type =
32+
tp match
33+
case tp: TypeRef =>
34+
healTypeRef(tp)
35+
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) =>
36+
levelError(tp.symbol, tp, pos)
37+
case tp: AnnotatedType =>
38+
derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
39+
case _ =>
40+
mapOver(tp)
41+
42+
private def healTypeRef(tp: TypeRef): Type =
43+
tp.prefix match
44+
case prefix: TermRef if tp.symbol.isTypeSplice =>
45+
checkNotWildcardSplice(tp)
46+
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
47+
case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) =>
48+
dealiasAndTryHeal(prefix.symbol, tp, pos)
49+
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
50+
dealiasAndTryHeal(tp.symbol, tp, pos)
51+
case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic =>
52+
dealiasAndTryHeal(tp.symbol, tp, pos)
53+
case _ =>
54+
mapOver(tp)
55+
56+
private def checkNotWildcardSplice(splice: TypeRef): Unit =
57+
splice.prefix.termSymbol.info.argInfos match
58+
case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos)
59+
case _ =>
60+
61+
private def dealiasAndTryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos): Type =
62+
val tp1 = tp.dealias
63+
if tp1 != tp then apply(tp1)
64+
else tryHeal(tp.symbol, tp, pos)
65+
66+
/** Try to heal reference to type `T` used in a higher level than its definition.
67+
* Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
68+
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.
69+
* Emits an error if `T` cannot be healed and returns `T`.
70+
*/
71+
protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos): TypeRef = {
72+
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
73+
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
74+
tag.tpe match
75+
case tp: TermRef =>
76+
ctx.typer.checkStable(tp, pos, "type witness")
77+
getQuoteTypeTags.getTagRef(tp)
78+
case _: SearchFailureType =>
79+
report.error(
80+
ctx.typer.missingArgMsg(tag, reqType, "")
81+
.prepend(i"Reference to $tp within quotes requires a given $reqType in scope.\n")
82+
.append("\n"),
83+
pos)
84+
tp
85+
case _ =>
86+
report.error(em"""Reference to $tp within quotes requires a given $reqType in scope.
87+
|
88+
|""", pos)
89+
tp
90+
}
91+
92+
private def levelError(sym: Symbol, tp: Type, pos: SrcPos): tp.type = {
93+
report.error(
94+
em"""access to $sym from wrong staging level:
95+
| - the definition is at level ${levelOf(sym)},
96+
| - but the access is at level $level""", pos)
97+
tp
98+
}
99+
}

0 commit comments

Comments
 (0)