From ff20874aaabdf12da4e709abf92104be14bb85e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Wed, 28 Feb 2024 11:49:42 +0100 Subject: [PATCH 1/5] Test for Enumeration support (#3411) --- .../scala/wvlet/airframe/surface/i3411.scala | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala diff --git a/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala b/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala new file mode 100644 index 0000000000..1d393dcc30 --- /dev/null +++ b/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala @@ -0,0 +1,35 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package wvlet.airframe.surface + +import wvlet.airspec.AirSpec + +object i3411 extends AirSpec { + + object SomeEnum extends Enumeration { + type SomeEnum = Value + + val A, B, C = Value + } + + import SomeEnum.SomeEnum + + test("Handle a Scala 2 enumeration") { + val s = Surface.of[SomeEnum] // just check there is no error - no expected properties + val m = Surface.methodsOf[SomeEnum] + debug(s.params) + // enumeration type (value) usually contains at least the compare method + m.map(_.name) shouldContain "compare" + } +} From c6d14662ada87c462bbd119ada818e25cc3438d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Wed, 28 Feb 2024 16:27:34 +0100 Subject: [PATCH 2/5] WIP: manipulate the type to avoid access using base --- .../surface/CompileTimeSurfaceFactory.scala | 42 +++++++++++++++++-- .../scala/wvlet/airframe/surface/i3411.scala | 24 +++++++++-- 2 files changed, 58 insertions(+), 8 deletions(-) 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 58831ffe91..d0e60204f6 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 @@ -1,5 +1,6 @@ package wvlet.airframe.surface import java.util.concurrent.atomic.AtomicInteger +import scala.annotation.tailrec import scala.collection.immutable.ListMap import scala.quoted.* import scala.reflect.ClassTag @@ -556,6 +557,7 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q): // println(s"=== target: ${m.name}, ${m.owner.name}") m.name == targetMethodName } + // println(s"MethodArg ${v.name} $resolved") MethodArg(v.name, resolved, defaultValueGetter, defaultMethodArgGetter, isImplicit, isRequired, isSecret) } } @@ -754,17 +756,49 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q): if seenMethodParent.contains(targetType) then sys.error(s"recursive method found in: ${targetType.typeSymbol.fullName}") else + println(s"=== targetType ${targetType.show} ${targetType}") seenMethodParent += targetType val localMethods = localMethodsOf(targetType).distinct.sortBy(_.name) + + // check for a type like OuterType.InnerType and get OuterType from it + val TypeRef(targetTypeParent, _) = targetType: @unchecked // it seems irefutable, but compiler does not agree + //println(s" ${targetTypeParent.baseClasses}") + def simplifyTypeRef(typeRepr: TypeRepr): TypeRepr = { + //println(s"simplifyTypeRef ${typeRepr.show} in ${targetType.show}: ${typeRepr}") + typeRepr match { + // Pattern matching to skip 'Base' and directly access 'InnerType' + case _ if targetTypeParent.baseClasses.exists(_.typeRef == typeRepr) => + //println(s" case base ${typeRepr.show} $typeRepr -> $targetType") + targetType + + case TypeRef(ThisType(parent), _) => + val result = simplifyTypeRef(parent).typeSymbol.typeMember(typeRepr.typeSymbol.name).typeRef + //println(s" case non-base ${typeRepr.show} $typeRepr -> $result") + result + + case _ => + //println(s" case other ${typeRepr.show} $typeRepr") + typeRepr + } + } + val methodSurfaces = localMethods.map(m => (m, m.tree)).collect { case (m, df: DefDef) => val mod = Expr(modifierBitMaskOf(m)) val owner = surfaceOf(targetType) val name = Expr(m.name) - // println(s"======= ${df.returnTpt.show}") - val ret = surfaceOf(df.returnTpt.tpe) - // println(s"==== method of: def ${m.name}") + val tpt = df.returnTpt + println(s"======= tpt ${tpt.show} ${simplifyTypeRef(tpt.tpe).show}") + + println(s"======= dealias: ${tpt.tpe.dealias.show}") + val ret = surfaceOf(tpt.tpe) + println(s"==== method of: def ${m.name}") val params = methodParametersOf(targetType, m) - val args = methodArgsOf(targetType, m) + val args = methodArgsOf(targetType, m).map { list => + list.map { arg => + println(s"======= arg ${arg.name}: ${arg.tpe.show} ==> ${simplifyTypeRef(arg.tpe).show}") + arg.copy(tpe = simplifyTypeRef(arg.tpe)) + } + } val methodCaller = createMethodCaller(targetType, m, args) '{ ClassMethodSurface(${ mod }, ${ owner }, ${ name }, ${ ret }, ${ params }.toIndexedSeq, ${ methodCaller }) diff --git a/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala b/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala index 1d393dcc30..937e326051 100644 --- a/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala +++ b/airframe-surface/src/test/scala/wvlet/airframe/surface/i3411.scala @@ -17,6 +17,22 @@ import wvlet.airspec.AirSpec object i3411 extends AirSpec { + trait Base { + class InnerType { + def compare(that: InnerType): Int = 0 + } + } + + object OuterType extends Base { + def create: InnerType = new InnerType + } + + test("Handle inherited inner class") { + //val mm = Surface.methodsOf[OuterType.type] + val m = Surface.methodsOf[OuterType.InnerType] + //m.map(_.name) shouldContain "compare" + } + object SomeEnum extends Enumeration { type SomeEnum = Value @@ -26,10 +42,10 @@ object i3411 extends AirSpec { import SomeEnum.SomeEnum test("Handle a Scala 2 enumeration") { - val s = Surface.of[SomeEnum] // just check there is no error - no expected properties - val m = Surface.methodsOf[SomeEnum] - debug(s.params) + //val s = Surface.of[SomeEnum] // just check there is no error - no expected properties + //val m = Surface.methodsOf[SomeEnum] + //debug(s.params) // enumeration type (value) usually contains at least the compare method - m.map(_.name) shouldContain "compare" + //m.map(_.name) shouldContain "compare" } } From f042ea32a99e14661a834721ac0c1378e3ff5cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Wed, 28 Feb 2024 16:36:23 +0100 Subject: [PATCH 3/5] WIP: manipulate the type to avoid access using base --- .../airframe/surface/CompileTimeSurfaceFactory.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 d0e60204f6..d175261af9 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 @@ -764,20 +764,20 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q): val TypeRef(targetTypeParent, _) = targetType: @unchecked // it seems irefutable, but compiler does not agree //println(s" ${targetTypeParent.baseClasses}") def simplifyTypeRef(typeRepr: TypeRepr): TypeRepr = { - //println(s"simplifyTypeRef ${typeRepr.show} in ${targetType.show}: ${typeRepr}") + println(s"simplifyTypeRef ${typeRepr.show} in ${targetType.show}: ${typeRepr}") typeRepr match { // Pattern matching to skip 'Base' and directly access 'InnerType' case _ if targetTypeParent.baseClasses.exists(_.typeRef == typeRepr) => - //println(s" case base ${typeRepr.show} $typeRepr -> $targetType") - targetType + println(s" case base ${typeRepr.show} $typeRepr -> $targetTypeParent") + targetTypeParent case TypeRef(ThisType(parent), _) => val result = simplifyTypeRef(parent).typeSymbol.typeMember(typeRepr.typeSymbol.name).typeRef - //println(s" case non-base ${typeRepr.show} $typeRepr -> $result") + println(s" case non-base ${typeRepr.show} $typeRepr -> $result") result case _ => - //println(s" case other ${typeRepr.show} $typeRepr") + println(s" case other ${typeRepr.show} $typeRepr") typeRepr } } From a2131f136a52abff8e3574d4d777b8b923fbd186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Wed, 28 Feb 2024 16:40:47 +0100 Subject: [PATCH 4/5] WIP: manipulate the type to avoid access using base --- .../wvlet/airframe/surface/CompileTimeSurfaceFactory.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 d175261af9..f63a247c4f 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 @@ -772,8 +772,11 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q): targetTypeParent case TypeRef(ThisType(parent), _) => - val result = simplifyTypeRef(parent).typeSymbol.typeMember(typeRepr.typeSymbol.name).typeRef - println(s" case non-base ${typeRepr.show} $typeRepr -> $result") + println(s" case non-base ${typeRepr.show} $typeRepr") + val parentRef = simplifyTypeRef(parent) + val result = parentRef.typeSymbol.typeMember(typeRepr.typeSymbol.name).typeRef + println(s" -> parentRef: $result") + println(s" -> result: $result") result case _ => From 6d5e1a47a430d63fe35e8c192ca4b15fc24a2406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Thu, 29 Feb 2024 15:48:46 +0100 Subject: [PATCH 5/5] Fix: pattern match refutable, handle other types --- .../airframe/surface/CompileTimeSurfaceFactory.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 f63a247c4f..2cabcf892b 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 @@ -761,15 +761,18 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q): val localMethods = localMethodsOf(targetType).distinct.sortBy(_.name) // check for a type like OuterType.InnerType and get OuterType from it - val TypeRef(targetTypeParent, _) = targetType: @unchecked // it seems irefutable, but compiler does not agree + val targetTypeParent = targetType match { + case TypeRef(parent, _) => Some(parent) + case _ => None + } //println(s" ${targetTypeParent.baseClasses}") def simplifyTypeRef(typeRepr: TypeRepr): TypeRepr = { println(s"simplifyTypeRef ${typeRepr.show} in ${targetType.show}: ${typeRepr}") typeRepr match { // Pattern matching to skip 'Base' and directly access 'InnerType' - case _ if targetTypeParent.baseClasses.exists(_.typeRef == typeRepr) => + case _ if targetTypeParent.exists(_.baseClasses.exists(_.typeRef == typeRepr)) => println(s" case base ${typeRepr.show} $typeRepr -> $targetTypeParent") - targetTypeParent + targetTypeParent.get case TypeRef(ThisType(parent), _) => println(s" case non-base ${typeRepr.show} $typeRepr")