Skip to content

Commit 26e5e0f

Browse files
committed
Move PCPCheckAndHeal into its own file
1 parent 4fdc844 commit 26e5e0f

File tree

2 files changed

+220
-194
lines changed

2 files changed

+220
-194
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd}
6+
import dotty.tools.dotc.core.Constants._
7+
import dotty.tools.dotc.core.Contexts._
8+
import dotty.tools.dotc.core.Decorators._
9+
import dotty.tools.dotc.core.Flags._
10+
import dotty.tools.dotc.core.quoted._
11+
import dotty.tools.dotc.core.NameKinds._
12+
import dotty.tools.dotc.core.StagingContext._
13+
import dotty.tools.dotc.core.StdNames._
14+
import dotty.tools.dotc.core.Symbols._
15+
import dotty.tools.dotc.core.tasty.TreePickler.Hole
16+
import dotty.tools.dotc.core.Types._
17+
import dotty.tools.dotc.util.SourcePosition
18+
import dotty.tools.dotc.util.Spans._
19+
import dotty.tools.dotc.transform.SymUtils._
20+
import dotty.tools.dotc.transform.TreeMapWithStages._
21+
import dotty.tools.dotc.typer.Implicits.SearchFailureType
22+
import dotty.tools.dotc.typer.Inliner
23+
24+
import scala.collection.mutable
25+
import dotty.tools.dotc.util.SourcePosition
26+
27+
import scala.annotation.constructorOnly
28+
29+
/** Checks that the Phase Consistency Principle (PCP) holds and heals types.
30+
*
31+
* Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`.
32+
*/
33+
class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) {
34+
import tpd._
35+
36+
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
37+
case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree
38+
case _ => checkLevel(super.transform(tree))
39+
}
40+
41+
/** Transform quoted trees while maintaining phase correctness */
42+
override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
43+
val body1 = transform(body)(quoteContext)
44+
super.transformQuotation(body1, quote)
45+
}
46+
47+
/** Transform splice
48+
* - If inside a quote, transform the contents of the splice.
49+
* - If inside inlined code, expand the macro code.
50+
* - If inside of a macro definition, check the validity of the macro.
51+
*/
52+
protected def transformSplice(splice: Select)(implicit ctx: Context): Tree = {
53+
if (level >= 1) {
54+
val body1 = transform(splice.qualifier)(spliceContext)
55+
val splice1 = cpy.Select(splice)(body1, splice.name)
56+
if (splice1.isType) splice1
57+
else addSpliceCast(splice1)
58+
}
59+
else {
60+
assert(!enclosingInlineds.nonEmpty, "unexpanded macro")
61+
assert(ctx.owner.isInlineMethod)
62+
if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition
63+
transform(splice.qualifier)(spliceContext) // Just check PCP
64+
splice
65+
}
66+
else { // level 0 inside an inline definition
67+
ctx.error(
68+
"Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.",
69+
splice.sourcePos)
70+
splice
71+
}
72+
}
73+
}
74+
75+
76+
/** Add cast to force boundaries where T and ~t (an alias of T) are used to ensure PCP.
77+
* '{ ~(...: T) } --> '{ ~(...: T).asInstanceOf[T] } --> '{ ~(...: T).asInstanceOf[~t] }
78+
*/
79+
protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = {
80+
val tp = checkType(tree.sourcePos).apply(tree.tpe.widenTermRefExpr)
81+
tree.cast(tp).withSpan(tree.span)
82+
}
83+
84+
/** If `tree` refers to a locally defined symbol (either directly, or in a pickled type),
85+
* check that its staging level matches the current level. References to types
86+
* that are phase-incorrect can still be healed as follows:
87+
*
88+
* If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
89+
* `~implicitly[quoted.Type[T]]`.
90+
*/
91+
protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = {
92+
def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp)
93+
tree match {
94+
case Quoted(_) | Spliced(_) =>
95+
tree
96+
case tree: RefTree if tree.symbol.is(InlineParam) =>
97+
tree
98+
case _: This =>
99+
assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty)
100+
tree
101+
case _: Ident =>
102+
checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match {
103+
case Some(tpRef) => tpRef
104+
case _ => tree
105+
}
106+
case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply | Select(_, OuterSelectName(_, _)) =>
107+
tree.withType(checkTp(tree.tpe))
108+
case _: ValOrDefDef | _: Bind =>
109+
tree.symbol.info = checkTp(tree.symbol.info)
110+
tree
111+
case _: Template =>
112+
checkTp(tree.symbol.owner.asClass.givenSelfType)
113+
tree
114+
case _ =>
115+
tree
116+
}
117+
}
118+
119+
/** Check and heal all named types and this-types in a given type for phase consistency. */
120+
private def checkType(pos: SourcePosition)(implicit ctx: Context): TypeMap = new TypeMap {
121+
def apply(tp: Type): Type = reporting.trace(i"check type level $tp at $level") {
122+
tp match {
123+
case tp: TypeRef if tp.symbol.isSplice =>
124+
if (tp.isTerm)
125+
ctx.error(i"splice outside quotes", pos)
126+
tp
127+
case tp: NamedType =>
128+
checkSymLevel(tp.symbol, tp, pos) match {
129+
case Some(tpRef) => tpRef.tpe
130+
case _ =>
131+
if (tp.symbol.is(Param)) tp
132+
else mapOver(tp)
133+
}
134+
case tp: ThisType =>
135+
assert(checkSymLevel(tp.cls, tp, pos).isEmpty)
136+
mapOver(tp)
137+
case _ =>
138+
mapOver(tp)
139+
}
140+
}
141+
}
142+
143+
/** Check reference to `sym` for phase consistency, where `tp` is the underlying type
144+
* by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it.
145+
*
146+
* @return `None` if the phase is correct or cannot be healed
147+
* `Some(tree)` with the `tree` of the healed type tree for `~implicitly[quoted.Type[T]]`
148+
*/
149+
private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
150+
val isThis = tp.isInstanceOf[ThisType]
151+
if (!isThis && !sym.is(Param) && sym.maybeOwner.isType)
152+
None
153+
else if (sym.exists && !sym.isStaticOwner && !levelOK(sym))
154+
tryHeal(sym, tp, pos)
155+
else
156+
None
157+
}
158+
159+
/** Does the level of `sym` match the current level?
160+
* An exception is made for inline vals in macros. These are also OK if their level
161+
* is one higher than the current level, because on execution such values
162+
* are constant expression trees and we can pull out the constant from the tree.
163+
*/
164+
private def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf(sym) match {
165+
case Some(l) =>
166+
l == level ||
167+
level == -1 && (
168+
sym == defn.TastyReflection_macroContext ||
169+
// here we assume that Splicer.canBeSpliced was true before going to level -1,
170+
// this implies that all non-inline arguments are quoted and that the following two cases are checked
171+
// on inline parameters or type parameters.
172+
sym.is(Param) ||
173+
sym.isClass // reference to this in inline methods
174+
)
175+
case None =>
176+
!sym.is(Param) || levelOK(sym.owner)
177+
}
178+
179+
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
180+
* @return None if successful
181+
* @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
182+
* to be added to the "inconsistent phase" message.
183+
*/
184+
protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
185+
def levelError(errMsg: String) = {
186+
def symStr =
187+
if (!tp.isInstanceOf[ThisType]) sym.show
188+
else if (sym.is(ModuleClass)) sym.sourceModule.show
189+
else i"${sym.name}.this"
190+
ctx.error(
191+
em"""access to $symStr from wrong staging level:
192+
| - the definition is at level ${levelOf(sym).getOrElse(0)},
193+
| - but the access is at level $level.$errMsg""", pos)
194+
None
195+
}
196+
tp match {
197+
case tp: TypeRef =>
198+
if (level == -1) {
199+
assert(ctx.inInlineMethod)
200+
None
201+
} else {
202+
val reqType = defn.QuotedTypeType.appliedTo(tp)
203+
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
204+
tag.tpe match {
205+
case fail: SearchFailureType =>
206+
levelError(i"""
207+
|
208+
| The access would be accepted with the right type tag, but
209+
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
210+
case _ =>
211+
Some(tag.select(tpnme.splice))
212+
}
213+
}
214+
case _ =>
215+
levelError("")
216+
}
217+
}
218+
219+
}

0 commit comments

Comments
 (0)