Skip to content

Commit

Permalink
Refactor ProductTypes to distinct: case objects, parameterless case f…
Browse files Browse the repository at this point in the history
…rom Scala 3 enum
  • Loading branch information
MateuszKubuszok committed Apr 4, 2024
1 parent c7f4283 commit 1dea112
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ private[compiletime] trait TypesPlatform extends Types { this: DefinitionsPlatfo
// everyone has the same baseClasses, everyone reports to have public primaryConstructor (which is <none>).
// The only different in behavior is that one prints com.my.Enum and another com.my.Enum(MyValue).
val javaEnumRegexpFormat = raw"^(.+)\((.+)\)$$".r
def isJavaEnumValue(tpe: c.Type): Boolean =
tpe.typeSymbol.isJavaEnum && javaEnumRegexpFormat.pattern
.matcher(tpe.toString)
.matches() // 2.12 doesn't have .matches
}

import platformSpecific.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,17 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
isPOJO[A] && A.tpe.typeSymbol.asClass.isCaseClass
def isCaseObject[A](implicit A: Type[A]): Boolean = {
val sym = A.tpe.typeSymbol
def isScala2Enum = sym.asClass.isCaseClass
def isScala3Enum = sym.isStatic && sym.isFinal // parameterless case in S3 cannot be checked for "case"
def isScalaEnum = sym.isModuleClass && (isScala2Enum || isScala3Enum)
sym.isPublic && (isScalaEnum || isJavaEnumValue(A.tpe))
sym.isPublic && sym.isModuleClass && sym.asClass.isCaseClass
}
def isCaseVal[A](implicit A: Type[A]): Boolean = {
val sym = A.tpe.typeSymbol
sym.isPublic && sym.isModuleClass && sym.isStatic && sym.isFinal // parameterless case in S3 cannot be checked for "case"
}
def isJavaEnumValue[A](implicit A: Type[A]): Boolean = {
val sym = A.tpe.typeSymbol
sym.isPublic && sym.isJavaEnum && javaEnumRegexpFormat.pattern
.matcher(A.tpe.toString)
.matches() // 2.12 doesn't have .matches
}
def isJavaBean[A](implicit A: Type[A]): Boolean = {
val mem = A.tpe.members
Expand Down Expand Up @@ -103,9 +110,9 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
val A = Type[A].tpe
val sym = A.typeSymbol

if (isJavaEnumValue(A)) {
if (isJavaEnumValue[A]) {
Some(Product.Constructor(ListMap.empty, _ => c.Expr[A](q"$A")))
} else if (isCaseObject[A]) {
} else if (isCaseObject[A] || isCaseVal[A]) {
Some(Product.Constructor(ListMap.empty, _ => c.Expr[A](q"${sym.asClass.module}")))
} else if (isPOJO[A]) {
val primaryConstructor =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
def isJavaSetterOrVar(setter: Symbol): Boolean =
isJavaSetter(setter) || isVar(setter)

def isJavaEnumValue[A: Type]: Boolean =
Type[A] <:< scala.quoted.Type.of[java.lang.Enum[?]] && !TypeRepr.of[A].typeSymbol.isAbstract
}

import platformSpecific.*
Expand All @@ -61,10 +59,14 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
}
def isCaseObject[A](implicit A: Type[A]): Boolean = {
val sym = TypeRepr.of(using A).typeSymbol
def isScala2Enum = sym.flags.is(Flags.Case | Flags.Module)
def isScala3Enum = sym.flags.is(Flags.Case | Flags.Enum | Flags.JavaStatic)
sym.isPublic && (isScala2Enum || isScala3Enum || isJavaEnumValue[A])
sym.isPublic && sym.flags.is(Flags.Case | Flags.Module)
}
def isCaseVal[A](implicit A: Type[A]): Boolean = {
val sym = TypeRepr.of(using A).typeSymbol
sym.isPublic && sym.flags.is(Flags.Case | Flags.Enum | Flags.JavaStatic)
}
def isJavaEnumValue[A: Type]: Boolean =
Type[A] <:< scala.quoted.Type.of[java.lang.Enum[?]] && !TypeRepr.of[A].typeSymbol.isAbstract
def isJavaBean[A](implicit A: Type[A]): Boolean = {
val sym = TypeRepr.of(using A).typeSymbol
val mem = sym.declarations
Expand Down Expand Up @@ -129,14 +131,11 @@ trait ProductTypesPlatform extends ProductTypes { this: DefinitionsPlatform =>
)

def parseConstructor[A: Type]: Option[Product.Constructor[A]] =
if isCaseObject[A] then {
if isCaseObject[A] || isCaseVal[A] || isJavaEnumValue[A] then {
val A = TypeRepr.of[A]
val sym = A.typeSymbol

// TODO: actually a duplication of isScala3Enum from isCaseObject
def isScala3Enum = sym.flags.is(Flags.Case | Flags.Enum | Flags.JavaStatic)

if isScala3Enum || isJavaEnumValue[A] then Some(Product.Constructor(ListMap.empty, _ => Ref(sym).asExprOf[A]))
if isCaseVal[A] || isJavaEnumValue[A] then Some(Product.Constructor(ListMap.empty, _ => Ref(sym).asExprOf[A]))
else Some(Product.Constructor(ListMap.empty, _ => Ref(sym.companionModule).asExprOf[A]))
} else if isPOJO[A] then {
val A = TypeRepr.of[A]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,22 @@ trait ProductTypes { this: Definitions =>
protected val ProductType: ProductTypesModule
protected trait ProductTypesModule { this: ProductType.type =>

/** Any class with a public constructor... explicitly excluding: primitives, String and Java enums */
def isPOJO[A](implicit A: Type[A]): Boolean

/** Class defined with "case class" */
def isCaseClass[A](implicit A: Type[A]): Boolean

/** Class defined with "case object" */
def isCaseObject[A](implicit A: Type[A]): Boolean

/** Scala 3 enum's case without parameters (a "val" under the hood, NOT an "object") */
def isCaseVal[A](implicit A: Type[A]): Boolean

/** Java enum value - not the abstract enum type, but the concrete enum value */
def isJavaEnumValue[A](implicit A: Type[A]): Boolean

/** Any POJO with a public DEFAULT constructor... and at least 1 setter or var */
def isJavaBean[A](implicit A: Type[A]): Boolean

def parseExtraction[A: Type]: Option[Product.Extraction[A]]
Expand Down

0 comments on commit 1dea112

Please sign in to comment.