-
Notifications
You must be signed in to change notification settings - Fork 27
Support for Scala 3 #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
44ccd85
7c7f88c
58b6f17
71c0dd1
6ef2c5f
78179fb
ca93ff3
7ea95e3
339b0aa
87a51ed
073549a
3bc960b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,10 @@ name: ci | |
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
tags: | ||
- '*' | ||
pull_request: | ||
branches: | ||
- master | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package mainargs | ||
|
||
import acyclic.skipped | ||
|
||
import scala.language.experimental.macros | ||
|
||
private[mainargs] trait ParserForClassCompanionVersionSpecific { | ||
def apply[T]: ParserForClass[T] = macro Macros.parserForClass[T] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package mainargs | ||
|
||
import acyclic.skipped | ||
|
||
import scala.language.experimental.macros | ||
|
||
private[mainargs] trait ParserForMethodsCompanionVersionSpecific { | ||
def apply[B](base: B): ParserForMethods[B] = macro Macros.parserForMethods[B] | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,152 @@ | ||||||
package mainargs | ||||||
|
||||||
import scala.quoted._ | ||||||
|
||||||
object Macros { | ||||||
private def mainAnnotation(using Quotes) = quotes.reflect.Symbol.requiredClass("mainargs.main") | ||||||
private def argAnnotation(using Quotes) = quotes.reflect.Symbol.requiredClass("mainargs.arg") | ||||||
def parserForMethods[B](base: Expr[B])(using Quotes, Type[B]): Expr[ParserForMethods[B]] = { | ||||||
import quotes.reflect._ | ||||||
val allMethods = TypeRepr.of[B].typeSymbol.memberMethods | ||||||
val annotatedMethodsWithMainAnnotations = allMethods.flatMap { methodSymbol => | ||||||
methodSymbol.getAnnotation(mainAnnotation).map(methodSymbol -> _) | ||||||
}.sortBy(_._1.pos.map(_.start)) | ||||||
val mainDatas = Expr.ofList(annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotationInstance) => | ||||||
createMainData[Any, B](annotatedMethod, mainAnnotationInstance) | ||||||
}) | ||||||
|
||||||
'{ | ||||||
new ParserForMethods[B]( | ||||||
MethodMains[B]($mainDatas, () => $base) | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
def parserForClass[B](using Quotes, Type[B]): Expr[ParserForClass[B]] = { | ||||||
import quotes.reflect._ | ||||||
val typeReprOfB = TypeRepr.of[B] | ||||||
val companionModule = typeReprOfB match { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a chance that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Do you know how?
|
||||||
case TypeRef(a,b) => TermRef(a,b) | ||||||
} | ||||||
val typeSymbolOfB = typeReprOfB.typeSymbol | ||||||
val companionModuleType = typeSymbolOfB.companionModule.tree.asInstanceOf[ValDef].tpt.tpe.asType | ||||||
val companionModuleExpr = Ident(companionModule).asExpr | ||||||
val mainAnnotationInstance = typeSymbolOfB.getAnnotation(mainAnnotation).getOrElse { | ||||||
report.throwError( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get an error since I'm compiling with Scala 3.0.2 and I want to be compatible with Scala 3.0
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lolgab why did you make it throw here? the main annotation is not necessary if you look at the Scala 2 implementation - and a default is provided There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was probably an overlook on my side. If you can change it to match the Scala 2 behavior we can release a new version, so you can continue with Mill's Scala 3 upgrade. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alright, no worries :) |
||||||
s"cannot find @main annotation on ${companionModule.name}", | ||||||
typeSymbolOfB.pos.get | ||||||
) | ||||||
} | ||||||
val annotatedMethod = TypeRepr.of[B].typeSymbol.companionModule.memberMethod("apply").head | ||||||
companionModuleType match | ||||||
case '[bCompanion] => | ||||||
val mainData = createMainData[B, Any](annotatedMethod, mainAnnotationInstance) | ||||||
'{ | ||||||
new ParserForClass[B]( | ||||||
ClassMains[B](${ mainData }, () => ${ Ident(companionModule).asExpr }) | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
def createMainData[T: Type, B: Type](using Quotes)(method: quotes.reflect.Symbol, annotation: quotes.reflect.Term): Expr[MainData[T, B]] = { | ||||||
import quotes.reflect.* | ||||||
val params = method.paramSymss.headOption.getOrElse(report.throwError("Multiple parameter lists not supported")) | ||||||
val defaultParams = getDefaultParams(method) | ||||||
val argSigs = Expr.ofList(params.map { param => | ||||||
val paramTree = param.tree.asInstanceOf[ValDef] | ||||||
val paramTpe = paramTree.tpt.tpe | ||||||
val arg = param.getAnnotation(argAnnotation).map(_.asExprOf[mainargs.arg]).getOrElse('{ new mainargs.arg() }) | ||||||
val paramType = paramTpe.asType | ||||||
paramType match | ||||||
case '[t] => | ||||||
val defaultParam: Expr[Option[B => t]] = defaultParams.get(param) match { | ||||||
case Some('{ $v: `t`}) => '{ Some(((_: B) => $v)) } | ||||||
case None => '{ None } | ||||||
} | ||||||
val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse { | ||||||
report.throwError( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||||||
s"No mainargs.ArgReader found for parameter ${param.name}", | ||||||
param.pos.get | ||||||
) | ||||||
} | ||||||
'{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader })).asInstanceOf[ArgSig[Any, B]] } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might be able to avoid the cast if you do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Otherwise I would sugest using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried both without success. |
||||||
}) | ||||||
|
||||||
val invokeRaw: Expr[(B, Seq[Any]) => T] = { | ||||||
def callOf(args: Expr[Seq[Any]]) = call(method, '{ Seq( ${ args }) }).asExprOf[T] | ||||||
'{ ((b: B, params: Seq[Any]) => ${ callOf('{ params }) }) } | ||||||
} | ||||||
'{ MainData.create[T, B](${ Expr(method.name) }, ${ annotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) } | ||||||
} | ||||||
|
||||||
/** Call a method given by its symbol. | ||||||
* | ||||||
* E.g. | ||||||
* | ||||||
* assuming: | ||||||
* | ||||||
* def foo(x: Int, y: String)(z: Int) | ||||||
* | ||||||
* val argss: List[List[Any]] = ??? | ||||||
* | ||||||
* then: | ||||||
* | ||||||
* call(<symbol of foo>, '{argss}) | ||||||
* | ||||||
* will expand to: | ||||||
* | ||||||
* foo(argss(0)(0), argss(0)(1))(argss(1)(0)) | ||||||
* | ||||||
*/ | ||||||
private def call(using Quotes)( | ||||||
method: quotes.reflect.Symbol, | ||||||
argss: Expr[Seq[Seq[Any]]] | ||||||
): Expr[_] = { | ||||||
// Copy pasted from Cask. | ||||||
// https://github.com/com-lihaoyi/cask/blob/65b9c8e4fd528feb71575f6e5ef7b5e2e16abbd9/cask/src-3/cask/router/Macros.scala#L106 | ||||||
import quotes.reflect._ | ||||||
val paramss = method.paramSymss | ||||||
|
||||||
if (paramss.isEmpty) { | ||||||
report.throwError("At least one parameter list must be declared.", method.pos.get) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||||||
} | ||||||
|
||||||
val fct = Ref(method) | ||||||
|
||||||
val accesses: List[List[Term]] = for (i <- paramss.indices.toList) yield { | ||||||
for (j <- paramss(i).indices.toList) yield { | ||||||
val tpe = paramss(i)(j).tree.asInstanceOf[ValDef].tpt.tpe | ||||||
tpe.asType match | ||||||
case '[t] => '{ $argss(${Expr(i)})(${Expr(j)}).asInstanceOf[t] }.asTerm | ||||||
} | ||||||
} | ||||||
|
||||||
val base = Apply(fct, accesses.head) | ||||||
val application: Apply = accesses.tail.foldLeft(base)((lhs, args) => Apply(lhs, args)) | ||||||
val expr = application.asExpr | ||||||
expr | ||||||
} | ||||||
|
||||||
|
||||||
/** Lookup default values for a method's parameters. */ | ||||||
private def getDefaultParams(using Quotes)(method: quotes.reflect.Symbol): Map[quotes.reflect.Symbol, Expr[Any]] = { | ||||||
// Copy pasted from Cask. | ||||||
// https://github.com/com-lihaoyi/cask/blob/65b9c8e4fd528feb71575f6e5ef7b5e2e16abbd9/cask/src-3/cask/router/Macros.scala#L38 | ||||||
import quotes.reflect._ | ||||||
|
||||||
val params = method.paramSymss.flatten | ||||||
val defaults = collection.mutable.Map.empty[Symbol, Expr[Any]] | ||||||
|
||||||
val Name = (method.name + """\$default\$(\d+)""").r | ||||||
|
||||||
val idents = method.owner.tree.asInstanceOf[ClassDef].body | ||||||
idents.foreach{ | ||||||
case deff @ DefDef(Name(idx), _, _, _) => | ||||||
val expr = Ref(deff.symbol).asExpr | ||||||
defaults += (params(idx.toInt - 1) -> expr) | ||||||
case _ => | ||||||
} | ||||||
|
||||||
defaults.toMap | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package mainargs | ||
|
||
import scala.language.experimental.macros | ||
|
||
private [mainargs] trait ParserForClassCompanionVersionSpecific { | ||
inline def apply[T]: ParserForClass[T] = ${ Macros.parserForClass[T] } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package mainargs | ||
|
||
private [mainargs] trait ParserForMethodsCompanionVersionSpecific { | ||
inline def apply[B](base: B): ParserForMethods[B] = ${ Macros.parserForMethods[B]('base) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package acyclic | ||
|
||
def skipped = ??? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package mainargs | ||
import utest._ | ||
|
||
object OldVarargsTests extends VarargsTests { | ||
object Base { | ||
|
||
@main | ||
def pureVariadic(nums: Int*) = nums.sum | ||
|
||
@main | ||
def mixedVariadic(@arg(short = 'f') first: Int, args: String*) = | ||
first + args.mkString | ||
} | ||
|
||
val check = new Checker(ParserForMethods(Base), allowPositional = true) | ||
val isNewVarargsTests = false | ||
} |
Uh oh!
There was an error while loading. Please reload this page.