Skip to content

Commit

Permalink
Do not expose ClassInfo in memberType in reflect API
Browse files Browse the repository at this point in the history
  • Loading branch information
jchyb committed Jan 17, 2025
1 parent 4219950 commit ab4390d
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 1 deletion.
28 changes: 27 additions & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,33 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
def termSymbol: Symbol = self.termSymbol
def isSingleton: Boolean = self.isSingleton
def memberType(member: Symbol): TypeRepr =
member.info.asSeenFrom(self, member.owner)
// This check only fails if no owner of the passed symbol is related to the `self` type
// Ideally we would just check if the symbol is a member of the type,
// but:
// A) the children of enums are not members of those enums, which is unintuitive
// B) this check was added late, risking major hard to fix regressions
// (including in compilation tests)
// Additionally, it's useful to be able to learn a type of a symbol using some prefix,
// even if it's not a direct member of that prefix, but e.g. a nested one.
def isTypePrefixingPassedMember =
import scala.util.boundary
boundary {
var checked: Symbol = member
while(checked.exists) {
if self.derivesFrom(checked)
|| self.typeSymbol.declarations.contains(checked)
|| self.typeSymbol.companionClass.declarations.contains(checked) then
boundary.break(true)
checked = checked.owner
}
boundary.break(false)
}
xCheckMacroAssert(isTypePrefixingPassedMember, s"$member is not a member of ${self.show}")
// We do not want to expose ClassInfo in the reflect API, instead we change it to a TypeRef,
// see issue #22395
member.info.asSeenFrom(self, member.owner) match
case dotc.core.Types.ClassInfo(prefix, sym, _, _, _) => prefix.select(sym)
case other => other
def baseClasses: List[Symbol] = self.baseClasses
def baseType(cls: Symbol): TypeRepr = self.baseType(cls)
def derivesFrom(cls: Symbol): Boolean = self.derivesFrom(cls)
Expand Down
17 changes: 17 additions & 0 deletions tests/neg-macros/i15159.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

-- Error: tests/neg-macros/i15159/Test_2.scala:5:16 --------------------------------------------------------------------
5 | TestMacro.test[A] // error
| ^^^^^^^^^^^^^^^^^
| Exception occurred while executing macro expansion.
| java.lang.AssertionError: class X is not a member of A
| at TestMacro$.testImpl$$anonfun$1(Macro_1.scala:8)
| at scala.collection.immutable.List.map(List.scala:247)
| at TestMacro$.testImpl(Macro_1.scala:7)
|
|---------------------------------------------------------------------------------------------------------------------
|Inline stack trace
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|This location contains code that was inlined from Macro_1.scala:3
3 | inline def test[T]: Unit = ${ testImpl[T] }
| ^^^^^^^^^^^^^^^^
---------------------------------------------------------------------------------------------------------------------
10 changes: 10 additions & 0 deletions tests/neg-macros/i15159/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import scala.quoted.*
object TestMacro:
inline def test[T]: Unit = ${ testImpl[T] }
def testImpl[T: Type](using Quotes): Expr[Unit] =
import quotes.reflect.*
val tpe = TypeRepr.of[T]
tpe.typeSymbol.children.map { childSymbol =>
tpe.memberType(childSymbol) // not a member of tpe
}
'{ () }
6 changes: 6 additions & 0 deletions tests/neg-macros/i15159/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
sealed trait A
case class X(i: Int) extends A

object Test extends App {
TestMacro.test[A] // error
}
12 changes: 12 additions & 0 deletions tests/pos-macros/i13319/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import scala.quoted._
object Macro:
inline def apply[A]: Unit = ${impl[A]}

private def impl[A: Type](using Quotes): Expr[String] =
import quotes.reflect._
val t = TypeRepr.of[A]
Expr.ofList(t.baseClasses.drop(1).filter(_.flags.is(Flags.Trait)).map { baseSymbol =>
t.memberType(baseSymbol).asType match { case '[t] => 42}
Expr("")
})
Expr("")
1 change: 1 addition & 0 deletions tests/pos-macros/i13319/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def Test = Macro[Option[String]]
1 change: 1 addition & 0 deletions tests/run-macros/i22395.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TypeRef(AppliedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),Foo),List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Int))),class Nested)
14 changes: 14 additions & 0 deletions tests/run-macros/i22395/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.quoted._

inline def test(): String = ${testImpl}
def testImpl(using Quotes) = {
import quotes.reflect._
val fooSymbol = TypeRepr.of[Foo[Int]].typeSymbol
val nestedSymbol = fooSymbol.typeMember("Nested")

Expr(TypeRepr.of[Foo[Int]].memberType(nestedSymbol).toString)
}


trait Foo[X]:
sealed abstract class Nested extends Foo[Int]
2 changes: 2 additions & 0 deletions tests/run-macros/i22395/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@main def Test =
println(test())

0 comments on commit ab4390d

Please sign in to comment.