Skip to content

Commit 0c2cdb7

Browse files
committed
Partial support for ParserForClass
1 parent 58b6f17 commit 0c2cdb7

17 files changed

+501
-351
lines changed

mainargs/src-2/ParserForClassCompanionVersionSpecific.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import acyclic.skipped
44

55
import scala.language.experimental.macros
66

7-
private [mainargs] trait ParserForClassCompanionVersionSpecific {
7+
private[mainargs] trait ParserForClassCompanionVersionSpecific {
88
def apply[T]: ParserForClass[T] = macro Macros.parserForClass[T]
99
}

mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import acyclic.skipped
44

55
import scala.language.experimental.macros
66

7-
private [mainargs] trait ParserForMethodsCompanionVersionSpecific {
7+
private[mainargs] trait ParserForMethodsCompanionVersionSpecific {
88
def apply[B](base: B): ParserForMethods[B] = macro Macros.parserForMethods[B]
99
}

mainargs/src-3/Macros.scala

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,17 @@ package mainargs
33
import scala.quoted._
44

55
object Macros {
6+
private def mainAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.main].typeSymbol
7+
private def argAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.arg].typeSymbol
68
def parserForMethods[B](base: Expr[B])(using Quotes, Type[B]): Expr[ParserForMethods[B]] = {
79
import quotes.reflect._
810
val allMethods = TypeRepr.of[B].typeSymbol.memberMethods
9-
val mainAnnotation = TypeRepr.of[mainargs.main].typeSymbol
10-
val argAnnotation = TypeRepr.of[mainargs.arg].typeSymbol
1111
val annotatedMethodsWithMainAnnotations = allMethods.flatMap { methodSymbol =>
1212
methodSymbol.getAnnotation(mainAnnotation).map(methodSymbol -> _)
1313
}.sortBy(_._1.pos.map(_.start))
14-
val mainDatasExprs: Seq[Expr[MainData[Any, B]]] = annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotation) =>
15-
val params = annotatedMethod.paramSymss.headOption.getOrElse(throw new Exception("Multiple parameter lists not supported"))
16-
val defaultParams = getDefaultParams(annotatedMethod)
17-
val argSigs = Expr.ofList(params.map { param =>
18-
val paramTree = param.tree.asInstanceOf[ValDef]
19-
val paramTpe = paramTree.tpt.tpe
20-
val arg = param.getAnnotation(argAnnotation).map(_.asExpr.asInstanceOf[Expr[mainargs.arg]]).getOrElse('{ new mainargs.arg() })
21-
val paramType = paramTpe.asType
22-
paramType match
23-
case '[t] =>
24-
val defaultParam: Expr[Option[B => t]] = defaultParams.get(param) match {
25-
case Some(v) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) }
26-
case None => '{ None }
27-
}
28-
val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{
29-
report.error(
30-
s"No mainargs.ArgReader of ${paramTpe.typeSymbol.fullName} found for parameter ${param.name}",
31-
param.pos.get
32-
)
33-
'{ ??? }
34-
}
35-
'{ ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader }).asInstanceOf[mainargs.ArgSig[Any, B]] }
36-
})
37-
38-
val invokeRaw: Expr[(B, Seq[Any]) => Any] = {
39-
def callOf(args: Expr[Seq[Any]]) = call(annotatedMethod, '{ Seq( ${ args }) })
40-
'{ (b: B, params: Seq[Any]) =>
41-
${ callOf('{ params }) }
42-
}
43-
}
44-
45-
'{ MainData.create[Any, B](${ Expr(annotatedMethod.name) }, ${ mainAnnotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) }
46-
}
47-
val mainDatas = Expr.ofList(mainDatasExprs)
14+
val mainDatas = Expr.ofList(annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotationInstance) =>
15+
createMainData[Any, B](annotatedMethod, mainAnnotationInstance)
16+
})
4817

4918
'{
5019
new ParserForMethods[B](
@@ -53,6 +22,65 @@ object Macros {
5322
}
5423
}
5524

