diff --git a/airframe-surface/src/main/scala-3/wvlet/airframe/surface/CompileTimeSurfaceFactory.scala b/airframe-surface/src/main/scala-3/wvlet/airframe/surface/CompileTimeSurfaceFactory.scala index 8339627f18..01aa78523f 100644 --- a/airframe-surface/src/main/scala-3/wvlet/airframe/surface/CompileTimeSurfaceFactory.scala +++ b/airframe-surface/src/main/scala-3/wvlet/airframe/surface/CompileTimeSurfaceFactory.scala @@ -76,14 +76,16 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q) { surfaceOf(TypeRepr.of(using tpe)) } - private val seen = scala.collection.mutable.Set[TypeRepr]() - private val memo = scala.collection.mutable.Map[TypeRepr, Expr[Surface]]() + private val seen = scala.collection.mutable.Set[TypeRepr]() + private val memo = scala.collection.mutable.Map[TypeRepr, Expr[Surface]]() + private val lazySurface = scala.collection.mutable.Set[TypeRepr]() private def surfaceOf(t: TypeRepr): Expr[Surface] = { if (seen.contains(t)) { if (memo.contains(t)) { memo(t) } else { + lazySurface += t '{ LazySurface(${ clsOf(t) }, ${ Expr(fullTypeNameOf(t)) }) } } } else { @@ -91,27 +93,36 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q) { // For debugging // println(s"[${typeNameOf(t)}]\n ${t}\nfull type name: ${fullTypeNameOf(t)}\nclass: ${t.getClass}") val generator = factory.andThen { expr => - val cacheKey = if (typeNameOf(t) == "scala.Any") { - t match { - case ParamRef(TypeLambda(typeNames, _, _), _) => - // Distinguish scala.Any and type bounds (such as _) - s"${fullTypeNameOf(t)} for ${t}" - case TypeBounds(_, _) => - // This ensures different cache key for each Type Parameter (such as T and U). - // This is required because fullTypeNameOf of every Type Parameters is `scala.Any`. - s"${fullTypeNameOf(t)} for ${t}" - case _ => - fullTypeNameOf(t) - } + if (!lazySurface.contains(t)) { + expr } else { - fullTypeNameOf(t) - } - '{ - val key = ${ Expr(cacheKey) } - if (!wvlet.airframe.surface.surfaceCache.contains(key)) { - wvlet.airframe.surface.surfaceCache += key -> ${ expr } + // Need to cache the recursive Surface to be referenced in a LazySurface + val cacheKey = if (typeNameOf(t) == "scala.Any") { + t match { + case ParamRef(TypeLambda(typeNames, _, _), _) => + // Distinguish scala.Any and type bounds (such as _) + s"${fullTypeNameOf(t)} for ${t}" + case TypeBounds(_, _) => + // This ensures different cache key for each Type Parameter (such as T and U). + // This is required because fullTypeNameOf of every Type Parameters is `scala.Any`. + s"${fullTypeNameOf(t)} for ${t}" + case _ => + fullTypeNameOf(t) + } + } else { + fullTypeNameOf(t) + } + '{ + val key = ${ + Expr(cacheKey) + } + if (!wvlet.airframe.surface.surfaceCache.contains(key)) { + wvlet.airframe.surface.surfaceCache += key -> ${ + expr + } + } + wvlet.airframe.surface.surfaceCache(key) } - wvlet.airframe.surface.surfaceCache(key) } } val surface = generator(t) @@ -693,6 +704,7 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q) { } } val expr = Expr.ofSeq(methodSurfaces) + // println(s"methodOf: ${targetType.typeSymbol.fullName} => \n${expr.show}") methodMemo += targetType -> expr expr } diff --git a/airframe-surface/src/test/scala/wvlet/airframe/surface/RecursiveSurfaceTest.scala b/airframe-surface/src/test/scala/wvlet/airframe/surface/RecursiveSurfaceTest.scala index ba02576a88..2b22d6c604 100644 --- a/airframe-surface/src/test/scala/wvlet/airframe/surface/RecursiveSurfaceTest.scala +++ b/airframe-surface/src/test/scala/wvlet/airframe/surface/RecursiveSurfaceTest.scala @@ -24,9 +24,9 @@ object RecursiveSurfaceTest { class RecursiveSurfaceTest extends SurfaceSpec { import RecursiveSurfaceTest._ - test("find surface from full type name string") { - val s = Surface.of[Leaf] - assert(wvlet.airframe.surface.getCached("wvlet.airframe.surface.RecursiveSurfaceTest.Leaf") == s) + test("find recursive surface cache from the full type name string") { + val s = Surface.of[Cons] + assert(wvlet.airframe.surface.getCached("wvlet.airframe.surface.RecursiveSurfaceTest.Cons") == s) } test("support recursive type") { diff --git a/airframe-surface/src/test/scala/wvlet/airframe/surface/SurfaceTest.scala b/airframe-surface/src/test/scala/wvlet/airframe/surface/SurfaceTest.scala index 6f8d290b56..1462bf063c 100644 --- a/airframe-surface/src/test/scala/wvlet/airframe/surface/SurfaceTest.scala +++ b/airframe-surface/src/test/scala/wvlet/airframe/surface/SurfaceTest.scala @@ -90,20 +90,24 @@ class SurfaceTest extends SurfaceSpec { test("be equal") { val a1 = Surface.of[A] val a2 = Surface.of[A] - assert(a1 eq a2) + + // In Scala 3, Surface instance identity is not guaranteed + // assert(a1 eq a2) + // equality assert(a1 == a2) assert(a1.hashCode() == a2.hashCode()) val b = Surface.of[B] val a3 = b.params.head.surface - assert(a1 eq a3) + + // assert(a1 eq a3) // Generic surface val c1 = Surface.of[Seq[Int]] val c2 = Surface.of[Seq[Int]] assert(c1.equals(c2) == true) - assert(c1 eq c2) + // assert(c1 eq c2) assert(c1.hashCode() == c2.hashCode()) assert(c1 ne a1)