From 9121a9d4d71a931fdfc1c68975656034feae9dbe Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 27 Nov 2024 14:41:22 +0100 Subject: [PATCH] `substSym` replaces owners of `RefinementClassSymbol`s Given ``` class C { def f = new { def g = new { def h = 1 } } } ``` the type of `C.f` is `AnyRef { def g: AnyRef { def h: Int } }`. The inner `RefinedType` has as `typeSymbol` a `RefinementClassSymbol`. Its owner points to `<$anon>.g`, the symbol for method `g` in the anonymous class. Thus, the anonymous class symbol escapes its scope, the type of `f` should not contain any references to it. Instead, the `RefinementClassSymbol` of the inner refinement should have symbol `g` from the scope of the outer refinement as its owner. --------- Context: there's reasons to think that this can cause overcompilation in zinc. Or maybe undercompilation. In the xsbt-dependency phase, when a reference to `f` is processed, the phase calls `addTypeDependencies` on the the type of `f`. The `addClassDependency` method goes to the enclosing class, that way we end up adding a dependency on the `<$anon>` class. We get to `externalDependency` in zinc and eventually to `lookupAnalyzedClass` https://github.com/sbt/zinc/blob/v1.10.4/internal/zinc-core/src/main/scala/sbt/internal/inc/Lookup.scala#L56 Here `analysis.relations.productClassName.reverse(binaryClassName)` finds nothing as `relations.productClassName` doesn't contain anonymous classes. --- .../scala/reflect/internal/Types.scala | 8 ++--- .../scala/reflect/internal/tpe/TypeMaps.scala | 4 +++ test/files/run/substSymRefinementOwner.check | 31 +++++++++++++++++++ test/files/run/substSymRefinementOwner.scala | 19 ++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 test/files/run/substSymRefinementOwner.check create mode 100644 test/files/run/substSymRefinementOwner.scala diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 4a8ddb910df0..2470624cf4c5 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4037,13 +4037,13 @@ trait Types private[this] val copyRefinedTypeSSM: ReusableInstance[SubstSymMap] = ReusableInstance[SubstSymMap](SubstSymMap(), enabled = isCompilerUniverse) - def copyRefinedType(original: RefinedType, parents: List[Type], decls: Scope) = - if ((parents eq original.parents) && (decls eq original.decls)) original + def copyRefinedType(original: RefinedType, parents: List[Type], decls: Scope, owner: Symbol = null) = + if ((parents eq original.parents) && (decls eq original.decls) && (owner eq null)) original else { - val owner = original.typeSymbol.owner + val newOwner = if (owner != null) owner else original.typeSymbol.owner val result = if (isIntersectionTypeForLazyBaseType(original)) intersectionTypeForLazyBaseType(parents) - else refinedType(parents, owner) + else refinedType(parents, newOwner) if (! decls.isEmpty){ val syms1 = decls.toList for (sym <- syms1) diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index 4c16ffa98d25..6593910e7f10 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -817,6 +817,10 @@ private[internal] trait TypeMaps { case SingleType(pre, sym) if pre ne NoPrefix => val newSym = substFor(sym) (if (sym eq newSym) tpe else singleType(pre, newSym)).mapOver(this) + case tp: RefinedType => + val owner = tpe.typeSymbol.owner + val newOwner = substFor(owner) + (if (newOwner eq owner) tpe else copyRefinedType(tp, tp.parents, tp.decls, newOwner)).mapOver(this) case _ => super.apply(tpe) } diff --git a/test/files/run/substSymRefinementOwner.check b/test/files/run/substSymRefinementOwner.check new file mode 100644 index 000000000000..d0768e38b9bc --- /dev/null +++ b/test/files/run/substSymRefinementOwner.check @@ -0,0 +1,31 @@ + +scala> :power +Power mode enabled. :phase is at typer. +import scala.tools.nsc._, intp.global._, definitions._ +Try :help or completions for vals._ and power._ + +scala> class C { + def f = new { + def g = new { + def h = 1 + } + } +} +class C + +scala> val f = typeOf[C].decl(TermName("f")) +val f: $r.intp.global.Symbol = method f + +scala> val g = f.tpe.resultType.decls.head +val g: $r.intp.global.Symbol = method g + +scala> g.ownerChain.take(4) +val res0: List[$r.intp.global.Symbol] = List(method g, , method f, class C) + +scala> g.tpe.resultType.typeSymbol +val res1: $r.intp.global.Symbol = + +scala> g.tpe.resultType.typeSymbol.ownerChain.take(4) +val res2: List[$r.intp.global.Symbol] = List(, method g, , method f) + +scala> :quit diff --git a/test/files/run/substSymRefinementOwner.scala b/test/files/run/substSymRefinementOwner.scala new file mode 100644 index 000000000000..d7077c2d2f72 --- /dev/null +++ b/test/files/run/substSymRefinementOwner.scala @@ -0,0 +1,19 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = + """:power + |class C { + | def f = new { + | def g = new { + | def h = 1 + | } + | } + |} + |val f = typeOf[C].decl(TermName("f")) + |val g = f.tpe.resultType.decls.head + |g.ownerChain.take(4) + |g.tpe.resultType.typeSymbol + |g.tpe.resultType.typeSymbol.ownerChain.take(4) + |""".stripMargin +}