Skip to content

Commit b3b7889

Browse files
committed
Use tree checker for macro expanded trees
Trees are only checked if -Xcheck-macros is enabled.
1 parent 2920a4f commit b3b7889

File tree

4 files changed

+104
-30
lines changed

4 files changed

+104
-30
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@ class Inliner(val call: tpd.Tree)(using Context):
816816
&& StagingContext.level == 0
817817
&& !hasInliningErrors =>
818818
val expanded = expandMacro(res.args.head, tree.srcPos)
819+
transform.TreeChecker.checkMacroGeneratedTree(res, expanded)
819820
typedExpr(expanded) // Inline calls and constant fold code generated by the macro
820821
case res =>
821822
specializeEq(inlineIfNeeded(res, pt, locked))

compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ class MacroAnnotations(thisPhase: DenotTransformer):
8282
case (prefixed, newTree :: suffixed) =>
8383
allTrees ++= prefixed
8484
insertedAfter = suffixed :: insertedAfter
85-
prefixed.foreach(checkAndEnter(_, tree.symbol, annot))
86-
suffixed.foreach(checkAndEnter(_, tree.symbol, annot))
85+
prefixed.foreach(checkAndEnter(_, tree, annot))
86+
suffixed.foreach(checkAndEnter(_, tree, annot))
87+
transform.TreeChecker.checkMacroGeneratedTree(tree, newTree)
8788
newTree
8889
case (Nil, Nil) =>
8990
report.error(i"Unexpected `Nil` returned by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos)
@@ -119,15 +120,17 @@ class MacroAnnotations(thisPhase: DenotTransformer):
119120
annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition])
120121

121122
/** Check that this tree can be added by the macro annotation and enter it if needed */
122-
private def checkAndEnter(newTree: Tree, annotated: Symbol, annot: Annotation)(using Context) =
123+
private def checkAndEnter(newTree: Tree, annotated: Tree, annot: Annotation)(using Context) =
124+
transform.TreeChecker.checkMacroGeneratedTree(annotated, newTree)
123125
val sym = newTree.symbol
126+
val annotatedSym = annotated.symbol
124127
if sym.isClass then
125128
report.error(i"macro annotation returning a `class` is not yet supported. $annot tried to add $sym", annot.tree)
126129
else if sym.isType then
127130
report.error(i"macro annotation cannot return a `type`. $annot tried to add $sym", annot.tree)
128-
else if sym.owner != annotated.owner then
129-
report.error(i"macro annotation $annot added $sym with an inconsistent owner. Expected it to be owned by ${annotated.owner} but was owned by ${sym.owner}.", annot.tree)
130-
else if annotated.isClass && annotated.owner.is(Package) /*&& !sym.isClass*/ then
131+
else if sym.owner != annotatedSym.owner then
132+
report.error(i"macro annotation $annot added $sym with an inconsistent owner. Expected it to be owned by ${annotatedSym.owner} but was owned by ${sym.owner}.", annot.tree)
133+
else if annotatedSym.isClass && annotatedSym.owner.is(Package) /*&& !sym.isClass*/ then
131134
report.error(i"macro annotation can not add top-level ${sym.showKind}. $annot tried to add $sym.", annot.tree)
132135
else
133136
sym.enteredAfter(thisPhase)

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -167,28 +167,6 @@ class TreeChecker extends Phase with SymTransformer {
167167
}
168168

