Skip to content

Commit 30b1338

Browse files
authored
Merge pull request #307 from scala/backport-lts-3.3-22240
Backport "Make Ref.apply() return trees usable in the largest scope possible" to 3.3 LTS
2 parents e430724 + adab04a commit 30b1338

File tree

8 files changed

+105
-3
lines changed

8 files changed

+105
-3
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
477477
def ref(sym: Symbol)(using Context): Tree =
478478
ref(NamedType(sym.owner.thisType, sym.name, sym.denot))
479479

480+
// Like `ref`, but avoids wrapping innermost module class references with This(),
481+
// instead mapping those to objects, so that the resulting trees can be used in
482+
// largest scope possible (method added for macros)
483+
def generalisedRef(sym: Symbol)(using Context): Tree =
484+
// Removes ThisType from inner module classes, replacing those with references to objects
485+
def simplifyThisTypePrefix(tpe: Type)(using Context): Type =
486+
tpe match
487+
case ThisType(tref @ TypeRef(prefix, _)) if tref.symbol.flags.is(Module) =>
488+
TermRef(simplifyThisTypePrefix(prefix), tref.symbol.companionModule)
489+
case TypeRef(prefix, designator) =>
490+
TypeRef(simplifyThisTypePrefix(prefix), designator)
491+
case _ =>
492+
tpe
493+
ref(NamedType(simplifyThisTypePrefix(sym.owner.thisType), sym.name, sym.denot))
494+
480495
private def followOuterLinks(t: Tree)(using Context) = t match {
481496
case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) =>
482497
// after erasure outer paths should be respected

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1945,7 +1945,6 @@ object SymDenotations {
19451945

19461946
/** The this-type depends on the kind of class:
19471947
* - for a package class `p`: ThisType(TypeRef(Noprefix, p))
1948-
* - for a module class `m`: A term ref to m's source module.
19491948
* - for all other classes `c` with owner `o`: ThisType(TypeRef(o.thisType, c))
19501949
*/
19511950
override def thisType(using Context): Type = {

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
454454
withDefaultPos(tpd.ref(tp).asInstanceOf[tpd.RefTree])
455455
def apply(sym: Symbol): Ref =
456456
assert(sym.isTerm, s"expected a term symbol, but received $sym")
457-
val refTree = tpd.ref(sym) match
457+
val refTree = tpd.generalisedRef(sym) match
458458
case t @ tpd.This(ident) => // not a RefTree, so we need to work around this - issue #19732
459459
// ident in `This` can be a TypeIdent of sym, so we manually prepare the ref here,
460460
// knowing that the owner is actually `This`.

library/src/scala/quoted/Quotes.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,10 +878,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
878878
* If `sym` refers to a class member `foo` in class `C`,
879879
* returns a tree representing `C.this.foo`.
880880
*
881+
* If `sym` refers to an object member `foo` in object C, itself in prefix
882+
* `pre` (which might include `.this`, if it contains a class),
883+
* returns `pre.C.foo`.
884+
*
881885
* If `sym` refers to a local definition `foo`, returns
882886
* a tree representing `foo`.
883887
*
884-
* @note In both cases, the constructed tree should only
888+
* @note In all cases, the constructed tree should only
885889
* be spliced into the places where such accesses make sense.
886890
* For example, it is incorrect to have `C.this.foo` outside
887891
* the class body of `C`, or have `foo` outside the lexical
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted.*
2+
3+
object Macros {
4+
def valuesImpl[A: Type](using Quotes): Expr[Any] = {
5+
import quotes.reflect.*
6+
val symbol = TypeRepr.of[A].typeSymbol.fieldMember("value")
7+
Ref(symbol).asExprOf[Any]
8+
}
9+
10+
transparent inline def values[A]: Any = ${ valuesImpl[A] }
11+
}

tests/pos-macros/i20349a/Test_2.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
class Cls{
3+
object a {
4+
object domain {
5+
val value = ""
6+
}
7+
}
8+
Macros.values[a.domain.type]
9+
}
10+
11+
object Test {
12+
lazy val script = new Cls()
13+
def main(args: Array[String]): Unit =
14+
val _ = script.hashCode()
15+
???
16+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import scala.quoted.*
2+
3+
object Macros {
4+
5+
6+
def valuesImpl[A: Type](using quotes: Quotes): Expr[List[A]] = {
7+
import quotes.*, quotes.reflect.*
8+
9+
extension (sym: Symbol)
10+
def isPublic: Boolean = !sym.isNoSymbol &&
11+
!(sym.flags.is(Flags.Private) || sym.flags.is(Flags.PrivateLocal) || sym.flags.is(Flags.Protected) ||
12+
sym.privateWithin.isDefined || sym.protectedWithin.isDefined)
13+
14+
def isSealed[A: Type]: Boolean =
15+
TypeRepr.of[A].typeSymbol.flags.is(Flags.Sealed)
16+
17+
def extractSealedSubtypes[A: Type]: List[Type[?]] = {
18+
def extractRecursively(sym: Symbol): List[Symbol] =
19+
if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively)
20+
else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol)
21+
else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass)
22+
else List(sym)
23+
24+
extractRecursively(TypeRepr.of[A].typeSymbol).distinct.map(typeSymbol =>
25+
typeSymbol.typeRef.asType
26+
)
27+
}
28+
29+
if isSealed[A] then {
30+
val refs = extractSealedSubtypes[A].flatMap { tpe =>
31+
val sym = TypeRepr.of(using tpe).typeSymbol
32+
val isCaseVal = sym.isPublic && sym.flags
33+
.is(Flags.Case | Flags.Enum) && (sym.flags.is(Flags.JavaStatic) || sym.flags.is(Flags.StableRealizable))
34+
35+
if (isCaseVal) then List(Ref(sym).asExprOf[A])
36+
else Nil
37+
}
38+
Expr.ofList(refs)
39+
} else '{ Nil }
40+
}
41+
42+
inline def values[A]: List[A] = ${ valuesImpl[A] }
43+
}

tests/pos-macros/i20349b/Test_2.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Test {
2+
object domain {
3+
enum PaymentMethod:
4+
case PayPal(email: String)
5+
case Card(digits: Long, name: String)
6+
case Cash
7+
}
8+
println(Macros.values[domain.PaymentMethod])
9+
}
10+
object Test {
11+
lazy val script = new Test()
12+
def main(args: Array[String]): Unit =
13+
val _ = script.hashCode()
14+
}

0 commit comments

Comments
 (0)