Skip to content
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

Do not expose ClassInfo in memberType in reflect API #22402

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
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())
Loading