Skip to content

Commit cfe35ed

Browse files
authored
Handle recursive types involving type aliases (#242)
* Add a test for generating surface of recursive types involving type aliases * Support type alias of higher kind types * Fix higher kind type reference in Scala.js
1 parent fc91387 commit cfe35ed

File tree

6 files changed

+76
-10
lines changed

6 files changed

+76
-10
lines changed

airframe-surface/js/src/main/scala/wvlet/surface/SurfaceMacros.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,15 @@ private[surface] object SurfaceMacros {
115115
if symbol.isType &&
116116
symbol.asType.isAliasType &&
117117
!belongsToScalaDefault(alias) =>
118-
val inner = surfaceOf(alias.dealias)
118+
val dealiased = alias.dealias
119+
val inner = if (alias != dealiased) {
120+
surfaceOf(dealiased)
121+
} else {
122+
// When higher kind types are aliased (e.g., type M[A] = Future[A]),
123+
// alias.dealias will not return the aliased type (Future[A]),
124+
// So we need to find the resulting type by applying type erasure.
125+
surfaceOf(alias.erasure)
126+
}
119127
val name = symbol.asType.name.decodedName.toString
120128
val fullName = s"${prefix.typeSymbol.fullName}.${name}"
121129
q"wvlet.surface.Alias(${name}, ${fullName}, $inner)"
@@ -399,7 +407,15 @@ private[surface] object SurfaceMacros {
399407
case t @ TypeRef(NoPrefix, symbol, args) if !t.typeSymbol.isClass =>
400408
q"wvlet.surface.ExistentialType"
401409
case t =>
402-
val expr = q"new wvlet.surface.GenericSurface(classOf[$t])"
410+
val finalType =
411+
if (t.typeSymbol.asType.isAbstract && !(t =:= typeOf[AnyRef])) {
412+
// Use M[_] for type M
413+
t.erasure
414+
} else {
415+
t
416+
}
417+
418+
val expr = q"new wvlet.surface.GenericSurface(classOf[${finalType}])"
403419
expr
404420
}
405421

airframe-surface/jvm/src/main/scala/wvlet/surface/reflect/SurfaceFactory.scala

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,20 @@ object SurfaceFactory extends LogSupport {
260260
if symbol.isType &&
261261
symbol.asType.isAliasType &&
262262
!belongsToScalaDefault(alias) =>
263-
val inner = surfaceOf(alias.dealias)
263+
val dealiased = alias.dealias
264+
val inner = if (alias != dealiased) {
265+
surfaceOf(dealiased)
266+
} else {
267+
// When higher kind types are aliased (e.g., type M[A] = Future[A]),
268+
// alias.dealias will not return the aliased type (Future[A]),
269+
// So we need to find the resulting type by applying type erasure.
270+
surfaceOf(alias.erasure)
271+
}
272+
264273
val name = symbol.asType.name.decodedName.toString
265274
val fullName = s"${prefix.typeSymbol.fullName}.${name}"
266-
Alias(name, fullName, inner)
275+
val a = Alias(name, fullName, inner)
276+
a
267277
}
268278

269279
private def arrayFactory: SurfaceMatcher = {

airframe-surface/shared/src/main/scala/wvlet/surface/Surfaces.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
*/
1414
package wvlet.surface
1515
import scala.language.existentials
16-
import scala.reflect.runtime.{universe => ru}
1716

1817
/**
1918
* Parameters of a Surface
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package wvlet.surface
15+
import wvlet.surface
16+
import scala.language.higherKinds
17+
18+
object RecursiveHigherKindTypeTest {
19+
trait Holder[M[_]]
20+
21+
class MyTask[A]
22+
23+
object Holder {
24+
type BySkinny[A] = MyTask[A]
25+
def bySkinny: Holder[BySkinny] = new InterpretedHolder
26+
}
27+
28+
import Holder._
29+
class InterpretedHolder extends Holder[BySkinny] {}
30+
}
31+
32+
/**
33+
*
34+
*/
35+
class RecursiveHigherKindTypeTest extends SurfaceSpec {
36+
import RecursiveHigherKindTypeTest._
37+
import Holder.BySkinny
38+
39+
"Surface" should {
40+
"support recursive higher kind types" in {
41+
val s = surface.of[Holder[BySkinny]]
42+
s.name shouldBe "Holder[BySkinny]"
43+
s.typeArgs(0).dealias.name shouldBe "MyTask[_]"
44+
}
45+
}
46+
}

airframe-surface/shared/src/test/scala/wvlet/surface/RecursiveSurfaceTest.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ object RecursiveSurfaceTest {
1919

2020
case class Leaf(name: String)
2121
case class Cons(head: String, tail: Cons)
22-
2322
case class TypedCons[A](head: String, tail: TypedCons[A])
2423

2524
}
@@ -71,5 +70,4 @@ class RecursiveSurfaceTest extends SurfaceSpec {
7170
lazyC.isOption shouldBe false
7271
}
7372
}
74-
7573
}

airframe/src/test/scala/wvlet/airframe/HigherKindTypeTest.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@ package wvlet.airframe
1515
import scala.language.higherKinds
1616

1717
object HigherKindTypeTest {
18-
1918
trait Holder[M[_]] {
2019
def hello = "hello"
2120
}
2221

2322
trait Task[A]
2423
trait MyFuture[A]
25-
2624
trait HolderInterpreted extends Holder[Task]
2725

2826
val interpreted = new HolderInterpreted {
@@ -47,7 +45,6 @@ class HigherKindTypeTest extends AirframeSpec {
4745
.noLifeCycleLogging
4846

4947
"Airframe" should {
50-
5148
"support higher kind types" in {
5249
design.build[HolderInterpreted] { repo =>
5350
repo.hello shouldBe "new interpretation"

0 commit comments

Comments
 (0)