25+
def parserForClass[B](using Quotes, Type[B]): Expr[ParserForClass[B]] = {
26+
import quotes.reflect._
27+
val typeReprOfB = TypeRepr.of[B]
28+
val companionModule = typeReprOfB match {
29+
case TypeRef(a,b) => TermRef(a,b)
30+
}
31+
val typeSymbolOfB = typeReprOfB.typeSymbol
32+
val companionModuleType = typeSymbolOfB.companionModule.tree.asInstanceOf[ValDef].tpt.tpe.asType
33+
val companionModuleExpr = Ident(companionModule).asExpr
34+
val mainAnnotationInstance = typeSymbolOfB.getAnnotation(mainAnnotation).getOrElse {
35+
report.error(
36+
s"cannot find @main annotation on ${companionModule.name}",
37+
typeSymbolOfB.pos.get
38+
)
39+
???
40+
}
41+
val annotatedMethod = TypeRepr.of[B].typeSymbol.companionModule.memberMethod("apply").head
42+
companionModuleType match
43+
case '[bCompanion] =>
44+
val mainData = createMainData[B, bCompanion](annotatedMethod, mainAnnotationInstance)
45+
'{
46+
new ParserForClass[B](
47+
ClassMains[B](${ mainData }.asInstanceOf[MainData[B, Any]], () => ${ Ident(companionModule).asExpr })
48+
)
49+
}
50+
}
51+
52+
def createMainData[T: Type, B: Type](using Quotes)(method: quotes.reflect.Symbol, annotation: quotes.reflect.Term): Expr[MainData[T, B]] = {
53+
import quotes.reflect.*
54+
val params = method.paramSymss.headOption.getOrElse(throw new Exception("Multiple parameter lists not supported"))
55+
val defaultParams = getDefaultParams(method)
56+
val argSigs = Expr.ofList(params.map { param =>
57+
val paramTree = param.tree.asInstanceOf[ValDef]
58+
val paramTpe = paramTree.tpt.tpe
59+
val arg = param.getAnnotation(argAnnotation).map(_.asExpr.asInstanceOf[Expr[mainargs.arg]]).getOrElse('{ new mainargs.arg() })
60+
val paramType = paramTpe.asType
61+
paramType match
62+
case '[t] =>
63+
val defaultParam: Expr[Option[B => t]] = defaultParams.get(param) match {
64+
case Some(v) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) }
65+
case None => '{ None }
66+
}
67+
val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{
68+
report.error(
69+
s"No mainargs.ArgReader of ###companionModule### found for parameter ${param.name}",
70+
param.pos.get
71+
)
72+
'{ ??? }
73+
}
74+
'{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader })).asInstanceOf[ArgSig[Any, B]] }
75+
})
76+
77+
val invokeRaw: Expr[(B, Seq[Any]) => T] = {
78+
def callOf(args: Expr[Seq[Any]]) = call(method, '{ Seq( ${ args }) })
79+
'{ ((b: B, params: Seq[Any]) => ${ callOf('{ params }) }).asInstanceOf[(B, Seq[Any]) => T] }
80+
}
81+
'{ MainData.create[T, B](${ Expr(method.name) }, ${ annotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) }
82+
}
83+
5684
/** Call a method given by its symbol.
5785
*
5886
* E.g.

mainargs/src-3/ParserForClassCompanionVersionSpecific.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package mainargs
33
import scala.language.experimental.macros
44

55
private [mainargs] trait ParserForClassCompanionVersionSpecific {
6-
inline def apply[T]: ParserForClass[T] = ???
6+
inline def apply[T]: ParserForClass[T] = ${ Macros.parserForClass[T] }
77
}

mainargs/test/src-2/ClassTests.scala

Lines changed: 0 additions & 101 deletions
This file was deleted.

mainargs/test/src-2/OldVarargsTests.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package mainargs
22
import utest._
33

4-
object OldVarargsTests extends VarargsTests{
5-
object Base{
4+
object OldVarargsTests extends VarargsTests {
5+
object Base {
66

77
@main
88
def pureVariadic(nums: Int*) = nums.sum
99

1010
@main
11-
def mixedVariadic(@arg(short = 'f') first: Int, args: String*) = first + args.mkString
11+
def mixedVariadic(@arg(short = 'f') first: Int, args: String*) =
12+
first + args.mkString
1213
}
1314

1415
val check = new Checker(ParserForMethods(Base), allowPositional = true)

mainargs/test/src-2/ParserTests.scala

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package mainargs
2+
3+
object VersionSpecific {
4+
val isScala3 = false
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package mainargs
2+
3+
object VersionSpecific {
4+
val isScala3 = true
5+
}

0 commit comments

Comments
 (0)