Skip to content

Commit 757e2d1

Browse files
authored
Merge pull request #5846 from dotty-staging/move-staging-to-typer
Move Staging to Typer
2 parents 4447d83 + cc3a8dd commit 757e2d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+691
-400
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Compiler {
3737
/** Phases dealing with the frontend up to trees ready for TASTY pickling */
3838
protected def frontendPhases: List[List[Phase]] =
3939
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
40+
List(new Staging) :: // Check PCP, heal quoted types and expand macros
4041
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4142
List(new PostTyper) :: // Additional checks and cleanups after type checking
4243
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
@@ -45,7 +46,6 @@ class Compiler {
4546

4647
/** Phases dealing with TASTY tree pickling and unpickling */
4748
protected def picklerPhases: List[List[Phase]] =
48-
List(new Staging) :: // Check PCP, heal quoted types and expand macros
4949
List(new Pickler) :: // Generate TASTY info
5050
List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures
5151
Nil

compiler/src/dotty/tools/dotc/Driver.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import dotty.tools.FatalError
66
import config.CompilerCommand
77
import core.Comments.{ContextDoc, ContextDocstrings}
88
import core.Contexts.{Context, ContextBase}
9-
import core.{Mode, TypeError}
9+
import core.{MacroClassLoader, Mode, TypeError}
1010
import reporting._
1111

1212
import scala.util.control.NonFatal
@@ -54,6 +54,7 @@ class Driver {
5454
val ctx = rootCtx.fresh
5555
val summary = CompilerCommand.distill(args)(ctx)
5656
ctx.setSettings(summary.sstate)
57+
MacroClassLoader.init(ctx)
5758

5859
if (!ctx.settings.YdropComments.value(ctx) || ctx.mode.is(Mode.ReadComments)) {
5960
ctx.setProperty(ContextDoc, new ContextDocstrings)

compiler/src/dotty/tools/dotc/ast/Desugar.scala

-9
Original file line numberDiff line numberDiff line change
@@ -1325,15 +1325,6 @@ object desugar {
13251325
val desugared = tree match {
13261326
case SymbolLit(str) =>
13271327
Literal(Constant(scala.Symbol(str)))
1328-
case Quote(t) =>
1329-
if (t.isType)
1330-
TypeApply(ref(defn.QuotedType_applyR), List(t))
1331-
else
1332-
Apply(ref(defn.QuotedExpr_applyR), t)
1333-
case Splice(expr) =>
1334-
Select(expr, nme.splice)
1335-
case TypSplice(expr) =>
1336-
Select(expr, tpnme.splice)
13371328
case InterpolatedString(id, segments) =>
13381329
val strs = segments map {
13391330
case ts: Thicket => ts.trees.head

compiler/src/dotty/tools/dotc/ast/tpd.scala

+7
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
10011001
foreachSubTree { tree => if (f(tree)) buf += tree }
10021002
buf.toList
10031003
}
1004+
1005+
/** Set this tree as the `defTree` of its symbol and return this tree */
1006+
def setDefTree(implicit ctx: Context): ThisTree = {
1007+
val sym = tree.symbol
1008+
if (sym.exists) sym.defTree = tree
1009+
tree
1010+
}
10041011
}
10051012

10061013
/** Map Inlined nodes, NamedArgs, Blocks with no statements and local references to underlying arguments.

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

+3
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,9 @@ object Flags {
608608
/** An inline method */
609609
final val InlineMethod: FlagConjunction = allOf(Inline, Method)
610610

611+
/** An inline by-name parameter proxy */
612+
final val InlineByNameProxy: FlagConjunction = allOf(InlineProxy, Method)
613+
611614
/** An inline parameter */
612615
final val InlineParam: FlagConjunction = allOf(Inline, Param)
613616

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc.core
2+
3+
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.util.Property
5+
import dotty.tools.dotc.reporting.trace
6+
7+
import scala.collection.mutable
8+
9+
object MacroClassLoader {
10+
11+
/** A key to be used in a context property that caches the class loader used for macro expansion */
12+
private val MacroClassLoaderKey = new Property.Key[ClassLoader]
13+
14+
/** Get the macro class loader */
15+
def fromContext(implicit ctx: Context): ClassLoader =
16+
ctx.property(MacroClassLoaderKey).getOrElse(makeMacroClassLoader)
17+
18+
/** Context with a cached macro class loader that can be accessed with `macroClassLoader` */
19+
def init(ctx: FreshContext): ctx.type =
20+
ctx.setProperty(MacroClassLoaderKey, makeMacroClassLoader(ctx))
21+
22+
private def makeMacroClassLoader(implicit ctx: Context): ClassLoader = trace("new macro class loader") {
23+
val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
24+
new java.net.URLClassLoader(urls, getClass.getClassLoader)
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dotty.tools.dotc.core
2+
3+
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.util.Property
5+
6+
import scala.collection.mutable
7+
8+
object StagingContext {
9+
10+
/** A key to be used in a context property that tracks the quoteation level */
11+
private val QuotationLevel = new Property.Key[Int]
12+
13+
/** All enclosing calls that are currently inlined, from innermost to outermost. */
14+
def level(implicit ctx: Context): Int =
15+
ctx.property(QuotationLevel).getOrElse(0)
16+
17+
/** Context with an incremented quotation level. */
18+
def quoteContext(implicit ctx: Context): Context =
19+
ctx.fresh.setProperty(QuotationLevel, level + 1)
20+
21+
/** Context with a decremented quotation level. */
22+
def spliceContext(implicit ctx: Context): Context =
23+
ctx.fresh.setProperty(QuotationLevel, level - 1)
24+
25+
}

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ object PickledQuotes {
145145
case Block(stats, expr) =>
146146
seq(stats, rec(expr)).withSpan(fn.span)
147147
case _ =>
148-
fn.select(nme.apply).appliedToArgs(argRefs())
148+
fn.select(nme.apply).appliedToArgs(argRefs()).withSpan(fn.span)
149149
}
150150
Block(argVals, rec(fn))
151151
}

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,6 @@ class TreeUnpickler(reader: TastyReader,
851851
sym.info = ta.avoidPrivateLeaks(sym, tree.sourcePos)
852852
}
853853

854-
sym.defTree = tree
855-
856854
if (ctx.mode.is(Mode.ReadComments)) {
857855
assert(ctx.docCtx.isDefined, "Mode is `ReadComments`, but no `docCtx` is set.")
858856
commentUnpicklerOpt.foreach { commentUnpickler =>
@@ -862,7 +860,7 @@ class TreeUnpickler(reader: TastyReader,
862860
}
863861
}
864862

865-
tree
863+
tree.setDefTree
866864
}
867865

868866
private def readTemplate(implicit ctx: Context): Template = {
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 a splice of `implicitly[Type[T]]`.
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 splice ${...} 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+
/** Is a reference to a class but not `this.type` */
151+
def isClassRef = sym.isClass && !tp.isInstanceOf[ThisType]
152+
153+
if (sym.exists && !sym.isStaticOwner && !isClassRef && !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)