From 44ccd8542cf33db7e2d7c09f196136ad2cfea3bc Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Sun, 27 Feb 2022 15:24:36 +0100 Subject: [PATCH 01/12] Partial Support for Scala 3 This implements the support for the `@main` annotated methods in Scala 3. While `ParserForMethods` is implemented and passes all tests (except the tests using the old varargs that were moved in the `src-2` directory), `ParserForClass` is not implemented yet. --- .github/workflows/actions.yml | 4 + build.sc | 53 ++++--- mainargs/{src => src-2}/Macros.scala | 0 ...rserForClassCompanionVersionSpecific.scala | 9 ++ ...erForMethodsCompanionVersionSpecific.scala | 9 ++ mainargs/src-3/Macros.scala | 133 ++++++++++++++++++ ...rserForClassCompanionVersionSpecific.scala | 7 + ...erForMethodsCompanionVersionSpecific.scala | 5 + mainargs/src-3/acyclic.scala | 3 + mainargs/src/Parser.scala | 12 +- mainargs/src/TokensReader.scala | 6 +- mainargs/test/{src => src-2}/ClassTests.scala | 9 +- mainargs/test/{src => src-2}/ManyTests.scala | 0 mainargs/test/src-2/OldVarargsTests.scala | 16 +++ .../test/{src => src-2}/ParserTests.scala | 0 .../AmmoniteTests.scala | 8 +- .../{src-jvm => src-jvm-2}/MillTests.scala | 18 +-- mainargs/test/src/NewVarargsTests.scala | 21 +++ mainargs/test/src/VarargsTests.scala | 34 +---- mill | 4 +- 20 files changed, 266 insertions(+), 85 deletions(-) rename mainargs/{src => src-2}/Macros.scala (100%) create mode 100644 mainargs/src-2/ParserForClassCompanionVersionSpecific.scala create mode 100644 mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala create mode 100644 mainargs/src-3/Macros.scala create mode 100644 mainargs/src-3/ParserForClassCompanionVersionSpecific.scala create mode 100644 mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala create mode 100644 mainargs/src-3/acyclic.scala rename mainargs/test/{src => src-2}/ClassTests.scala (91%) rename mainargs/test/{src => src-2}/ManyTests.scala (100%) create mode 100644 mainargs/test/src-2/OldVarargsTests.scala rename mainargs/test/{src => src-2}/ParserTests.scala (100%) rename mainargs/test/{src-jvm => src-jvm-2}/AmmoniteTests.scala (95%) rename mainargs/test/{src-jvm => src-jvm-2}/MillTests.scala (98%) create mode 100644 mainargs/test/src/NewVarargsTests.scala diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 4d7929a..69438eb 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -2,6 +2,10 @@ name: ci on: push: + branches: + - master + tags: + - '*' pull_request: branches: - master diff --git a/build.sc b/build.sc index 110cfbc..259613b 100644 --- a/build.sc +++ b/build.sc @@ -1,20 +1,25 @@ import mill._, scalalib._, scalajslib._, scalanativelib._, publish._ +import mill.scalalib.api.Util.isScala3 import scalalib._ -import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version_mill0.9:0.1.1` +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.1` import de.tobiasroeser.mill.vcs.version.VcsVersion -import $ivy.`com.github.lolgab::mill-mima_mill0.9:0.0.4` +import $ivy.`com.github.lolgab::mill-mima::0.0.4` import com.github.lolgab.mill.mima._ val scala212 = "2.12.13" val scala213 = "2.13.4" +val scala3 = "3.0.2" + +val scalaVersions = Seq(scala212, scala213, scala3) +val scala2Versions = scalaVersions.filter(_.startsWith("2.")) val scalaJSVersions = for { - scalaV <- Seq(scala213, scala212) + scalaV <- scalaVersions scalaJSV <- Seq("1.4.0") } yield (scalaV, scalaJSV) val scalaNativeVersions = for { - scalaV <- Seq(scala213, scala212) + scalaV <- scala2Versions scalaNativeV <- Seq("0.4.0") } yield (scalaV, scalaNativeV) @@ -39,48 +44,50 @@ trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mim ) ) - def scalacOptions = super.scalacOptions() ++ Seq("-P:acyclic:force") + def scalacOptions = super.scalacOptions() ++ (if (!isScala3(crossScalaVersion)) Seq("-P:acyclic:force") else Seq.empty) - def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg(ivy"com.lihaoyi::acyclic:0.2.0") + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ (if (!isScala3(crossScalaVersion)) Agg(ivy"com.lihaoyi::acyclic:0.2.0") else Agg.empty) - def compileIvyDeps = super.compileIvyDeps() ++ Agg( - ivy"com.lihaoyi::acyclic:0.2.0", - ivy"org.scala-lang:scala-reflect:$crossScalaVersion" - ) + def compileIvyDeps = super.compileIvyDeps() ++ (if (!isScala3(crossScalaVersion)) Agg( + ivy"com.lihaoyi::acyclic:0.2.0", + ivy"org.scala-lang:scala-reflect:$crossScalaVersion" + ) else Agg.empty) def ivyDeps = Agg( - ivy"org.scala-lang.modules::scala-collection-compat::2.4.0" - ) + ivy"org.scala-lang.modules::scala-collection-compat::2.4.4" + ) ++ Agg(ivy"com.lihaoyi::pprint:0.6.6") } trait Common extends CrossScalaModule { def millSourcePath = build.millSourcePath / "mainargs" def sources = T.sources( - millSourcePath / "src", - millSourcePath / s"src-$platform" + super.sources() ++ Seq(PathRef(millSourcePath / s"src-$platform")) ) def platform: String } -trait CommonTestModule extends ScalaModule with TestModule { - def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.6") - def testFrameworks = Seq("utest.runner.Framework") - def sources = T.sources( - millSourcePath / "src", - millSourcePath / s"src-$platform" - ) +trait CommonTestModule extends ScalaModule with TestModule.Utest { + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.10") + def sources = T.sources { + val scalaMajor = if(isScala3(scalaVersion())) "3" else "2" + super.sources() ++ Seq( + millSourcePath / "src", + millSourcePath / s"src-$platform", + millSourcePath / s"src-$platform-$scalaMajor" + ).map(PathRef(_)) + } def platform: String } object mainargs extends Module { - object jvm extends Cross[JvmMainArgsModule](scala212, scala213) + object jvm extends Cross[JvmMainArgsModule](scalaVersions: _*) class JvmMainArgsModule(val crossScalaVersion: String) extends Common with ScalaModule with MainArgsPublishModule { def platform = "jvm" object test extends Tests with CommonTestModule{ def platform = "jvm" - def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::os-lib:0.7.1") + def ivyDeps = super.ivyDeps() ++ Agg(ivy"com.lihaoyi::os-lib:0.7.8") } } diff --git a/mainargs/src/Macros.scala b/mainargs/src-2/Macros.scala similarity index 100% rename from mainargs/src/Macros.scala rename to mainargs/src-2/Macros.scala diff --git a/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala b/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala new file mode 100644 index 0000000..3649936 --- /dev/null +++ b/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala @@ -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] +} diff --git a/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala b/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala new file mode 100644 index 0000000..cff9d25 --- /dev/null +++ b/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala @@ -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] +} diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala new file mode 100644 index 0000000..baac241 --- /dev/null +++ b/mainargs/src-3/Macros.scala @@ -0,0 +1,133 @@ +package mainargs + +import scala.quoted._ + +object Macros { + def parserForMethods[B](base: Expr[B])(using Quotes, Type[B]): Expr[ParserForMethods[B]] = { + import quotes.reflect._ + val allMethods = TypeRepr.of[B].typeSymbol.memberMethods + val mainAnnotation = TypeRepr.of[mainargs.main].typeSymbol + val argAnnotation = TypeRepr.of[mainargs.arg].typeSymbol + val annotatedMethodsWithMainAnnotations = allMethods.flatMap { methodSymbol => + methodSymbol.getAnnotation(mainAnnotation).map(methodSymbol -> _) + }.sortBy(_._1.pos.map(_.start)) + val mainDatasExprs: Seq[Expr[MainData[Any, B]]] = annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotation) => + val doc: Option[String] = mainAnnotation match { + case Apply(_, args) => args.collectFirst { + case NamedArg("doc", Literal(constant)) => constant.value.asInstanceOf[String] + } + case _ => None + } + val params = annotatedMethod.paramSymss.headOption.getOrElse(throw new Exception("Multiple parameter lists not supported")) + val defaultParams = getDefaultParams(annotatedMethod) + val argSigs = Expr.ofList(params.map { param => + val paramTree = param.tree.asInstanceOf[ValDef] + val paramTpe = paramTree.tpt.tpe + val arg = param.getAnnotation(argAnnotation).map(_.asExpr.asInstanceOf[Expr[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) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) } + case None => '{ None } + } + val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{ + report.error( + s"No mainargs.ArgReader of ${paramTpe.typeSymbol.fullName} found for parameter ${param.name}", + param.pos.get + ) + '{ ??? } + } + '{ ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader }) } + }) + + val invokeRaw: Expr[(B, Seq[Any]) => Any] = { + def callOf(args: Expr[Seq[Any]]) = call(annotatedMethod, '{ Seq( ${ args }) }) + '{ (b: B, params: Seq[Any]) => + ${ callOf('{ params }) } + } + } + + '{ MainData[Any, B](${ Expr(annotatedMethod.name) }, ${ argSigs }, ${Expr(doc)}, ${ invokeRaw }) } + } + val mainDatas = Expr.ofList(mainDatasExprs) + + '{ + new ParserForMethods[B]( + MethodMains[B](${ mainDatas }, () => ${ base }) + ) + } + } + + /** 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(, '{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.error("At least one parameter list must be declared.", method.pos.get) + return '{???} + } + + 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 + } +} diff --git a/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala b/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala new file mode 100644 index 0000000..01af670 --- /dev/null +++ b/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala @@ -0,0 +1,7 @@ +package mainargs + +import scala.language.experimental.macros + +private [mainargs] trait ParserForClassCompanionVersionSpecific { + inline def apply[T]: ParserForClass[T] = ??? +} diff --git a/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala new file mode 100644 index 0000000..4307456 --- /dev/null +++ b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala @@ -0,0 +1,5 @@ +package mainargs + +private [mainargs] trait ParserForMethodsCompanionVersionSpecific { + inline def apply[B](base: B) : ParserForMethods[B] = ${ Macros.parserForMethods[B]('base) } +} \ No newline at end of file diff --git a/mainargs/src-3/acyclic.scala b/mainargs/src-3/acyclic.scala new file mode 100644 index 0000000..efd59c7 --- /dev/null +++ b/mainargs/src-3/acyclic.scala @@ -0,0 +1,3 @@ +package acyclic + +def skipped = ??? diff --git a/mainargs/src/Parser.scala b/mainargs/src/Parser.scala index 226f672..1fe2f94 100644 --- a/mainargs/src/Parser.scala +++ b/mainargs/src/Parser.scala @@ -1,9 +1,11 @@ package mainargs + +import acyclic.skipped + import scala.language.experimental.macros import java.io.PrintStream -object ParserForMethods{ - def apply[B](base: B): ParserForMethods[B] = macro Macros.parserForMethods[B] -} + +object ParserForMethods extends ParserForMethodsCompanionVersionSpecific class ParserForMethods[B](val mains: MethodMains[B]){ def helpText(totalWidth: Int = 100, docsOnNewLine: Boolean = false, @@ -102,9 +104,7 @@ class ParserForMethods[B](val mains: MethodMains[B]){ } } -object ParserForClass{ - def apply[T]: ParserForClass[T] = macro Macros.parserForClass[T] -} +object ParserForClass extends ParserForClassCompanionVersionSpecific class ParserForClass[T](val mains: ClassMains[T]) extends SubParser[T]{ def helpText(totalWidth: Int = 100, docsOnNewLine: Boolean = false, diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 920f021..064d3a1 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -18,7 +18,7 @@ object TokensReader{ implicit object FloatRead extends TokensReader[Float]("float", strs => tryEither(strs.last.toFloat)) implicit object DoubleRead extends TokensReader[Double]("double", strs => tryEither(strs.last.toDouble)) - implicit def OptionRead[T: TokensReader] = new TokensReader[Option[T]]( + implicit def OptionRead[T: TokensReader]: TokensReader[Option[T]] = new TokensReader[Option[T]]( implicitly[TokensReader[T]].shortName, strs => { strs.lastOption match{ @@ -31,7 +31,7 @@ object TokensReader{ }, allowEmpty = true ) - implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader](implicit factory: Factory[T, C[T]]) = new TokensReader[C[T]]( + implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader](implicit factory: Factory[T, C[T]]): TokensReader[C[T]] = new TokensReader[C[T]]( implicitly[TokensReader[T]].shortName, strs => { strs @@ -50,7 +50,7 @@ object TokensReader{ alwaysRepeatable = true, allowEmpty = true ) - implicit def MapRead[K: TokensReader, V: TokensReader] = new TokensReader[Map[K, V]]( + implicit def MapRead[K: TokensReader, V: TokensReader]: TokensReader[Map[K, V]] = new TokensReader[Map[K, V]]( "k=v", strs => { strs.foldLeft[Either[String, Map[K, V]]](Right(Map())){ diff --git a/mainargs/test/src/ClassTests.scala b/mainargs/test/src-2/ClassTests.scala similarity index 91% rename from mainargs/test/src/ClassTests.scala rename to mainargs/test/src-2/ClassTests.scala index c12cd47..ef2a655 100644 --- a/mainargs/test/src/ClassTests.scala +++ b/mainargs/test/src-2/ClassTests.scala @@ -13,15 +13,15 @@ object ClassTests extends TestSuite{ @main case class Qux(moo: String, b: Bar) - implicit val fooParser = ParserForClass[Foo] - implicit val barParser = ParserForClass[Bar] - implicit val quxParser = ParserForClass[Qux] + implicit val fooParser: ParserForClass[Foo] = ParserForClass[Foo] + implicit val barParser: ParserForClass[Bar] = ParserForClass[Bar] + implicit val quxParser: ParserForClass[Qux] = ParserForClass[Qux] object Main{ @main def run(bar: Bar, bool: Boolean = false) = { - bar.w.value + " " + bar.f.x + " " + bar.f.y + " " + bar.zzzz + " " + bool + s"${bar.w.value} ${bar.f.x} ${bar.f.y} ${bar.zzzz} $bool" } } @@ -99,4 +99,3 @@ object ClassTests extends TestSuite{ } } } - diff --git a/mainargs/test/src/ManyTests.scala b/mainargs/test/src-2/ManyTests.scala similarity index 100% rename from mainargs/test/src/ManyTests.scala rename to mainargs/test/src-2/ManyTests.scala diff --git a/mainargs/test/src-2/OldVarargsTests.scala b/mainargs/test/src-2/OldVarargsTests.scala new file mode 100644 index 0000000..1534ec2 --- /dev/null +++ b/mainargs/test/src-2/OldVarargsTests.scala @@ -0,0 +1,16 @@ +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 +} diff --git a/mainargs/test/src/ParserTests.scala b/mainargs/test/src-2/ParserTests.scala similarity index 100% rename from mainargs/test/src/ParserTests.scala rename to mainargs/test/src-2/ParserTests.scala diff --git a/mainargs/test/src-jvm/AmmoniteTests.scala b/mainargs/test/src-jvm-2/AmmoniteTests.scala similarity index 95% rename from mainargs/test/src-jvm/AmmoniteTests.scala rename to mainargs/test/src-jvm-2/AmmoniteTests.scala index aaa9705..f191e9f 100644 --- a/mainargs/test/src-jvm/AmmoniteTests.scala +++ b/mainargs/test/src-jvm-2/AmmoniteTests.scala @@ -69,7 +69,7 @@ object AmmoniteConfig{ @arg(doc = "Print this message") help: Flag ) - implicit val coreParser = ParserForClass[Core] + implicit val coreParser: ParserForClass[Core] = ParserForClass[Core] @main case class Predef( @@ -86,7 +86,7 @@ object AmmoniteConfig{ "choose an additional predef to use using `--predef") noHomePredef: Flag ) - implicit val predefParser = ParserForClass[Predef] + implicit val predefParser: ParserForClass[Predef] = ParserForClass[Predef] @main case class Repl( @@ -105,12 +105,12 @@ object AmmoniteConfig{ "friendliness.") classBased: Flag ) - implicit val replParser = ParserForClass[Repl] + implicit val replParser: ParserForClass[Repl] = ParserForClass[Repl] } object AmmoniteTests extends TestSuite{ - val parser = ParserForClass[AmmoniteConfig] + val parser: ParserForClass[AmmoniteConfig] = ParserForClass[AmmoniteConfig] val tests = Tests { diff --git a/mainargs/test/src-jvm/MillTests.scala b/mainargs/test/src-jvm-2/MillTests.scala similarity index 98% rename from mainargs/test/src-jvm/MillTests.scala rename to mainargs/test/src-jvm-2/MillTests.scala index f5a1d11..03d6810 100644 --- a/mainargs/test/src-jvm/MillTests.scala +++ b/mainargs/test/src-jvm-2/MillTests.scala @@ -1,9 +1,9 @@ -package mainargs -import utest._ +// package mainargs +// import utest._ + + +// object MillTests extends TestSuite{ -// -//object MillTests extends TestSuite{ -// // implicit object PathRead extends TokensReader[os.Path]("path", strs => Right(os.Path(strs.head, os.pwd))) // @main( // name = "Mill Build Tool", @@ -53,11 +53,11 @@ import utest._ // threadCount: Int = 1, // ammoniteConig: AmmoniteTests.Config = AmmoniteTests.Config() // ) -// + // val tests = Tests { -// + // val parser = ParserForClass[Config] -// + // test("formatMainMethods"){ // val rendered = parser.helpText() // val expected = @@ -106,5 +106,5 @@ import utest._ // Right(Config(threadCount = 12)) // } // } -//} +// } diff --git a/mainargs/test/src/NewVarargsTests.scala b/mainargs/test/src/NewVarargsTests.scala new file mode 100644 index 0000000..21c44bb --- /dev/null +++ b/mainargs/test/src/NewVarargsTests.scala @@ -0,0 +1,21 @@ +package mainargs +import utest._ +object NewVarargsTests extends VarargsTests{ + object Base{ + @main + def pureVariadic(nums: Leftover[Int]) = nums.value.sum + + @main + def mixedVariadic(@arg(short = 'f') first: Int, args: Leftover[String]) = { + first + args.value.mkString + } + @main + def mixedVariadicWithDefault(@arg(short = 'f') first: Int = 1337, + args: Leftover[String]) = { + first + args.value.mkString + } + } + + val check = new Checker(ParserForMethods(Base), allowPositional = true) + val isNewVarargsTests = true +} diff --git a/mainargs/test/src/VarargsTests.scala b/mainargs/test/src/VarargsTests.scala index 5504acd..cfeaa0b 100644 --- a/mainargs/test/src/VarargsTests.scala +++ b/mainargs/test/src/VarargsTests.scala @@ -1,39 +1,9 @@ package mainargs import utest._ -object NewVarargsTests extends VarargsTests{ - object Base{ - @main - def pureVariadic(nums: Leftover[Int]) = nums.value.sum - - @main - def mixedVariadic(@arg(short = 'f') first: Int, args: Leftover[String]) = { - first + args.value.mkString - } - @main - def mixedVariadicWithDefault(@arg(short = 'f') first: Int = 1337, - args: Leftover[String]) = { - first + args.value.mkString - } - } - - val check = new Checker(ParserForMethods(Base), allowPositional = true) -} - -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) -} trait VarargsTests extends TestSuite{ def check: Checker[_] + def isNewVarargsTests: Boolean val tests = Tests { test("happyPathPasses"){ @@ -45,7 +15,7 @@ trait VarargsTests extends TestSuite{ Result.Success("12345") ) test - { - if (this == NewVarargsTests) check( + if (isNewVarargsTests) check( List("mixedVariadicWithDefault"), Result.Success("1337") ) diff --git a/mill b/mill index e9a95e4..e0dbdff 100755 --- a/mill +++ b/mill @@ -3,9 +3,7 @@ # This is a wrapper script, that automatically download mill from GitHub release pages # You can give the required mill version with MILL_VERSION env variable # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -DEFAULT_MILL_VERSION=0.9.4-18-82ea87 - - +DEFAULT_MILL_VERSION=0.9.10 set -e From 7c7f88c6d2c0c108caf280c67714619ebc1cede6 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Sun, 27 Feb 2022 18:26:22 +0100 Subject: [PATCH 02/12] Add empty artifacts for Mima on Scala 3 --- build.sc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sc b/build.sc index 259613b..f9380ff 100644 --- a/build.sc +++ b/build.sc @@ -15,7 +15,7 @@ val scala2Versions = scalaVersions.filter(_.startsWith("2.")) val scalaJSVersions = for { scalaV <- scalaVersions - scalaJSV <- Seq("1.4.0") + scalaJSV <- Seq("1.5.1") } yield (scalaV, scalaJSV) val scalaNativeVersions = for { @@ -31,6 +31,8 @@ trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mim .lastTag .getOrElse(throw new Exception("Missing last tag")) ) + // Remove after Scala 3 artifacts are published + def mimaPreviousArtifacts = T{ if(isScala3(scalaVersion())) Seq() else super.mimaPreviousArtifacts() } def artifactName = "mainargs" def pomSettings = PomSettings( From 58b6f1749abe5d3dd025afc0015854608d5d5ea3 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 28 Feb 2022 12:18:49 +0100 Subject: [PATCH 03/12] Use MainData.create instead of MainData.apply --- mainargs/src-3/Macros.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index baac241..8500a62 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -12,12 +12,6 @@ object Macros { methodSymbol.getAnnotation(mainAnnotation).map(methodSymbol -> _) }.sortBy(_._1.pos.map(_.start)) val mainDatasExprs: Seq[Expr[MainData[Any, B]]] = annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotation) => - val doc: Option[String] = mainAnnotation match { - case Apply(_, args) => args.collectFirst { - case NamedArg("doc", Literal(constant)) => constant.value.asInstanceOf[String] - } - case _ => None - } val params = annotatedMethod.paramSymss.headOption.getOrElse(throw new Exception("Multiple parameter lists not supported")) val defaultParams = getDefaultParams(annotatedMethod) val argSigs = Expr.ofList(params.map { param => @@ -38,7 +32,7 @@ object Macros { ) '{ ??? } } - '{ ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader }) } + '{ ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader }).asInstanceOf[mainargs.ArgSig[Any, B]] } }) val invokeRaw: Expr[(B, Seq[Any]) => Any] = { @@ -48,7 +42,7 @@ object Macros { } } - '{ MainData[Any, B](${ Expr(annotatedMethod.name) }, ${ argSigs }, ${Expr(doc)}, ${ invokeRaw }) } + '{ MainData.create[Any, B](${ Expr(annotatedMethod.name) }, ${ mainAnnotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) } } val mainDatas = Expr.ofList(mainDatasExprs) From 71c0dd1b4d291c291d5454b7368793e4e51c1e2c Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Mon, 28 Feb 2022 22:12:05 +0100 Subject: [PATCH 04/12] Partial support for ParserForClass --- ...rserForClassCompanionVersionSpecific.scala | 2 +- ...erForMethodsCompanionVersionSpecific.scala | 2 +- mainargs/src-3/Macros.scala | 100 ++++++---- ...rserForClassCompanionVersionSpecific.scala | 2 +- ...erForMethodsCompanionVersionSpecific.scala | 2 +- mainargs/test/src-2/ClassTests.scala | 101 ---------- mainargs/test/src-2/OldVarargsTests.scala | 7 +- mainargs/test/src-2/ParserTests.scala | 53 ------ mainargs/test/src-2/VersionSpecific.scala | 5 + mainargs/test/src-3/VersionSpecific.scala | 5 + mainargs/test/src-jvm-2/AmmoniteTests.scala | 8 +- mainargs/test/src-jvm-2/MillTests.scala | 2 - mainargs/test/src/ClassTests.scala | 173 ++++++++++++++++++ mainargs/test/{src-2 => src}/ManyTests.scala | 0 mainargs/test/src/NewVarargsTests.scala | 10 +- mainargs/test/src/ParserTests.scala | 61 ++++++ mainargs/test/src/TestUtils.scala | 7 + mainargs/test/src/VarargsTests.scala | 134 ++++++++------ 18 files changed, 406 insertions(+), 268 deletions(-) delete mode 100644 mainargs/test/src-2/ClassTests.scala delete mode 100644 mainargs/test/src-2/ParserTests.scala create mode 100644 mainargs/test/src-2/VersionSpecific.scala create mode 100644 mainargs/test/src-3/VersionSpecific.scala create mode 100644 mainargs/test/src/ClassTests.scala rename mainargs/test/{src-2 => src}/ManyTests.scala (100%) create mode 100644 mainargs/test/src/ParserTests.scala create mode 100644 mainargs/test/src/TestUtils.scala diff --git a/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala b/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala index 3649936..7e8feb4 100644 --- a/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala +++ b/mainargs/src-2/ParserForClassCompanionVersionSpecific.scala @@ -4,6 +4,6 @@ import acyclic.skipped import scala.language.experimental.macros -private [mainargs] trait ParserForClassCompanionVersionSpecific { +private[mainargs] trait ParserForClassCompanionVersionSpecific { def apply[T]: ParserForClass[T] = macro Macros.parserForClass[T] } diff --git a/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala b/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala index cff9d25..4e43163 100644 --- a/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala +++ b/mainargs/src-2/ParserForMethodsCompanionVersionSpecific.scala @@ -4,6 +4,6 @@ import acyclic.skipped import scala.language.experimental.macros -private [mainargs] trait ParserForMethodsCompanionVersionSpecific { +private[mainargs] trait ParserForMethodsCompanionVersionSpecific { def apply[B](base: B): ParserForMethods[B] = macro Macros.parserForMethods[B] } diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 8500a62..4c0e790 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -3,48 +3,17 @@ package mainargs import scala.quoted._ object Macros { + private def mainAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.main].typeSymbol + private def argAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.arg].typeSymbol def parserForMethods[B](base: Expr[B])(using Quotes, Type[B]): Expr[ParserForMethods[B]] = { import quotes.reflect._ val allMethods = TypeRepr.of[B].typeSymbol.memberMethods - val mainAnnotation = TypeRepr.of[mainargs.main].typeSymbol - val argAnnotation = TypeRepr.of[mainargs.arg].typeSymbol val annotatedMethodsWithMainAnnotations = allMethods.flatMap { methodSymbol => methodSymbol.getAnnotation(mainAnnotation).map(methodSymbol -> _) }.sortBy(_._1.pos.map(_.start)) - val mainDatasExprs: Seq[Expr[MainData[Any, B]]] = annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotation) => - val params = annotatedMethod.paramSymss.headOption.getOrElse(throw new Exception("Multiple parameter lists not supported")) - val defaultParams = getDefaultParams(annotatedMethod) - val argSigs = Expr.ofList(params.map { param => - val paramTree = param.tree.asInstanceOf[ValDef] - val paramTpe = paramTree.tpt.tpe - val arg = param.getAnnotation(argAnnotation).map(_.asExpr.asInstanceOf[Expr[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) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) } - case None => '{ None } - } - val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{ - report.error( - s"No mainargs.ArgReader of ${paramTpe.typeSymbol.fullName} found for parameter ${param.name}", - param.pos.get - ) - '{ ??? } - } - '{ ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader }).asInstanceOf[mainargs.ArgSig[Any, B]] } - }) - - val invokeRaw: Expr[(B, Seq[Any]) => Any] = { - def callOf(args: Expr[Seq[Any]]) = call(annotatedMethod, '{ Seq( ${ args }) }) - '{ (b: B, params: Seq[Any]) => - ${ callOf('{ params }) } - } - } - - '{ MainData.create[Any, B](${ Expr(annotatedMethod.name) }, ${ mainAnnotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) } - } - val mainDatas = Expr.ofList(mainDatasExprs) + val mainDatas = Expr.ofList(annotatedMethodsWithMainAnnotations.map { (annotatedMethod, mainAnnotationInstance) => + createMainData[Any, B](annotatedMethod, mainAnnotationInstance) + }) '{ new ParserForMethods[B]( @@ -53,6 +22,65 @@ object Macros { } } + def parserForClass[B](using Quotes, Type[B]): Expr[ParserForClass[B]] = { + import quotes.reflect._ + val typeReprOfB = TypeRepr.of[B] + val companionModule = typeReprOfB match { + 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.error( + 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, bCompanion](annotatedMethod, mainAnnotationInstance) + '{ + new ParserForClass[B]( + ClassMains[B](${ mainData }.asInstanceOf[MainData[B, Any]], () => ${ 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(throw new Exception("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(_.asExpr.asInstanceOf[Expr[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) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) } + case None => '{ None } + } + val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{ + report.error( + s"No mainargs.ArgReader of ###companionModule### found for parameter ${param.name}", + param.pos.get + ) + '{ ??? } + } + '{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader })).asInstanceOf[ArgSig[Any, B]] } + }) + + val invokeRaw: Expr[(B, Seq[Any]) => T] = { + def callOf(args: Expr[Seq[Any]]) = call(method, '{ Seq( ${ args }) }) + '{ ((b: B, params: Seq[Any]) => ${ callOf('{ params }) }).asInstanceOf[(B, Seq[Any]) => T] } + } + '{ MainData.create[T, B](${ Expr(method.name) }, ${ annotation.asExprOf[mainargs.main] }, ${ argSigs }, ${ invokeRaw }) } + } + /** Call a method given by its symbol. * * E.g. diff --git a/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala b/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala index 01af670..ae1ac2d 100644 --- a/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala +++ b/mainargs/src-3/ParserForClassCompanionVersionSpecific.scala @@ -3,5 +3,5 @@ package mainargs import scala.language.experimental.macros private [mainargs] trait ParserForClassCompanionVersionSpecific { - inline def apply[T]: ParserForClass[T] = ??? + inline def apply[T]: ParserForClass[T] = ${ Macros.parserForClass[T] } } diff --git a/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala index 4307456..ff9509f 100644 --- a/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala +++ b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala @@ -2,4 +2,4 @@ package mainargs private [mainargs] trait ParserForMethodsCompanionVersionSpecific { inline def apply[B](base: B) : ParserForMethods[B] = ${ Macros.parserForMethods[B]('base) } -} \ No newline at end of file +} diff --git a/mainargs/test/src-2/ClassTests.scala b/mainargs/test/src-2/ClassTests.scala deleted file mode 100644 index ef2a655..0000000 --- a/mainargs/test/src-2/ClassTests.scala +++ /dev/null @@ -1,101 +0,0 @@ -package mainargs -import utest._ - - -object ClassTests extends TestSuite{ - - @main - case class Foo(x: Int, y: Int) - - @main - case class Bar(w: Flag = Flag(), f: Foo, @arg(short = 'z') zzzz: String) - - @main - case class Qux(moo: String, b: Bar) - - implicit val fooParser: ParserForClass[Foo] = ParserForClass[Foo] - implicit val barParser: ParserForClass[Bar] = ParserForClass[Bar] - implicit val quxParser: ParserForClass[Qux] = ParserForClass[Qux] - - object Main{ - @main - def run(bar: Bar, - bool: Boolean = false) = { - s"${bar.w.value} ${bar.f.x} ${bar.f.y} ${bar.zzzz} $bool" - } - } - - val tests = Tests { - test("simple") { - test("success"){ - fooParser.constructOrThrow(Seq("-x", "1", "-y", "2")) ==> Foo(1, 2) - } - test("missing") { - fooParser.constructRaw(Seq("-x", "1")) ==> - Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(None, Some('y'),None,None,mainargs.TokensReader.IntRead, false)), - List(), - List(), - None - ) - - } - } - - test("nested") { - test("success"){ - barParser.constructOrThrow(Seq("-w", "-x", "1", "-y", "2", "--zzzz", "xxx")) ==> - Bar(Flag(true), Foo(1, 2), "xxx") - } - test("missingInner"){ - barParser.constructRaw(Seq("-w", "-x", "1", "-z", "xxx")) ==> - Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(None,Some('y'),None,None,mainargs.TokensReader.IntRead, false)), - List(), - List(), - None - ) - } - test("missingOuter"){ - barParser.constructRaw(Seq("-w", "-x", "1", "-y", "2")) ==> - Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(Some("zzzz"),Some('z'),None,None,mainargs.TokensReader.StringRead, false)), - List(), - List(), - None - ) - } - - test("missingInnerOuter"){ - barParser.constructRaw(Seq("-w", "-x", "1")) ==> - Result.Failure.MismatchedArguments( - Seq( - ArgSig.Simple(None,Some('y'),None,None,mainargs.TokensReader.IntRead, false), - ArgSig.Simple(Some("zzzz"),Some('z'),None,None,mainargs.TokensReader.StringRead, false) - ), - List(), - List(), - None - ) - } - test("failedInnerOuter") { - assertMatch(barParser.constructRaw(Seq("-w","-x", "xxx", "-y", "hohoho", "-z", "xxx"))) { - case Result.Failure.InvalidArguments( - Seq( - Result.ParamError.Failed(ArgSig.Simple(None, Some('x'), None, None, _, false), Seq("xxx"), _), - Result.ParamError.Failed(ArgSig.Simple(None, Some('y'), None, None, _, false), Seq("hohoho"), _) - ) - ) => - } - } - } - - test("doubleNested"){ - quxParser.constructOrThrow(Seq("-w", "-x", "1", "-y", "2", "-z", "xxx", "--moo", "cow")) ==> - Qux("cow", Bar(Flag(true), Foo(1, 2), "xxx")) - } - test("success"){ - ParserForMethods(Main).runOrThrow(Seq("-x", "1", "-y", "2", "-z", "hello")) ==> "false 1 2 hello false" - } - } -} diff --git a/mainargs/test/src-2/OldVarargsTests.scala b/mainargs/test/src-2/OldVarargsTests.scala index 1534ec2..6e7bdef 100644 --- a/mainargs/test/src-2/OldVarargsTests.scala +++ b/mainargs/test/src-2/OldVarargsTests.scala @@ -1,14 +1,15 @@ package mainargs import utest._ -object OldVarargsTests extends VarargsTests{ - object Base{ +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 + def mixedVariadic(@arg(short = 'f') first: Int, args: String*) = + first + args.mkString } val check = new Checker(ParserForMethods(Base), allowPositional = true) diff --git a/mainargs/test/src-2/ParserTests.scala b/mainargs/test/src-2/ParserTests.scala deleted file mode 100644 index ebf26ce..0000000 --- a/mainargs/test/src-2/ParserTests.scala +++ /dev/null @@ -1,53 +0,0 @@ -package mainargs -import utest._ - - -object ParserTests extends TestSuite{ - - object SingleBase{ - @main(doc = "Qux is a function that does stuff") - def run(i: Int, - @arg(doc = "Pass in a custom `s` to override it") - s: String = "lols") = s * i - } - - object MultiBase{ - @main - def foo() = 1 - - @main - def bar(i: Int) = i - } - - @main - case class ClassBase(code: Option[String] = None, other: String = "hello") - - val multiMethodParser = ParserForMethods(MultiBase) - val singleMethodParser = ParserForMethods(SingleBase) - val classParser = ParserForClass[ClassBase] - val tests = Tests { - test("runEitherMulti") { - - test { - multiMethodParser.runEither(Array("foo")) ==> Right(1) - } - test { - multiMethodParser.runEither(Array("bar", "-i", "123")) ==> Right(123) - } - test { - assert( - multiMethodParser.runEither(Array("f")) - .left - .exists(_.contains("Unable to find subcommand: f")) - ) - } - } - test("runEitherSingle"){ - singleMethodParser.runEither(Array("5", "x"), allowPositional = true) ==> Right("xxxxx") - } - test("constructEither"){ - classParser.constructEither(Array("--code", "println(1)")) ==> - Right(ClassBase(code = Some("println(1)"), other = "hello")) - } - } -} diff --git a/mainargs/test/src-2/VersionSpecific.scala b/mainargs/test/src-2/VersionSpecific.scala new file mode 100644 index 0000000..807759b --- /dev/null +++ b/mainargs/test/src-2/VersionSpecific.scala @@ -0,0 +1,5 @@ +package mainargs + +object VersionSpecific { + val isScala3 = false +} diff --git a/mainargs/test/src-3/VersionSpecific.scala b/mainargs/test/src-3/VersionSpecific.scala new file mode 100644 index 0000000..aed72d3 --- /dev/null +++ b/mainargs/test/src-3/VersionSpecific.scala @@ -0,0 +1,5 @@ +package mainargs + +object VersionSpecific { + val isScala3 = true +} diff --git a/mainargs/test/src-jvm-2/AmmoniteTests.scala b/mainargs/test/src-jvm-2/AmmoniteTests.scala index f191e9f..aaa9705 100644 --- a/mainargs/test/src-jvm-2/AmmoniteTests.scala +++ b/mainargs/test/src-jvm-2/AmmoniteTests.scala @@ -69,7 +69,7 @@ object AmmoniteConfig{ @arg(doc = "Print this message") help: Flag ) - implicit val coreParser: ParserForClass[Core] = ParserForClass[Core] + implicit val coreParser = ParserForClass[Core] @main case class Predef( @@ -86,7 +86,7 @@ object AmmoniteConfig{ "choose an additional predef to use using `--predef") noHomePredef: Flag ) - implicit val predefParser: ParserForClass[Predef] = ParserForClass[Predef] + implicit val predefParser = ParserForClass[Predef] @main case class Repl( @@ -105,12 +105,12 @@ object AmmoniteConfig{ "friendliness.") classBased: Flag ) - implicit val replParser: ParserForClass[Repl] = ParserForClass[Repl] + implicit val replParser = ParserForClass[Repl] } object AmmoniteTests extends TestSuite{ - val parser: ParserForClass[AmmoniteConfig] = ParserForClass[AmmoniteConfig] + val parser = ParserForClass[AmmoniteConfig] val tests = Tests { diff --git a/mainargs/test/src-jvm-2/MillTests.scala b/mainargs/test/src-jvm-2/MillTests.scala index 03d6810..ffc78a1 100644 --- a/mainargs/test/src-jvm-2/MillTests.scala +++ b/mainargs/test/src-jvm-2/MillTests.scala @@ -1,7 +1,6 @@ // package mainargs // import utest._ - // object MillTests extends TestSuite{ // implicit object PathRead extends TokensReader[os.Path]("path", strs => Right(os.Path(strs.head, os.pwd))) @@ -107,4 +106,3 @@ // } // } // } - diff --git a/mainargs/test/src/ClassTests.scala b/mainargs/test/src/ClassTests.scala new file mode 100644 index 0000000..f12fa1e --- /dev/null +++ b/mainargs/test/src/ClassTests.scala @@ -0,0 +1,173 @@ +package mainargs +import utest._ + +object ClassTests extends TestSuite { + + @main + case class Foo(x: Int, y: Int) + + @main + case class Bar(w: Flag = Flag(), f: Foo, @arg(short = 'z') zzzz: String) + + @main + case class Qux(moo: String, b: Bar) + + implicit val fooParser: ParserForClass[Foo] = ParserForClass[Foo] + implicit val barParser: ParserForClass[Bar] = ParserForClass[Bar] + implicit val quxParser: ParserForClass[Qux] = ParserForClass[Qux] + + object Main { + @main + def run(bar: Bar, bool: Boolean = false) = { + s"${bar.w.value} ${bar.f.x} ${bar.f.y} ${bar.zzzz} $bool" + } + } + + val tests = Tests { + test("simple") { + test("success") { + fooParser.constructOrThrow(Seq("-x", "1", "-y", "2")) ==> Foo(1, 2) + } + test("missing") { + fooParser.constructRaw(Seq("-x", "1")) ==> + Result.Failure.MismatchedArguments( + Seq( + ArgSig.Simple( + None, + Some('y'), + None, + None, + mainargs.TokensReader.IntRead, + false + ) + ), + List(), + List(), + None + ) + + } + } + + test("nested") { + test("success") { + barParser.constructOrThrow( + Seq("-w", "-x", "1", "-y", "2", "--zzzz", "xxx") + ) ==> + Bar(Flag(true), Foo(1, 2), "xxx") + } + test("missingInner") { + // Blocked by https://github.com/lampepfl/dotty/issues/12492 + TestUtils.scala2Only { + barParser.constructRaw(Seq("-w", "-x", "1", "-z", "xxx")) ==> + Result.Failure.MismatchedArguments( + Seq( + ArgSig.Simple( + None, + Some('y'), + None, + None, + mainargs.TokensReader.IntRead, + false + ) + ), + List(), + List(), + None + ) + } + } + test("missingOuter") { + // Blocked by https://github.com/lampepfl/dotty/issues/12492 + TestUtils.scala2Only { + barParser.constructRaw(Seq("-w", "-x", "1", "-y", "2")) ==> + Result.Failure.MismatchedArguments( + Seq( + ArgSig.Simple( + Some("zzzz"), + Some('z'), + None, + None, + mainargs.TokensReader.StringRead, + false + ) + ), + List(), + List(), + None + ) + } + } + + test("missingInnerOuter") { + // Blocked by https://github.com/lampepfl/dotty/issues/12492 + TestUtils.scala2Only { + barParser.constructRaw(Seq("-w", "-x", "1")) ==> + Result.Failure.MismatchedArguments( + Seq( + ArgSig.Simple( + None, + Some('y'), + None, + None, + mainargs.TokensReader.IntRead, + false + ), + ArgSig.Simple( + Some("zzzz"), + Some('z'), + None, + None, + mainargs.TokensReader.StringRead, + false + ) + ), + List(), + List(), + None + ) + } + } + test("failedInnerOuter") { + TestUtils.scala2Only { + assertMatch( + barParser.constructRaw( + Seq("-w", "-x", "xxx", "-y", "hohoho", "-z", "xxx") + ) + ) { + case Result.Failure.InvalidArguments( + Seq( + Result.ParamError.Failed( + ArgSig.Simple(None, Some('x'), None, None, _, false), + Seq("xxx"), + _ + ), + Result.ParamError.Failed( + ArgSig.Simple(None, Some('y'), None, None, _, false), + Seq("hohoho"), + _ + ) + ) + ) => + } + } + } + } + + test("doubleNested") { + TestUtils.scala2Only { + quxParser.constructOrThrow( + Seq("-w", "-x", "1", "-y", "2", "-z", "xxx", "--moo", "cow") + ) ==> + Qux("cow", Bar(Flag(true), Foo(1, 2), "xxx")) + } + } + test("success") { + TestUtils.scala2Only { + ParserForMethods(Main).runOrThrow( + Seq("-x", "1", "-y", "2", "-z", "hello") + ) ==> "false 1 2 hello false" + } + } + } +} diff --git a/mainargs/test/src-2/ManyTests.scala b/mainargs/test/src/ManyTests.scala similarity index 100% rename from mainargs/test/src-2/ManyTests.scala rename to mainargs/test/src/ManyTests.scala diff --git a/mainargs/test/src/NewVarargsTests.scala b/mainargs/test/src/NewVarargsTests.scala index 21c44bb..c55065b 100644 --- a/mainargs/test/src/NewVarargsTests.scala +++ b/mainargs/test/src/NewVarargsTests.scala @@ -1,7 +1,7 @@ package mainargs import utest._ -object NewVarargsTests extends VarargsTests{ - object Base{ +object NewVarargsTests extends VarargsTests { + object Base { @main def pureVariadic(nums: Leftover[Int]) = nums.value.sum @@ -10,8 +10,10 @@ object NewVarargsTests extends VarargsTests{ first + args.value.mkString } @main - def mixedVariadicWithDefault(@arg(short = 'f') first: Int = 1337, - args: Leftover[String]) = { + def mixedVariadicWithDefault( + @arg(short = 'f') first: Int = 1337, + args: Leftover[String] + ) = { first + args.value.mkString } } diff --git a/mainargs/test/src/ParserTests.scala b/mainargs/test/src/ParserTests.scala new file mode 100644 index 0000000..ad7e4c1 --- /dev/null +++ b/mainargs/test/src/ParserTests.scala @@ -0,0 +1,61 @@ +package mainargs +import utest._ + +object ParserTests extends TestSuite { + + object SingleBase { + @main(doc = "Qux is a function that does stuff") + def run( + i: Int, + @arg(doc = "Pass in a custom `s` to override it") + s: String = "lols" + ) = s * i + } + + object MultiBase { + @main + def foo() = 1 + + @main + def bar(i: Int) = i + } + + @main + case class ClassBase(code: Option[String] = None, other: String = "hello") + + val multiMethodParser = ParserForMethods(MultiBase) + val singleMethodParser = ParserForMethods(SingleBase) + val classParser = ParserForClass[ClassBase] + val tests = Tests { + test("runEitherMulti") { + + test { + multiMethodParser.runEither(Array("foo")) ==> Right(1) + } + test { + multiMethodParser.runEither(Array("bar", "-i", "123")) ==> Right(123) + } + test { + assert( + multiMethodParser + .runEither(Array("f")) + .left + .exists(_.contains("Unable to find subcommand: f")) + ) + } + } + test("runEitherSingle") { + singleMethodParser.runEither( + Array("5", "x"), + allowPositional = true + ) ==> Right("xxxxx") + } + test("constructEither") { + TestUtils.scala2Only { + // default values in classes not working on Scala 3 + classParser.constructEither(Array("--code", "println(1)")) ==> + Right(ClassBase(code = Some("println(1)"), other = "hello")) + } + } + } +} diff --git a/mainargs/test/src/TestUtils.scala b/mainargs/test/src/TestUtils.scala new file mode 100644 index 0000000..d697bad --- /dev/null +++ b/mainargs/test/src/TestUtils.scala @@ -0,0 +1,7 @@ +package mainargs + +object TestUtils { + def scala2Only(f: => Unit): Unit = { + if (VersionSpecific.isScala3) {} else f + } +} diff --git a/mainargs/test/src/VarargsTests.scala b/mainargs/test/src/VarargsTests.scala index cfeaa0b..cea2a97 100644 --- a/mainargs/test/src/VarargsTests.scala +++ b/mainargs/test/src/VarargsTests.scala @@ -1,61 +1,69 @@ package mainargs import utest._ -trait VarargsTests extends TestSuite{ +trait VarargsTests extends TestSuite { def check: Checker[_] def isNewVarargsTests: Boolean val tests = Tests { - test("happyPathPasses"){ + test("happyPathPasses") { test - check( - List("pureVariadic", "1", "2", "3"), Result.Success(6) + List("pureVariadic", "1", "2", "3"), + Result.Success(6) ) test - check( List("mixedVariadic", "1", "2", "3", "4", "5"), Result.Success("12345") ) test - { - if (isNewVarargsTests) check( - List("mixedVariadicWithDefault"), - Result.Success("1337") - ) + if (isNewVarargsTests) + check( + List("mixedVariadicWithDefault"), + Result.Success("1337") + ) } } - test("emptyVarargsPasses"){ + test("emptyVarargsPasses") { test - check(List("pureVariadic"), Result.Success(0)) test - check( - List("mixedVariadic", "-f", "1"), Result.Success("1") + List("mixedVariadic", "-f", "1"), + Result.Success("1") ) test - check( - List("mixedVariadic", "1"), Result.Success("1") + List("mixedVariadic", "1"), + Result.Success("1") ) } - test("varargsAreAlwaysPositional"){ + test("varargsAreAlwaysPositional") { val invoked = check.parseInvoke( List("pureVariadic", "--nums", "31337") ) - test - assertMatch(invoked){ - case Result.Failure.InvalidArguments(List( - Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), - Seq("--nums"), - """java.lang.NumberFormatException: For input string: "--nums"""" | - """java.lang.NumberFormatException: --nums""" - ) - ))=> + test - assertMatch(invoked) { + case Result.Failure.InvalidArguments( + List( + Result.ParamError.Failed( + ArgSig.Leftover("nums", _, _), + Seq("--nums"), + """java.lang.NumberFormatException: For input string: "--nums"""" | + """java.lang.NumberFormatException: --nums""" + ) + ) + ) => } test - assertMatch( check.parseInvoke(List("pureVariadic", "1", "2", "3", "--nums", "4")) - ){ - case Result.Failure.InvalidArguments(List( - Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), - Seq("--nums"), - "java.lang.NumberFormatException: For input string: \"--nums\"" | - "java.lang.NumberFormatException: --nums" - ) - ))=> + ) { + case Result.Failure.InvalidArguments( + List( + Result.ParamError.Failed( + ArgSig.Leftover("nums", _, _), + Seq("--nums"), + "java.lang.NumberFormatException: For input string: \"--nums\"" | + "java.lang.NumberFormatException: --nums" + ) + ) + ) => } test - check( List("mixedVariadic", "1", "--args", "foo"), @@ -64,47 +72,51 @@ trait VarargsTests extends TestSuite{ } - test("notEnoughNormalArgsStillFails"){ - assertMatch(check.parseInvoke(List("mixedVariadic"))){ + test("notEnoughNormalArgsStillFails") { + assertMatch(check.parseInvoke(List("mixedVariadic"))) { case Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(Some("first"), _, _, _, _, _)), - Nil, - Nil, - None - ) => + Seq(ArgSig.Simple(Some("first"), _, _, _, _, _)), + Nil, + Nil, + None + ) => } } - test("multipleVarargParseFailures"){ + test("multipleVarargParseFailures") { test - assertMatch( check.parseInvoke(List("pureVariadic", "aa", "bb", "3")) - ){ - case Result.Failure.InvalidArguments(List( - Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), - Seq("aa"), - "java.lang.NumberFormatException: For input string: \"aa\"" | - "java.lang.NumberFormatException: aa" - ), - Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), - Seq("bb"), - "java.lang.NumberFormatException: For input string: \"bb\"" | - "java.lang.NumberFormatException: bb" - ) - ))=> + ) { + case Result.Failure.InvalidArguments( + List( + Result.ParamError.Failed( + ArgSig.Leftover("nums", _, _), + Seq("aa"), + "java.lang.NumberFormatException: For input string: \"aa\"" | + "java.lang.NumberFormatException: aa" + ), + Result.ParamError.Failed( + ArgSig.Leftover("nums", _, _), + Seq("bb"), + "java.lang.NumberFormatException: For input string: \"bb\"" | + "java.lang.NumberFormatException: bb" + ) + ) + ) => } test - assertMatch( check.parseInvoke(List("mixedVariadic", "aa", "bb", "3")) - ){ - case Result.Failure.InvalidArguments(List( - Result.ParamError.Failed( - ArgSig.Simple(Some("first"), _, _, _, _, _), - Seq("aa"), - "java.lang.NumberFormatException: For input string: \"aa\"" | - "java.lang.NumberFormatException: aa" - ) - ))=> + ) { + case Result.Failure.InvalidArguments( + List( + Result.ParamError.Failed( + ArgSig.Simple(Some("first"), _, _, _, _, _), + Seq("aa"), + "java.lang.NumberFormatException: For input string: \"aa\"" | + "java.lang.NumberFormatException: aa" + ) + ) + ) => } } } From 6ef2c5f7a304c06d371364df52cb3885b53cac5a Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 1 Mar 2022 09:30:00 +0100 Subject: [PATCH 05/12] Update Mill to support Scala Native on Scala 3 Scala Native is not supported yet since it's blocked by PPrint --- build.sc | 41 +++++++++++++++++++++-------------------- mill | 26 +++++++++++++++++++------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/build.sc b/build.sc index f9380ff..d55f70b 100644 --- a/build.sc +++ b/build.sc @@ -1,26 +1,26 @@ import mill._, scalalib._, scalajslib._, scalanativelib._, publish._ import mill.scalalib.api.Util.isScala3 import scalalib._ -import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.1` +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.4` import de.tobiasroeser.mill.vcs.version.VcsVersion -import $ivy.`com.github.lolgab::mill-mima::0.0.4` +import $ivy.`com.github.lolgab::mill-mima::0.0.9` import com.github.lolgab.mill.mima._ val scala212 = "2.12.13" val scala213 = "2.13.4" -val scala3 = "3.0.2" +val scala30 = "3.0.2" +val scala31 = "3.1.1" -val scalaVersions = Seq(scala212, scala213, scala3) -val scala2Versions = scalaVersions.filter(_.startsWith("2.")) +val scala2Versions = List(scala212, scala213) val scalaJSVersions = for { - scalaV <- scalaVersions + scalaV <- scala30 :: scala2Versions scalaJSV <- Seq("1.5.1") } yield (scalaV, scalaJSV) val scalaNativeVersions = for { scalaV <- scala2Versions - scalaNativeV <- Seq("0.4.0") + scalaNativeV <- Seq("0.4.3") } yield (scalaV, scalaNativeV) trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mima { @@ -60,30 +60,31 @@ trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mim ) ++ Agg(ivy"com.lihaoyi::pprint:0.6.6") } +def scalaMajor(scalaVersion: String) = if(isScala3(scalaVersion)) "3" else "2" + trait Common extends CrossScalaModule { def millSourcePath = build.millSourcePath / "mainargs" def sources = T.sources( - super.sources() ++ Seq(PathRef(millSourcePath / s"src-$platform")) + millSourcePath / "src", + millSourcePath / s"src-$platform", + millSourcePath / s"src-${scalaMajor(scalaVersion())}", ) def platform: String } trait CommonTestModule extends ScalaModule with TestModule.Utest { - def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.10") - def sources = T.sources { - val scalaMajor = if(isScala3(scalaVersion())) "3" else "2" - super.sources() ++ Seq( - millSourcePath / "src", - millSourcePath / s"src-$platform", - millSourcePath / s"src-$platform-$scalaMajor" - ).map(PathRef(_)) - } + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.11") + def sources = T.sources( + millSourcePath / "src", + millSourcePath / s"src-$platform", + millSourcePath / s"src-${scalaMajor(scalaVersion())}", + ) def platform: String } object mainargs extends Module { - object jvm extends Cross[JvmMainArgsModule](scalaVersions: _*) + object jvm extends Cross[JvmMainArgsModule](scala30 :: scala2Versions: _*) class JvmMainArgsModule(val crossScalaVersion: String) extends Common with ScalaModule with MainArgsPublishModule { def platform = "jvm" @@ -95,7 +96,7 @@ object mainargs extends Module { object js extends Cross[JSMainArgsModule](scalaJSVersions: _*) class JSMainArgsModule(val crossScalaVersion: String, crossJSVersion: String) - extends Common with ScalaJSModule with MainArgsPublishModule { + extends Common with MainArgsPublishModule with ScalaJSModule { def platform = "js" def scalaJSVersion = crossJSVersion object test extends Tests with CommonTestModule{ @@ -105,7 +106,7 @@ object mainargs extends Module { object native extends Cross[NativeMainArgsModule](scalaNativeVersions: _*) class NativeMainArgsModule(val crossScalaVersion: String, crossScalaNativeVersion: String) - extends Common with ScalaNativeModule with MainArgsPublishModule { + extends Common with MainArgsPublishModule with ScalaNativeModule { def scalaNativeVersion = crossScalaNativeVersion def platform = "native" object test extends Tests with CommonTestModule{ diff --git a/mill b/mill index e0dbdff..89580b2 100755 --- a/mill +++ b/mill @@ -3,27 +3,39 @@ # This is a wrapper script, that automatically download mill from GitHub release pages # You can give the required mill version with MILL_VERSION env variable # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -DEFAULT_MILL_VERSION=0.9.10 +DEFAULT_MILL_VERSION=0.10.0-38-677a10 set -e if [ -z "$MILL_VERSION" ] ; then if [ -f ".mill-version" ] ; then MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" - elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then + elif [ -f "mill" ] && [ "$0" != "mill" ] ; then MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) else MILL_VERSION=$DEFAULT_MILL_VERSION fi fi -MILL_DOWNLOAD_PATH="$HOME/.mill/download" -MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/$MILL_VERSION" +if [ "x${XDG_CACHE_HOME}" != "x" ] ; then + MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" +else + MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +fi +MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" + +version_remainder="$MILL_VERSION" +MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" +MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" -if [ ! -x "$MILL_EXEC_PATH" ] ; then - mkdir -p $MILL_DOWNLOAD_PATH +if [ ! -s "$MILL_EXEC_PATH" ] ; then + mkdir -p "$MILL_DOWNLOAD_PATH" + if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then + ASSEMBLY="-assembly" + fi DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download - MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION-assembly" + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION_TAG}/$MILL_VERSION${ASSEMBLY}" curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" chmod +x "$DOWNLOAD_FILE" mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" From 78179fbec4b9bf9e81be33345254d199cd5ab670 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Wed, 2 Mar 2022 17:32:54 +0100 Subject: [PATCH 06/12] Apply code review suggestions --- mainargs/src-3/Macros.scala | 15 ++++++--------- ...ParserForMethodsCompanionVersionSpecific.scala | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 4c0e790..4d994bf 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -32,11 +32,10 @@ object Macros { val companionModuleType = typeSymbolOfB.companionModule.tree.asInstanceOf[ValDef].tpt.tpe.asType val companionModuleExpr = Ident(companionModule).asExpr val mainAnnotationInstance = typeSymbolOfB.getAnnotation(mainAnnotation).getOrElse { - report.error( + report.throwError( s"cannot find @main annotation on ${companionModule.name}", typeSymbolOfB.pos.get ) - ??? } val annotatedMethod = TypeRepr.of[B].typeSymbol.companionModule.memberMethod("apply").head companionModuleType match @@ -51,12 +50,12 @@ object Macros { 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(throw new Exception("Multiple parameter lists not supported")) + 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(_.asExpr.asInstanceOf[Expr[mainargs.arg]]).getOrElse('{ new mainargs.arg() }) + val arg = param.getAnnotation(argAnnotation).map(_.asExprOf[mainargs.arg]).getOrElse('{ new mainargs.arg() }) val paramType = paramTpe.asType paramType match case '[t] => @@ -64,12 +63,11 @@ object Macros { case Some(v) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) } case None => '{ None } } - val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse{ - report.error( + val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse { + report.throwError( s"No mainargs.ArgReader of ###companionModule### found for parameter ${param.name}", param.pos.get ) - '{ ??? } } '{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader })).asInstanceOf[ArgSig[Any, B]] } }) @@ -110,8 +108,7 @@ object Macros { val paramss = method.paramSymss if (paramss.isEmpty) { - report.error("At least one parameter list must be declared.", method.pos.get) - return '{???} + report.throwError("At least one parameter list must be declared.", method.pos.get) } val fct = Ref(method) diff --git a/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala index ff9509f..c66f286 100644 --- a/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala +++ b/mainargs/src-3/ParserForMethodsCompanionVersionSpecific.scala @@ -1,5 +1,5 @@ package mainargs private [mainargs] trait ParserForMethodsCompanionVersionSpecific { - inline def apply[B](base: B) : ParserForMethods[B] = ${ Macros.parserForMethods[B]('base) } + inline def apply[B](base: B): ParserForMethods[B] = ${ Macros.parserForMethods[B]('base) } } From ca93ff3a9e63ab57405d98a5e06a7367aa4c5e63 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 18:27:55 +0100 Subject: [PATCH 07/12] Use Symbol.requiredClass to get annotations --- mainargs/src-3/Macros.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 4d994bf..3d0c7c2 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -3,8 +3,8 @@ package mainargs import scala.quoted._ object Macros { - private def mainAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.main].typeSymbol - private def argAnnotation(using Quotes) = quotes.reflect.TypeRepr.of[mainargs.arg].typeSymbol + 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 From 7ea95e380667b48dbd1ae30b08618e05b554ef7f Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 18:28:51 +0100 Subject: [PATCH 08/12] Use cleaner syntax for splice --- mainargs/src-3/Macros.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 3d0c7c2..78f7edb 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -17,7 +17,7 @@ object Macros { '{ new ParserForMethods[B]( - MethodMains[B](${ mainDatas }, () => ${ base }) + MethodMains[B]($mainDatas, () => $base) ) } } From 339b0aa164049adaf0283dae5f2efed8550ffbd8 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 18:33:00 +0100 Subject: [PATCH 09/12] Match type to avoid casting --- mainargs/src-3/Macros.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 78f7edb..f5d830f 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -60,7 +60,7 @@ object Macros { paramType match case '[t] => val defaultParam: Expr[Option[B => t]] = defaultParams.get(param) match { - case Some(v) => '{ Some(((_: B) => $v).asInstanceOf[B => t]) } + case Some('{ $v: `t`}) => '{ Some(((_: B) => $v)) } case None => '{ None } } val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse { From 87a51ede05c65e566258257045874a51ae53f8ec Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 18:55:27 +0100 Subject: [PATCH 10/12] Rephrase error message --- mainargs/src-3/Macros.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index f5d830f..dd61016 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -65,7 +65,7 @@ object Macros { } val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse { report.throwError( - s"No mainargs.ArgReader of ###companionModule### found for parameter ${param.name}", + s"No mainargs.ArgReader found for parameter ${param.name}", param.pos.get ) } From 073549a793c8705fdf35cd9f4096d361083b655c Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 18:57:12 +0100 Subject: [PATCH 11/12] Remove asInstanceOf --- mainargs/src-3/Macros.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index dd61016..c4d366e 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -40,10 +40,10 @@ object Macros { val annotatedMethod = TypeRepr.of[B].typeSymbol.companionModule.memberMethod("apply").head companionModuleType match case '[bCompanion] => - val mainData = createMainData[B, bCompanion](annotatedMethod, mainAnnotationInstance) + val mainData = createMainData[B, Any](annotatedMethod, mainAnnotationInstance) '{ new ParserForClass[B]( - ClassMains[B](${ mainData }.asInstanceOf[MainData[B, Any]], () => ${ Ident(companionModule).asExpr }) + ClassMains[B](${ mainData }, () => ${ Ident(companionModule).asExpr }) ) } } From 3bc960b5ff9dfdfb3126b202fb180772f34b1614 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 4 Mar 2022 19:00:11 +0100 Subject: [PATCH 12/12] Remove asInstanceOf --- mainargs/src-3/Macros.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index c4d366e..4145ca5 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -73,8 +73,8 @@ object Macros { }) val invokeRaw: Expr[(B, Seq[Any]) => T] = { - def callOf(args: Expr[Seq[Any]]) = call(method, '{ Seq( ${ args }) }) - '{ ((b: B, params: Seq[Any]) => ${ callOf('{ params }) }).asInstanceOf[(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 }) } }