Skip to content

Backport "Make Ref.apply() return trees usable in the largest scope possible" to 3.3 LTS #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,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
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1945,7 +1945,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 = {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,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`.
Expand Down
6 changes: 5 additions & 1 deletion library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -878,10 +878,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
Expand Down
11 changes: 11 additions & 0 deletions tests/pos-macros/i20349a/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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] }
}
16 changes: 16 additions & 0 deletions tests/pos-macros/i20349a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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()
???
}
43 changes: 43 additions & 0 deletions tests/pos-macros/i20349b/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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] }
}
14 changes: 14 additions & 0 deletions tests/pos-macros/i20349b/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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()
}