Skip to content

Commit 7fc43a0

Browse files
authored
Fix #24547: Strip inferred retains annotation from macro/inline call trees (#24560)
Fix #24547 Strip inferred retains annotation from macro/inline call trees
2 parents 93408e8 + 83735b9 commit 7fc43a0

File tree

4 files changed

+102
-11
lines changed

4 files changed

+102
-11
lines changed

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

Lines changed: 27 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,33 @@ 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+
treeMap = {
109+
case tree: InferredTypeTree =>
110+
val stripped = stripRetains(tree.tpe)
111+
if stripped ne tree.tpe then tree.withType(stripped)
112+
else tree
113+
case tree => tree
114+
}
115+
)
116+
117+
val tree0 = stripper.transform(tree)
118+
119+
if tree0.symbol.denot.exists
120+
&& tree0.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass
105121
then
106-
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
107-
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
122+
if (tree0.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree0)
123+
if (tree0.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree0)
108124

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

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

116132
/** Set the position of all trees logically contained in the expansion of
117133
* inlined call `call` to the position of `call`. This transform is necessary
@@ -159,17 +175,17 @@ object Inlines:
159175
tree
160176
}
161177

162-
// assertAllPositioned(tree) // debug
163-
val tree1 = liftBindings(tree, identity)
178+
// assertAllPositioned(tree0) // debug
179+
val tree1 = liftBindings(tree0, identity)
164180
val tree2 =
165181
if bindings.nonEmpty then
166-
cpy.Block(tree)(bindings.toList, inlineCall(tree1))
182+
cpy.Block(tree0)(bindings.toList, inlineCall(tree1))
167183
else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then
168184
val body =
169-
try bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
185+
try bodyToInline(tree0.symbol) // can typecheck the tree and thereby produce errors
170186
catch case _: MissingInlineInfo =>
171187
throw CyclicReference(ctx.owner)
172-
new InlineCall(tree).expand(body)
188+
new InlineCall(tree0).expand(body)
173189
else
174190
ctx.base.stopInlining = true
175191
val (reason, setting) =

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class CompilationTests {
4141
compileFile("tests/pos-special/utf8encoded.scala", defaultOptions.and("-encoding", "UTF8")),
4242
compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")),
4343
compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")),
44+
compileDir("tests/pos-special/i24547", defaultOptions.without("-Ycheck:all")),
4445
// Run tests for legacy lazy vals
4546
compileFilesInDir("tests/pos", defaultOptions.and("-Wsafe-init", "-Ylegacy-lazy-vals", "-Ycheck-constraint-deps"), FileFilter.include(TestSources.posLazyValsAllowlist)),
4647
compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")),
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
// TODO: there are some other issues with this test case, so it is currently disabled
6+
// for `Ycheck:all`. We should move it back to `pos-macro` once those issues are fixed.
7+
8+
import scala.collection.IterableOps
9+
10+
def reproTest[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]]: Unit =
11+
def xsValues: Coll[Int] = ???
12+
13+
// The .span method returns (Coll[Int], Coll[Int])
14+
// With capture checking, these types get @retains annotations
15+
// When the macro creates new ValDefs with these types, compilation fails
16+
ReproMacro.transform {
17+
val (take, drop) = xsValues.span(???)
18+
take.toSeq
19+
}

0 commit comments

Comments
 (0)