From ab4390d67aa498fb1bab7e1e77eda0976f0687de Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Fri, 17 Jan 2025 17:58:07 +0100 Subject: [PATCH] Do not expose ClassInfo in memberType in reflect API --- .../quoted/runtime/impl/QuotesImpl.scala | 28 ++++++++++++++++++- tests/neg-macros/i15159.check | 17 +++++++++++ tests/neg-macros/i15159/Macro_1.scala | 10 +++++++ tests/neg-macros/i15159/Test_2.scala | 6 ++++ tests/pos-macros/i13319/Macro_1.scala | 12 ++++++++ tests/pos-macros/i13319/Test_2.scala | 1 + tests/run-macros/i22395.check | 1 + tests/run-macros/i22395/Macro_1.scala | 14 ++++++++++ tests/run-macros/i22395/Test_2.scala | 2 ++ 9 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/neg-macros/i15159.check create mode 100644 tests/neg-macros/i15159/Macro_1.scala create mode 100644 tests/neg-macros/i15159/Test_2.scala create mode 100644 tests/pos-macros/i13319/Macro_1.scala create mode 100644 tests/pos-macros/i13319/Test_2.scala create mode 100644 tests/run-macros/i22395.check create mode 100644 tests/run-macros/i22395/Macro_1.scala create mode 100644 tests/run-macros/i22395/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index fd5b635e11e2..5fe6e1f262c0 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -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) diff --git a/tests/neg-macros/i15159.check b/tests/neg-macros/i15159.check new file mode 100644 index 000000000000..42dc31582c34 --- /dev/null +++ b/tests/neg-macros/i15159.check @@ -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] } + | ^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i15159/Macro_1.scala b/tests/neg-macros/i15159/Macro_1.scala new file mode 100644 index 000000000000..617478f51965 --- /dev/null +++ b/tests/neg-macros/i15159/Macro_1.scala @@ -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 + } + '{ () } diff --git a/tests/neg-macros/i15159/Test_2.scala b/tests/neg-macros/i15159/Test_2.scala new file mode 100644 index 000000000000..0c290822550e --- /dev/null +++ b/tests/neg-macros/i15159/Test_2.scala @@ -0,0 +1,6 @@ +sealed trait A +case class X(i: Int) extends A + +object Test extends App { + TestMacro.test[A] // error +} diff --git a/tests/pos-macros/i13319/Macro_1.scala b/tests/pos-macros/i13319/Macro_1.scala new file mode 100644 index 000000000000..8a666f5c4849 --- /dev/null +++ b/tests/pos-macros/i13319/Macro_1.scala @@ -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("") diff --git a/tests/pos-macros/i13319/Test_2.scala b/tests/pos-macros/i13319/Test_2.scala new file mode 100644 index 000000000000..e4ae7c834b71 --- /dev/null +++ b/tests/pos-macros/i13319/Test_2.scala @@ -0,0 +1 @@ +@main def Test = Macro[Option[String]] diff --git a/tests/run-macros/i22395.check b/tests/run-macros/i22395.check new file mode 100644 index 000000000000..5be77e32fac9 --- /dev/null +++ b/tests/run-macros/i22395.check @@ -0,0 +1 @@ +TypeRef(AppliedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class )),module class )),Foo),List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class )),object scala),Int))),class Nested) diff --git a/tests/run-macros/i22395/Macro_1.scala b/tests/run-macros/i22395/Macro_1.scala new file mode 100644 index 000000000000..6f5cf55b9dd3 --- /dev/null +++ b/tests/run-macros/i22395/Macro_1.scala @@ -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] diff --git a/tests/run-macros/i22395/Test_2.scala b/tests/run-macros/i22395/Test_2.scala new file mode 100644 index 000000000000..824e6ec869c9 --- /dev/null +++ b/tests/run-macros/i22395/Test_2.scala @@ -0,0 +1,2 @@ +@main def Test = + println(test())