169169
object TreeChecker {
170-
/** - Check that TypeParamRefs and MethodParams refer to an enclosing type.
171-
* - Check that all type variables are instantiated.
172-
*/
173-
def checkNoOrphans(tp0: Type, tree: untpd.Tree = untpd.EmptyTree)(using Context): Type = new TypeMap() {
174-
val definedBinders = new java.util.IdentityHashMap[Type, Any]
175-
def apply(tp: Type): Type = {
176-
tp match {
177-
case tp: BindingType =>
178-
definedBinders.put(tp, tp)
179-
mapOver(tp)
180-
definedBinders.remove(tp)
181-
case tp: ParamRef =>
182-
assert(definedBinders.get(tp.binder) != null, s"orphan param: ${tp.show}, hash of binder = ${System.identityHashCode(tp.binder)}, tree = ${tree.show}, type = $tp0")
183-
case tp: TypeVar =>
184-
assert(tp.isInstantiated, s"Uninstantiated type variable: ${tp.show}, tree = ${tree.show}")
185-
apply(tp.underlying)
186-
case _ =>
187-
mapOver(tp)
188-
}
189-
tp
190-
}
191-
}.apply(tp0)
192170

193171
/** Run some additional checks on the nodes of the trees. Specifically:
194172
*
@@ -723,5 +701,97 @@ object TreeChecker {
723701
}
724702

725703
override def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = tree
704+
705+
/** - Check that TypeParamRefs and MethodParams refer to an enclosing type.
706+
* - Check that all type variables are instantiated.
707+
*/
708+
def checkNoOrphans(tp0: Type, tree: untpd.Tree = untpd.EmptyTree)(using Context): Type = new TypeMap() {
709+
val definedBinders = new java.util.IdentityHashMap[Type, Any]
710+
def apply(tp: Type): Type = {
711+
tp match {
712+
case tp: BindingType =>
713+
definedBinders.put(tp, tp)
714+
mapOver(tp)
715+
definedBinders.remove(tp)
716+
case tp: ParamRef =>
717+
assert(definedBinders.get(tp.binder) != null, s"orphan param: ${tp.show}, hash of binder = ${System.identityHashCode(tp.binder)}, tree = ${tree.show}, type = $tp0")
718+
case tp: TypeVar =>
719+
assert(tp.isInstantiated, s"Uninstantiated type variable: ${tp.show}, tree = ${tree.show}")
720+
apply(tp.underlying)
721+
case _ =>
722+
mapOver(tp)
723+
}
724+
tp
725+
}
726+
}.apply(tp0)
726727
}
728+
729+
class LocalChecker(phasesToCheck: Seq[Phase]) extends Checker(phasesToCheck: Seq[Phase]) {
730+
override def assertDefined(tree: untpd.Tree)(using Context): Unit =
731+
() // TODO: check locally defined symbols
732+
// if (tree.symbol.maybeOwner.isTerm) {
733+
// val sym = tree.symbol
734+
// assert(
735+
// nowDefinedSyms.contains(sym) || patBoundSyms.contains(sym),
736+
// i"undefined symbol ${sym} at line " + tree.srcPos.line
737+
// )
738+
739+
// if (!ctx.phase.patternTranslated)
740+
// assert(
741+
// !sym.isPatternBound || patBoundSyms.contains(sym),
742+
// i"sym.isPatternBound => patBoundSyms.contains(sym) is broken, sym = $sym, line " + tree.srcPos.line
743+
// )
744+
// }
745+
746+
/** - Check that TypeParamRefs and MethodParams refer to an enclosing type.
747+
*/
748+
override def checkNoOrphans(tp0: Type, tree: untpd.Tree = untpd.EmptyTree)(using Context): Type =
749+
tp0 // TODO: Can we check part of this?
750+
// new TypeMap() {
751+
// val definedBinders = new java.util.IdentityHashMap[Type, Any]
752+
// def apply(tp: Type): Type = {
753+
// tp match {
754+
// case tp: BindingType =>
755+
// definedBinders.put(tp, tp)
756+
// mapOver(tp)
757+
// definedBinders.remove(tp)
758+
// case tp: ParamRef =>
759+
// assert(definedBinders.get(tp.binder) != null, s"orphan param: ${tp.show}, hash of binder = ${System.identityHashCode(tp.binder)}, tree = ${tree.show}, type = $tp0")
760+
// case tp: TypeVar =>
761+
// assert(tp.isInstantiated, s"Uninstantiated type variable: ${tp.show}, tree = ${tree.show}")
762+
// apply(tp.underlying)
763+
// case _ =>
764+
// mapOver(tp)
765+
// }
766+
// tp
767+
// }
768+
// }.apply(tp0)
769+
}
770+
771+
def checkMacroGeneratedTree(original: tpd.Tree, expansion: tpd.Tree)(using Context): Unit =
772+
if ctx.settings.XcheckMacros.value then
773+
val checkingCtx = ctx
774+
.fresh
775+
.addMode(Mode.ImplicitsEnabled)
776+
.setReporter(new ThrowingReporter(ctx.reporter)) // TODO report as errors
777+
val treeChecker = new LocalChecker(Nil) // TODO enable previous phase post-conditions
778+
try treeChecker.typed(expansion)(using checkingCtx)
779+
catch
780+
case err: java.lang.AssertionError =>
781+
report.error(
782+
em"""Malformed tree was found while expanding macro with -Xcheck-macros.
783+
|
784+
|Macro was:
785+
|${original}
786+
|
787+
|The macro returned:
788+
|${expansion}
789+
|
790+
|Error:
791+
|${err.getMessage}
792+
|
793+
|""",
794+
original
795+
)
796+
727797
}

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
298298

299299
object ValDef extends ValDefModule:
300300
def apply(symbol: Symbol, rhs: Option[Term]): ValDef =
301-
tpd.ValDef(symbol.asTerm, xCheckMacroedOwners(xCheckMacroValidExpr(rhs), symbol).getOrElse(tpd.EmptyTree))
301+
withDefaultPos(tpd.ValDef(symbol.asTerm, xCheckMacroedOwners(xCheckMacroValidExpr(rhs), symbol).getOrElse(tpd.EmptyTree)))
302302
def copy(original: Tree)(name: String, tpt: TypeTree, rhs: Option[Term]): ValDef =
303303
tpd.cpy.ValDef(original)(name.toTermName, tpt, xCheckMacroedOwners(xCheckMacroValidExpr(rhs), original.symbol).getOrElse(tpd.EmptyTree))
304304
def unapply(vdef: ValDef): (String, TypeTree, Option[Term]) =
@@ -1474,7 +1474,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
14741474

14751475
object Bind extends BindModule:
14761476
def apply(sym: Symbol, pattern: Tree): Bind =
1477-
tpd.Bind(sym, pattern)
1477+
withDefaultPos(tpd.Bind(sym, pattern))
14781478
def copy(original: Tree)(name: String, pattern: Tree): Bind =
14791479
withDefaultPos(tpd.cpy.Bind(original)(name.toTermName, pattern))
14801480
def unapply(pattern: Bind): (String, Tree) =

0 commit comments

Comments
 (0)