diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 55021bf50ace..37e3eaf1da22 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -487,6 +487,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def ref(sym: Symbol)(using Context): Tree = ref(NamedType(sym.owner.thisType, sym.name, sym.denot)) + // Like `ref`, but avoids wrapping innermost module class references with This(), + // instead mapping those to objects, so that the resulting trees can be used in + // largest scope possible (method added for macros) + def generalisedRef(sym: Symbol)(using Context): Tree = + // Removes ThisType from inner module classes, replacing those with references to objects + def simplifyThisTypePrefix(tpe: Type)(using Context): Type = + tpe match + case ThisType(tref @ TypeRef(prefix, _)) if tref.symbol.flags.is(Module) => + TermRef(simplifyThisTypePrefix(prefix), tref.symbol.companionModule) + case TypeRef(prefix, designator) => + TypeRef(simplifyThisTypePrefix(prefix), designator) + case _ => + tpe + ref(NamedType(simplifyThisTypePrefix(sym.owner.thisType), sym.name, sym.denot)) + private def followOuterLinks(t: Tree)(using Context) = t match { case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) => // after erasure outer paths should be respected diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f54b8a62fa25..9fc56102118c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1950,7 +1950,6 @@ object SymDenotations { /** The this-type depends on the kind of class: * - for a package class `p`: ThisType(TypeRef(Noprefix, p)) - * - for a module class `m`: A term ref to m's source module. * - for all other classes `c` with owner `o`: ThisType(TypeRef(o.thisType, c)) */ override def thisType(using Context): Type = { diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 22be293c3562..e067327adfd5 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -473,7 +473,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler withDefaultPos(tpd.ref(tp).asInstanceOf[tpd.RefTree]) def apply(sym: Symbol): Ref = assert(sym.isTerm, s"expected a term symbol, but received $sym") - val refTree = tpd.ref(sym) match + val refTree = tpd.generalisedRef(sym) match case t @ tpd.This(ident) => // not a RefTree, so we need to work around this - issue #19732 // ident in `This` can be a TypeIdent of sym, so we manually prepare the ref here, // knowing that the owner is actually `This`. diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7a98d6f6f761..605916724694 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -907,10 +907,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * If `sym` refers to a class member `foo` in class `C`, * returns a tree representing `C.this.foo`. * + * If `sym` refers to an object member `foo` in object C, itself in prefix + * `pre` (which might include `.this`, if it contains a class), + * returns `pre.C.foo`. + * * If `sym` refers to a local definition `foo`, returns * a tree representing `foo`. * - * @note In both cases, the constructed tree should only + * @note In all cases, the constructed tree should only * be spliced into the places where such accesses make sense. * For example, it is incorrect to have `C.this.foo` outside * the class body of `C`, or have `foo` outside the lexical diff --git a/tests/pos-macros/i20349a/Macro_1.scala b/tests/pos-macros/i20349a/Macro_1.scala new file mode 100644 index 000000000000..41c5083b328c --- /dev/null +++ b/tests/pos-macros/i20349a/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +object Macros { + def valuesImpl[A: Type](using Quotes): Expr[Any] = { + import quotes.reflect.* + val symbol = TypeRepr.of[A].typeSymbol.fieldMember("value") + Ref(symbol).asExprOf[Any] + } + + transparent inline def values[A]: Any = ${ valuesImpl[A] } +} diff --git a/tests/pos-macros/i20349a/Test_2.scala b/tests/pos-macros/i20349a/Test_2.scala new file mode 100644 index 000000000000..4569c501e055 --- /dev/null +++ b/tests/pos-macros/i20349a/Test_2.scala @@ -0,0 +1,16 @@ + +class Cls{ + object a { + object domain { + val value = "" + } + } + Macros.values[a.domain.type] +} + +object Test { + lazy val script = new Cls() + def main(args: Array[String]): Unit = + val _ = script.hashCode() + ??? +} diff --git a/tests/pos-macros/i20349b/Macro_1.scala b/tests/pos-macros/i20349b/Macro_1.scala new file mode 100644 index 000000000000..413274b4b516 --- /dev/null +++ b/tests/pos-macros/i20349b/Macro_1.scala @@ -0,0 +1,43 @@ +import scala.quoted.* + +object Macros { + + + def valuesImpl[A: Type](using quotes: Quotes): Expr[List[A]] = { + import quotes.*, quotes.reflect.* + + extension (sym: Symbol) + def isPublic: Boolean = !sym.isNoSymbol && + !(sym.flags.is(Flags.Private) || sym.flags.is(Flags.PrivateLocal) || sym.flags.is(Flags.Protected) || + sym.privateWithin.isDefined || sym.protectedWithin.isDefined) + + def isSealed[A: Type]: Boolean = + TypeRepr.of[A].typeSymbol.flags.is(Flags.Sealed) + + def extractSealedSubtypes[A: Type]: List[Type[?]] = { + def extractRecursively(sym: Symbol): List[Symbol] = + if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively) + else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol) + else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass) + else List(sym) + + extractRecursively(TypeRepr.of[A].typeSymbol).distinct.map(typeSymbol => + typeSymbol.typeRef.asType + ) + } + + if isSealed[A] then { + val refs = extractSealedSubtypes[A].flatMap { tpe => + val sym = TypeRepr.of(using tpe).typeSymbol + val isCaseVal = sym.isPublic && sym.flags + .is(Flags.Case | Flags.Enum) && (sym.flags.is(Flags.JavaStatic) || sym.flags.is(Flags.StableRealizable)) + + if (isCaseVal) then List(Ref(sym).asExprOf[A]) + else Nil + } + Expr.ofList(refs) + } else '{ Nil } + } + + inline def values[A]: List[A] = ${ valuesImpl[A] } +} diff --git a/tests/pos-macros/i20349b/Test_2.scala b/tests/pos-macros/i20349b/Test_2.scala new file mode 100644 index 000000000000..a392a636dc44 --- /dev/null +++ b/tests/pos-macros/i20349b/Test_2.scala @@ -0,0 +1,14 @@ +class Test { + object domain { + enum PaymentMethod: + case PayPal(email: String) + case Card(digits: Long, name: String) + case Cash + } + println(Macros.values[domain.PaymentMethod]) +} +object Test { + lazy val script = new Test() + def main(args: Array[String]): Unit = + val _ = script.hashCode() +}