Description
Compiler version
3.3.4
Minimized code
package example
import scala.quoted.*
object Macro {
inline def typeMembers[T <: AnyKind]: String = ${ typeMembersImpl[T] }
def typeMembersImpl[T <: AnyKind: Type](using quotes: Quotes): Expr[String] = {
import quotes.reflect.*
Expr(TypeRepr.of[T].typeSymbol.typeMembers.toString)
}
}
import example.Macro
@main def test(): Unit = {
class FooSmall[A, B] { type C; type D }
class FooLarge[A, B, C] { type D; type E }
println(Macro.typeMembers[FooSmall])
println(Macro.typeMembers[FooLarge])
}
Output
List(type A, type B, type C, type D)
List(type B, type D, type C, type E, type A)
Expectation
List(type A, type B, type C, type D)
List(type A, type B, type C, type D, type E)
The list returned by .typeMembers
is ordered by declaration order, with type parameters first and type members second. BUT only if the total number of type members + type parameters is less than 5. Otherwise the order is non-deterministic due to underlying usage of Set
.
I think expecting a defined order in Symbold.typeMembers
method is reasonable, because:
- When this method was added, the tests added with it only tested naive usage
.typeMembers.filter(_.isTypeParam)
– indicating that this is the correct usage pattern. - It's the only source of type parameter variance in public reflection API.
- Restoring order without relying on it being correct in
.typeMembers
is very hard - the only way I found is to match type parameter names with the same from.primaryConstructor
- and that's hardly intuitive and I'm not even sure if all types that can have variance defined on type parameters also have a primaryConstructor, otherwise the order is not restorable. - The .typeMembers call returns a
List
, not aSet
, indicating order.
A user of izumi-reflect
uncovered this undefined behavior due to getting incorrect variance in typetags generated by izumi-reflect on classes with many type parameters: zio/izumi-reflect#511
I'm not sure there's a bulletproof workaround for this issue downstream – because I'm not sure if every relevant type that may be inspected by izumi-reflect has a .primaryConstructor to match order against.