Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions compiler/src/dotty/tools/dotc/inlines/Inlines.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import dotty.tools.dotc.transform.MegaPhase.MiniPhase
import parsing.Parsers.Parser
import transform.{PostTyper, Inlining, CrossVersionChecks}
import staging.StagingLevel
import cc.CleanupRetains

import collection.mutable
import reporting.{NotConstant, trace}
Expand Down Expand Up @@ -100,18 +101,33 @@ object Inlines:
* and body that replace it.
*/
def inlineCall(tree: Tree)(using Context): Tree = ctx.profiler.onInlineCall(tree.symbol):
if tree.symbol.denot != SymDenotations.NoDenotation
&& tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass

/** Strip @retains annotations from inferred types in the call tree */
val stripRetains = CleanupRetains()
val stripper = new TreeTypeMap(
treeMap = {
case tree: InferredTypeTree =>
val stripped = stripRetains(tree.tpe)
if stripped ne tree.tpe then tree.withType(stripped)
else tree
case tree => tree
}
)

val tree0 = stripper.transform(tree)

if tree0.symbol.denot.exists
&& tree0.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass
then
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
if (tree0.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree0)
if (tree0.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree0)

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

if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition
if tree0.symbol.isConstructor then return tree // error already reported for the inline constructor definition

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

// assertAllPositioned(tree) // debug
val tree1 = liftBindings(tree, identity)
// assertAllPositioned(tree0) // debug
val tree1 = liftBindings(tree0, identity)
val tree2 =
if bindings.nonEmpty then
cpy.Block(tree)(bindings.toList, inlineCall(tree1))
cpy.Block(tree0)(bindings.toList, inlineCall(tree1))
else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then
val body =
try bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
try bodyToInline(tree0.symbol) // can typecheck the tree and thereby produce errors
catch case _: MissingInlineInfo =>
throw CyclicReference(ctx.owner)
new InlineCall(tree).expand(body)
new InlineCall(tree0).expand(body)
else
ctx.base.stopInlining = true
val (reason, setting) =
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CompilationTests {
compileFile("tests/pos-special/utf8encoded.scala", defaultOptions.and("-encoding", "UTF8")),
compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")),
compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")),
compileDir("tests/pos-special/i24547", defaultOptions.without("-Ycheck:all")),
// Run tests for legacy lazy vals
compileFilesInDir("tests/pos", defaultOptions.and("-Wsafe-init", "-Ylegacy-lazy-vals", "-Ycheck-constraint-deps"), FileFilter.include(TestSources.posLazyValsAllowlist)),
compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")),
Expand Down
55 changes: 55 additions & 0 deletions tests/pos-special/i24547/ReproMacro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// ReproMacro.scala
// Minimal macro that reproduces "Coll[Int] is not a legal path" error
// when creating new ValDefs with types containing @retains annotations

import scala.quoted.*

object ReproMacro:

transparent inline def transform[A](inline expr: A): A = ${ transformImpl[A]('expr) }

private def transformImpl[A: Type](expr: Expr[A])(using Quotes): Expr[A] =
import quotes.reflect.*

val term = expr.asTerm
val owner = Symbol.spliceOwner

// Rebuild the block, creating NEW ValDefs with the same types
val result = rebuildBlock(term, owner)

result.asExprOf[A]

private def rebuildBlock(using Quotes)(term: quotes.reflect.Term, owner: quotes.reflect.Symbol): quotes.reflect.Term =
import quotes.reflect.*

term match
case Block(stats, expr) =>
var symbolMap = Map.empty[Symbol, Symbol]

val newStats = stats.map {
case vd @ ValDef(name, tpt, Some(rhs)) =>
// Create a new symbol with the same type - this causes the error
// when tpt.tpe contains AnnotatedType(LazyRef(Coll[Int]), @retains(...))
val newSym = Symbol.newVal(owner, name, tpt.tpe, Flags.EmptyFlags, Symbol.noSymbol)
symbolMap = symbolMap + (vd.symbol -> newSym)
ValDef(newSym, Some(substituteRefs(rhs, symbolMap)))
case other => other
}

Block(newStats, substituteRefs(expr, symbolMap))

case Inlined(call, bindings, expansion) =>
Inlined(call, bindings, rebuildBlock(expansion, owner))

case _ => term

private def substituteRefs(using Quotes)(term: quotes.reflect.Term, map: Map[quotes.reflect.Symbol, quotes.reflect.Symbol]): quotes.reflect.Term =
import quotes.reflect.*

val mapper = new TreeMap:
override def transformTerm(t: Term)(owner: Symbol): Term = t match
case Ident(name) if map.contains(t.symbol) =>
Ref(map(t.symbol))
case _ => super.transformTerm(t)(owner)

mapper.transformTerm(term)(Symbol.spliceOwner)
19 changes: 19 additions & 0 deletions tests/pos-special/i24547/ReproTest_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// ReproTest.scala
// Test case: abstract type parameter Coll[Int] with @retains annotation
// causes "not a legal path" error when macro creates new ValDef

// TODO: there are some other issues with this test case, so it is currently disabled
// for `Ycheck:all`. We should move it back to `pos-macro` once those issues are fixed.

import scala.collection.IterableOps

def reproTest[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]]: Unit =
def xsValues: Coll[Int] = ???

// The .span method returns (Coll[Int], Coll[Int])
// With capture checking, these types get @retains annotations
// When the macro creates new ValDefs with these types, compilation fails
ReproMacro.transform {
val (take, drop) = xsValues.span(???)
take.toSeq
}
Loading