Skip to content

Commit

Permalink
Resolution of Scala 2 vs 3 reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbanda committed Sep 19, 2024
1 parent 2e8c126 commit 627642d
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 14 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ addCommandAlias(
inThisBuild(
List(
scalacOptions += "-Ywarn-unused",
// scalaVersion := "2.12.15",
// scalaVersion := scala3,
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package io.cequence.openaiscala.service

import io.cequence.openaiscala.OpenAIScalaClientException
import io.cequence.openaiscala.domain.JsonSchema
import io.cequence.openaiscala.service.ReflectionUtil._

import scala.reflect.runtime.universe._
import io.cequence.openaiscala.service.ReflectionUtil._

// This is experimental and subject to change
trait JsonSchemaReflectionHelper {
Expand Down Expand Up @@ -53,7 +53,7 @@ trait JsonSchemaReflectionHelper {
val itemsSchema = asJsonSchema(innerType, mirror, dateAsNumber)
JsonSchema.Array(itemsSchema)

case t if isCaseClass(t) =>
case t if t.isCaseClass() =>
caseClassAsJsonSchema(t, mirror, dateAsNumber)

// map - TODO
Expand Down Expand Up @@ -83,7 +83,7 @@ trait JsonSchemaReflectionHelper {
mirror: Mirror,
dateAsNumber: Boolean
): JsonSchema = {
val memberNamesAndTypes = getCaseClassMemberNamesAndTypes(typ)
val memberNamesAndTypes = typ.getCaseClassFields()

val fieldSchemas = memberNamesAndTypes.toSeq.map {
case (fieldName: String, memberType: Type) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@ object ReflectionUtil {

def isOption(): Boolean =
typ <:< typeOf[Option[_]]
}

def isCaseClass(runType: Type): Boolean =
runType.members.exists(m => m.isMethod && m.asMethod.isCaseAccessor)
def isCaseClass(): Boolean =
typ.members.exists(m => m.isMethod && m.asMethod.isCaseAccessor)

def getCaseClassFields(): Iterable[(String, Type)] =
typ.decls.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => (shortName(m), m.returnType)
}
}

def shortName(symbol: Symbol): String = {
val paramFullName = symbol.fullName
paramFullName.substring(paramFullName.lastIndexOf('.') + 1, paramFullName.length)
}

def getCaseClassMemberNamesAndTypes(
runType: Type
): Traversable[(String, Type)] =
runType.decls.sorted.collect {
case m: MethodSymbol if m.isCaseAccessor => (shortName(m), m.returnType)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.cequence.openaiscala.service

import scala.quoted._

object ReflectionUtil {

class InfixOp[T](using q: Quotes, val typ: Type[T]) {

import q.reflect.* // Import the reflection API

private val typeRepr: TypeRepr = TypeRepr.of[T]
private val typeSymbol = typeRepr.typeSymbol

private val optionInnerType: Option[TypeRepr] =
if (typeRepr <:< TypeRepr.of[Option[_]])
Some(typeRepr.typeArgs.head)
else
None

def matches(types: Type[_]*): Boolean =
types.exists { candidateType =>
val candidateRepr = TypeRepr.of(using candidateType)
typeRepr =:= candidateRepr || (optionInnerType.isDefined && optionInnerType.get =:= candidateRepr)
}

def subMatches(types: Type[_]*): Boolean =
types.exists { candidateType =>
val candidateRepr = TypeRepr.of(using candidateType)
typeRepr <:< candidateRepr || (optionInnerType.isDefined && optionInnerType.get <:< candidateRepr)
}

def isOption(): Boolean =
typeRepr <:< TypeRepr.of[Option[_]]

def isCaseClass(): Boolean = {
typeSymbol.isClassDef && typeSymbol.flags.is(Flags.Case)
}

def getCaseClassFields(): List[(String, Type[_])] = {
import q.reflect.*

// Ensure it's a case class
if (isCaseClass()) {
// Collect case accessor fields
typeSymbol.caseFields.map { field =>
val fieldName = field.name

val fieldTypeRepr = field.tree match {
case v: ValDef => v.tpt.tpe // Extract the type of the field
}

// Convert TypeRepr to Type[_]
val fieldType = fieldTypeRepr.asType match {
case '[t] => Type.of[t] // Convert TypeRepr to Type[_]
}

(fieldName, fieldType)
}
} else {
List.empty // Not a case class, return empty list
}
}
}

def shortName(symbol: Symbol): String = {
val paramFullName = symbol.name
paramFullName.substring(paramFullName.lastIndexOf('.') + 1, paramFullName.length)
}
}

0 comments on commit 627642d

Please sign in to comment.