From 11d4295e68fbb27a38bf56ad1bd13d8f9052c618 Mon Sep 17 00:00:00 2001 From: Jan Chyb <48855024+jchyb@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:32:05 +0100 Subject: [PATCH] Replace symbol traversal with tree traversal when finding top level experimentals (#21827) Aims to fix stale symbol errors caused by the symbol traversal after suspending by a macro --- .../src/dotty/tools/dotc/typer/Checking.scala | 30 +++++++++---------- tests/pos-macros/i21802/Macro.scala | 15 ++++++++++ tests/pos-macros/i21802/Test.scala | 13 ++++++++ 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 tests/pos-macros/i21802/Macro.scala create mode 100644 tests/pos-macros/i21802/Test.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 761e7cdab37c..1cd531046753 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -806,20 +806,20 @@ object Checking { * */ def checkAndAdaptExperimentalImports(trees: List[Tree])(using Context): Unit = - def nonExperimentalTopLevelDefs(pack: Symbol): Iterator[Symbol] = - def isNonExperimentalTopLevelDefinition(sym: Symbol) = - sym.isDefinedInCurrentRun - && sym.source == ctx.compilationUnit.source - && !sym.isConstructor // not constructor of package object - && !sym.is(Package) && !sym.name.isPackageObjectName - && !sym.isExperimental - - pack.info.decls.toList.iterator.flatMap: sym => - if sym.isClass && (sym.is(Package) || sym.isPackageObject) then - nonExperimentalTopLevelDefs(sym) - else if isNonExperimentalTopLevelDefinition(sym) then - sym :: Nil - else Nil + def nonExperimentalTopLevelDefs(): List[Symbol] = + new TreeAccumulator[List[Symbol]] { + override def apply(x: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = + def addIfNotExperimental(sym: Symbol) = + if !sym.isExperimental then sym :: x + else x + tree match { + case tpd.PackageDef(_, contents) => apply(x, contents) + case typeDef @ tpd.TypeDef(_, temp: Template) if typeDef.symbol.isPackageObject => + apply(x, temp.body) + case mdef: tpd.MemberDef => addIfNotExperimental(mdef.symbol) + case _ => x + } + }.apply(Nil, ctx.compilationUnit.tpdTree) def unitExperimentalLanguageImports = def isAllowedImport(sel: untpd.ImportSelector) = @@ -837,7 +837,7 @@ object Checking { if ctx.owner.is(Package) || ctx.owner.name.startsWith(str.REPL_SESSION_LINE) then def markTopLevelDefsAsExperimental(why: String): Unit = - for sym <- nonExperimentalTopLevelDefs(ctx.owner) do + for sym <- nonExperimentalTopLevelDefs() do sym.addAnnotation(ExperimentalAnnotation(s"Added by $why", sym.span)) unitExperimentalLanguageImports match diff --git a/tests/pos-macros/i21802/Macro.scala b/tests/pos-macros/i21802/Macro.scala new file mode 100644 index 000000000000..e2eb1287c727 --- /dev/null +++ b/tests/pos-macros/i21802/Macro.scala @@ -0,0 +1,15 @@ +class MetricsGroup[A] +object MetricsGroup: + import scala.quoted.* + + transparent inline final def refine[A]: MetricsGroup[A] = + ${ refineImpl[A] } + + private def refineImpl[A](using qctx: Quotes, tpe: Type[A]): Expr[MetricsGroup[A]] = + import qctx.reflect.* + + val mt = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[A]) + val tpe = Refinement(TypeRepr.of[MetricsGroup[A]], "apply", mt).asType + tpe match + case '[tpe] => + '{ MetricsGroup[A]().asInstanceOf[MetricsGroup[A] & tpe] } diff --git a/tests/pos-macros/i21802/Test.scala b/tests/pos-macros/i21802/Test.scala new file mode 100644 index 000000000000..70063653c43c --- /dev/null +++ b/tests/pos-macros/i21802/Test.scala @@ -0,0 +1,13 @@ +//> using options -experimental -Ydebug + +class ProbeFailedException(cause: Exception) extends Exception(cause) +trait Probing: + self: Metrics => + val probeFailureCounter: MetricsGroup[Counter] = + counters("ustats_probe_failures_count").labelled + + +trait Counter +class Metrics: + class counters(name: String): + transparent inline final def labelled: MetricsGroup[Counter] = MetricsGroup.refine[Counter]