Skip to content

Commit aad8f7d

Browse files
noti0na1WojciechMazur
authored andcommitted
Strip inferred retains annotation from Macro/inline call trees
[Cherry-picked ee89e58]
1 parent ec61d98 commit aad8f7d

File tree

3 files changed

+99
-11
lines changed

3 files changed

+99
-11
lines changed

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import dotty.tools.dotc.transform.MegaPhase.MiniPhase
1818
import parsing.Parsers.Parser
1919
import transform.{PostTyper, Inlining, CrossVersionChecks}
2020
import staging.StagingLevel
21+
import cc.CleanupRetains
2122

2223
import collection.mutable
2324
import reporting.{NotConstant, trace}
@@ -100,18 +101,34 @@ object Inlines:
100101
* and body that replace it.
101102
*/
102103
def inlineCall(tree: Tree)(using Context): Tree = ctx.profiler.onInlineCall(tree.symbol):
103-
if tree.symbol.denot != SymDenotations.NoDenotation
104-
&& tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass
104+
105+
/** Strip @retains annotations from inferred types in the call tree */
106+
val stripRetains = CleanupRetains()
107+
val stripper = new TreeTypeMap(
108+
typeMap = stripRetains,
109+
treeMap = {
110+
case tree: InferredTypeTree =>
111+
val stripped = stripRetains(tree.tpe)
112+
if stripped ne tree.tpe then tree.withType(stripped)
113+
else tree
114+
case tree => tree
115+
}
116+
)
117+
118+
val tree0 = stripper.transform(tree)
119+
120+
if tree0.symbol.denot != SymDenotations.NoDenotation
121+
&& tree0.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass
105122
then
106-
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
107-
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
123+
if (tree0.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree0)
124+
if (tree0.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree0)
108125

109126
if ctx.isAfterTyper then
110127
// During typer we wait with cross version checks until PostTyper, in order
111128
// not to provoke cyclic references. See i16116 for a test case.
112-
CrossVersionChecks.checkRef(tree.symbol, tree.srcPos)
129+
CrossVersionChecks.checkRef(tree0.symbol, tree0.srcPos)
113130

114-
if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition
131+
if tree0.symbol.isConstructor then return tree // error already reported for the inline constructor definition
115132

116133
/** Set the position of all trees logically contained in the expansion of
117134
* inlined call `call` to the position of `call`. This transform is necessary
@@ -159,17 +176,17 @@ object Inlines:
159176
tree
160177
}
161178

162-
// assertAllPositioned(tree) // debug
163-
val tree1 = liftBindings(tree, identity)
179+
// assertAllPositioned(tree0) // debug
180+
val tree1 = liftBindings(tree0, identity)
164181
val tree2 =
165182
if bindings.nonEmpty then
166-
cpy.Block(tree)(bindings.toList, inlineCall(tree1))
183+
cpy.Block(tree0)(bindings.toList, inlineCall(tree1))
167184
else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then
168185
val body =
169-
try bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
186+
try bodyToInline(tree0.symbol) // can typecheck the tree and thereby produce errors
170187
catch case _: MissingInlineInfo =>
171188
throw CyclicReference(ctx.owner)
172-
new InlineCall(tree).expand(body)
189+
new InlineCall(tree0).expand(body)
173190
else
174191
ctx.base.stopInlining = true
175192
val (reason, setting) =
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// ReproMacro.scala
2+
// Minimal macro that reproduces "Coll[Int] is not a legal path" error
3+
// when creating new ValDefs with types containing @retains annotations
4+
5+
import scala.quoted.*
6+
7+
object ReproMacro:
8+
9+
transparent inline def transform[A](inline expr: A): A = ${ transformImpl[A]('expr) }
10+
11+
private def transformImpl[A: Type](expr: Expr[A])(using Quotes): Expr[A] =
12+
import quotes.reflect.*
13+
14+
val term = expr.asTerm
15+
val owner = Symbol.spliceOwner
16+
17+
// Rebuild the block, creating NEW ValDefs with the same types
18+
val result = rebuildBlock(term, owner)
19+
20+
result.asExprOf[A]
21+
22+
private def rebuildBlock(using Quotes)(term: quotes.reflect.Term, owner: quotes.reflect.Symbol): quotes.reflect.Term =
23+
import quotes.reflect.*
24+
25+
term match
26+
case Block(stats, expr) =>
27+
var symbolMap = Map.empty[Symbol, Symbol]
28+
29+
val newStats = stats.map {
30+
case vd @ ValDef(name, tpt, Some(rhs)) =>
31+
// Create a new symbol with the same type - this causes the error
32+
// when tpt.tpe contains AnnotatedType(LazyRef(Coll[Int]), @retains(...))
33+
val newSym = Symbol.newVal(owner, name, tpt.tpe, Flags.EmptyFlags, Symbol.noSymbol)
34+
symbolMap = symbolMap + (vd.symbol -> newSym)
35+
ValDef(newSym, Some(substituteRefs(rhs, symbolMap)))
36+
case other => other
37+
}
38+
39+
Block(newStats, substituteRefs(expr, symbolMap))
40+
41+
case Inlined(call, bindings, expansion) =>
42+
Inlined(call, bindings, rebuildBlock(expansion, owner))
43+
44+
case _ => term
45+
46+
private def substituteRefs(using Quotes)(term: quotes.reflect.Term, map: Map[quotes.reflect.Symbol, quotes.reflect.Symbol]): quotes.reflect.Term =
47+
import quotes.reflect.*
48+
49+
val mapper = new TreeMap:
50+
override def transformTerm(t: Term)(owner: Symbol): Term = t match
51+
case Ident(name) if map.contains(t.symbol) =>
52+
Ref(map(t.symbol))
53+
case _ => super.transformTerm(t)(owner)
54+
55+
mapper.transformTerm(term)(Symbol.spliceOwner)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// ReproTest.scala
2+
// Test case: abstract type parameter Coll[Int] with @retains annotation
3+
// causes "not a legal path" error when macro creates new ValDef
4+
5+
import scala.collection.IterableOps
6+
7+
def reproTest[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]]: Unit =
8+
def xsValues: Coll[Int] = ???
9+
10+
// The .span method returns (Coll[Int], Coll[Int])
11+
// With capture checking, these types get @retains annotations
12+
// When the macro creates new ValDefs with these types, compilation fails
13+
ReproMacro.transform {
14+
val (take, drop) = xsValues.span(???)
15+
take.toSeq
16+
}

0 commit comments

Comments
 (0)