From 3bd6f8c920fb168894e07e8a32f3853e9c271141 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Wed, 26 Mar 2025 16:38:07 +0100 Subject: [PATCH 1/2] Sort the typeMembers output list and filter out non-members --- .../src/dotty/tools/dotc/core/Types.scala | 18 ++++++++++ .../quoted/runtime/impl/QuotesImpl.scala | 28 +++++++++++++++- tests/run-macros/type-members.check | 8 +++++ tests/run-macros/type-members/Macro_1.scala | 12 +++++++ tests/run-macros/type-members/Test_2.scala | 33 +++++++++++++++++++ 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 tests/run-macros/type-members.check create mode 100644 tests/run-macros/type-members/Macro_1.scala create mode 100644 tests/run-macros/type-members/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index df7700c73a17..04592885992f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -44,6 +44,7 @@ import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe import dotty.tools.dotc.cc.ccConfig +import scala.collection.mutable.LinkedHashSet object Types extends TypeUtils { @@ -1013,6 +1014,23 @@ object Types extends TypeUtils { buf.toList } + /** For use in quotes reflect. + * A bit slower than the usual approach due to the use of LinkedHashSet. + **/ + def sortedParents(using Context): LinkedHashSet[Type] = this match + case tp: ClassInfo => + LinkedHashSet(tp) | LinkedHashSet(tp.declaredParents.flatMap(_.sortedParents.toList)*) + case tp: RefinedType => + tp.parent.sortedParents + case tp: TypeProxy => + tp.superType.sortedParents + case tp: AndType => + tp.tp1.sortedParents | tp.tp2.sortedParents + case tp: OrType => + tp.tp1.sortedParents & tp.tp2.sortedParents + case _ => + LinkedHashSet() + /** The set of abstract term members of this type. */ final def abstractTermMembers(using Context): Seq[SingleDenotation] = { record("abstractTermMembers") diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a93e010ddc34..47115e195dc1 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -3010,7 +3010,33 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def memberTypes: List[Symbol] = self.typeRef.decls.filter(_.isType) def typeMembers: List[Symbol] = - lookupPrefix.typeMembers.map(_.symbol).toList + // lookupPrefix.typeMembers currently returns a Set wrapped into a unsorted Seq, + // so we try to sort that here (see discussion: https://github.com/scala/scala3/issues/22472), + // without adding too much of a performance hit. + // It first sorts by parents, then for type params by their positioning, then for members + // derived from declarations it sorts them by their name lexicographically + val parentsMap = lookupPrefix.sortedParents.map(_.typeSymbol).zipWithIndex.toList.toMap + val unsortedTypeMembers = lookupPrefix.typeMembers.map(_.symbol).filter(_.exists).toList + unsortedTypeMembers.sortWith { + case (typeA, typeB) => + val msg = "Unknown type member found. Please consider reporting the issue to the compiler. " + assert(parentsMap.contains(typeA.owner), msg) + assert(parentsMap.contains(typeB.owner), msg) + val parentPlacementA = parentsMap(typeA.owner) + val parentPlacementB = parentsMap(typeB.owner) + if (parentPlacementA == parentPlacementB) then + if typeA.isTypeParam && typeB.isTypeParam then + // put type params at the beginning (and sort them by declaration order) + val pl = typeA.owner + val typeParamPositionMap = pl.typeParams.map(_.asInstanceOf[Symbol]).zipWithIndex.toMap + typeParamPositionMap(typeA) < typeParamPositionMap(typeB) + else if typeA.isTypeParam then true + else if typeB.isTypeParam then false + else + // sort by name lexicographically + typeA.name.toString().compareTo(typeB.name.toString()) < 0 + else parentPlacementA < parentPlacementB + }.map(_.asInstanceOf[Symbol]) def declarations: List[Symbol] = self.typeRef.info.decls.toList diff --git a/tests/run-macros/type-members.check b/tests/run-macros/type-members.check new file mode 100644 index 000000000000..6805e6158e8e --- /dev/null +++ b/tests/run-macros/type-members.check @@ -0,0 +1,8 @@ +class FooSmall: List(type A, type B, type C, type D) +class FooLarge: List(type A, type B, type C, type D, type E) +type FooUnion: List() +type FooAnd: List(type A, type B, type C, type D, type E) +trait CLS1: List(type A, type B, type C, type B1, type B2, type A3, type B3, type B4) +type SharedAnd1: List(type B, type Shared, type A, type C) +type SharedAnd2: List(type C, type Shared, type A, type B) +type SharedUnion: List(type A, type Shared) diff --git a/tests/run-macros/type-members/Macro_1.scala b/tests/run-macros/type-members/Macro_1.scala new file mode 100644 index 000000000000..1aff04803798 --- /dev/null +++ b/tests/run-macros/type-members/Macro_1.scala @@ -0,0 +1,12 @@ +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(s"${TypeRepr.of[T].typeSymbol}: ${TypeRepr.of[T].typeSymbol.typeMembers.toString}") + } +} diff --git a/tests/run-macros/type-members/Test_2.scala b/tests/run-macros/type-members/Test_2.scala new file mode 100644 index 000000000000..506e67068492 --- /dev/null +++ b/tests/run-macros/type-members/Test_2.scala @@ -0,0 +1,33 @@ +import example.Macro + +class FooSmall[A, B] { type D; type C } +class FooLarge[A, B, C] { type E; type D } + +type FooUnion[A, B] = FooSmall[A, B] | FooLarge[A, B, Int] +type FooAnd[A, B] = FooSmall[A, B] & FooLarge[A, B, Int] + +trait CLS4[A] { type B4 } +trait CLS3[A] extends CLS4[A] { type B3; type A3 } +trait CLS2[A] { type B2 } +trait CLS1[A, B, C] extends CLS2[A] with CLS3[B] { type B1 } + +trait SharedParent[A] { type Shared } +trait SharedA[A] extends SharedParent[A] { type B } +trait SharedB[A] extends SharedParent[A] { type C } +type SharedAnd1[A] = SharedA[A] & SharedB[A] +type SharedAnd2[A] = SharedB[A] & SharedA[A] +type SharedUnion[A] = SharedA[A] | SharedB[A] + +@main def Test(): Unit = { + println(Macro.typeMembers[FooSmall]) + println(Macro.typeMembers[FooLarge]) + + println(Macro.typeMembers[FooUnion]) + println(Macro.typeMembers[FooAnd]) + + println(Macro.typeMembers[CLS1]) + + println(Macro.typeMembers[SharedAnd1]) + println(Macro.typeMembers[SharedAnd2]) + println(Macro.typeMembers[SharedUnion]) +} From d63f19d2d5f7f43e77110227e55b16598fb3fe97 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 31 Mar 2025 11:32:39 +0200 Subject: [PATCH 2/2] Change expected ordering of typeMembers in an older testcase --- tests/run-macros/i14902.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-macros/i14902.check b/tests/run-macros/i14902.check index 9b27fcb7e5dc..5e6373a9516c 100644 --- a/tests/run-macros/i14902.check +++ b/tests/run-macros/i14902.check @@ -1,4 +1,4 @@ List(X) -List(X, Y, Z) +List(Y, Z, X) List(X) List(Y, Z)