Skip to content

Commit adab04a

Browse files
jchybtgodzik
authored andcommitted
Make Ref.apply() return trees usable in the largest scope possible
Previously for symbols contained in objects (prefixed by, let's say, 'pre'), we would return: * an Ident if pre contained only static object and packages; * Select(This(moduleClassSymbol), sym) if a prefix contained a class. However, this meant that in the second case, the generated tree would require the macro to be expanded inside of the object, even though it should be enough to just expand inside of the innermost class. This was unexpected and confusing, so it was changed to not return innermost module classes wrapped with This(). [Cherry-picked 238ba45]
1 parent f65d04f commit adab04a

File tree

8 files changed

+105
-3
lines changed

8 files changed

+105
-3
lines changed

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

+15
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

-1
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

+1-1
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

+5-1
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
+11
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

+16
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+
}
+43
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

+14
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)