Skip to content

Commit ab4390d

Browse files
committed
Do not expose ClassInfo in memberType in reflect API
1 parent 4219950 commit ab4390d

File tree

9 files changed

+90
-1
lines changed

9 files changed

+90
-1
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1826,7 +1826,33 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
18261826
def termSymbol: Symbol = self.termSymbol
18271827
def isSingleton: Boolean = self.isSingleton
18281828
def memberType(member: Symbol): TypeRepr =
1829-
member.info.asSeenFrom(self, member.owner)
1829+
// This check only fails if no owner of the passed symbol is related to the `self` type
1830+
// Ideally we would just check if the symbol is a member of the type,
1831+
// but:
1832+
// A) the children of enums are not members of those enums, which is unintuitive
1833+
// B) this check was added late, risking major hard to fix regressions
1834+
// (including in compilation tests)
1835+
// Additionally, it's useful to be able to learn a type of a symbol using some prefix,
1836+
// even if it's not a direct member of that prefix, but e.g. a nested one.
1837+
def isTypePrefixingPassedMember =
1838+
import scala.util.boundary
1839+
boundary {
1840+
var checked: Symbol = member
1841+
while(checked.exists) {
1842+
if self.derivesFrom(checked)
1843+
|| self.typeSymbol.declarations.contains(checked)
1844+
|| self.typeSymbol.companionClass.declarations.contains(checked) then
1845+
boundary.break(true)
1846+
checked = checked.owner
1847+
}
1848+
boundary.break(false)
1849+
}
1850+
xCheckMacroAssert(isTypePrefixingPassedMember, s"$member is not a member of ${self.show}")
1851+
// We do not want to expose ClassInfo in the reflect API, instead we change it to a TypeRef,
1852+
// see issue #22395
1853+
member.info.asSeenFrom(self, member.owner) match
1854+
case dotc.core.Types.ClassInfo(prefix, sym, _, _, _) => prefix.select(sym)
1855+
case other => other
18301856
def baseClasses: List[Symbol] = self.baseClasses
18311857
def baseType(cls: Symbol): TypeRepr = self.baseType(cls)
18321858
def derivesFrom(cls: Symbol): Boolean = self.derivesFrom(cls)

tests/neg-macros/i15159.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
-- Error: tests/neg-macros/i15159/Test_2.scala:5:16 --------------------------------------------------------------------
3+
5 | TestMacro.test[A] // error
4+
| ^^^^^^^^^^^^^^^^^
5+
| Exception occurred while executing macro expansion.
6+
| java.lang.AssertionError: class X is not a member of A
7+
| at TestMacro$.testImpl$$anonfun$1(Macro_1.scala:8)
8+
| at scala.collection.immutable.List.map(List.scala:247)
9+
| at TestMacro$.testImpl(Macro_1.scala:7)
10+
|
11+
|---------------------------------------------------------------------------------------------------------------------
12+
|Inline stack trace
13+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
14+
|This location contains code that was inlined from Macro_1.scala:3
15+
3 | inline def test[T]: Unit = ${ testImpl[T] }
16+
| ^^^^^^^^^^^^^^^^
17+
---------------------------------------------------------------------------------------------------------------------
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted.*
2+
object TestMacro:
3+
inline def test[T]: Unit = ${ testImpl[T] }
4+
def testImpl[T: Type](using Quotes): Expr[Unit] =
5+
import quotes.reflect.*
6+
val tpe = TypeRepr.of[T]
7+
tpe.typeSymbol.children.map { childSymbol =>
8+
tpe.memberType(childSymbol) // not a member of tpe
9+
}
10+
'{ () }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sealed trait A
2+
case class X(i: Int) extends A
3+
4+
object Test extends App {
5+
TestMacro.test[A] // error
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.quoted._
2+
object Macro:
3+
inline def apply[A]: Unit = ${impl[A]}
4+
5+
private def impl[A: Type](using Quotes): Expr[String] =
6+
import quotes.reflect._
7+
val t = TypeRepr.of[A]
8+
Expr.ofList(t.baseClasses.drop(1).filter(_.flags.is(Flags.Trait)).map { baseSymbol =>
9+
t.memberType(baseSymbol).asType match { case '[t] => 42}
10+
Expr("")
11+
})
12+
Expr("")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def Test = Macro[Option[String]]

tests/run-macros/i22395.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted._
2+
3+
inline def test(): String = ${testImpl}
4+
def testImpl(using Quotes) = {
5+
import quotes.reflect._
6+
val fooSymbol = TypeRepr.of[Foo[Int]].typeSymbol
7+
val nestedSymbol = fooSymbol.typeMember("Nested")
8+
9+
Expr(TypeRepr.of[Foo[Int]].memberType(nestedSymbol).toString)
10+
}
11+
12+
13+
trait Foo[X]:
14+
sealed abstract class Nested extends Foo[Int]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@main def Test =
2+
println(test())

0 commit comments

Comments
 (0)