From b916f9c1bfb05be8b2a5a8dca5dcd509c85a2ee5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Apr 2022 12:51:58 +0200 Subject: [PATCH 1/2] Add newMain prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on prototype in #13727 Co-authored-by: Timothée Loyck Andres --- .../dotty/tools/dotc/ast/MainProxies.scala | 22 +- .../dotty/tools/dotc/core/Definitions.scala | 1 - .../src/dotty/tools/dotc/typer/Checking.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- library/src/scala/main.scala | 338 +++++++++++++++++- tests/neg/main-annotation-by-name-param.scala | 17 + tests/neg/main-annotation-currying.scala | 6 + tests/neg/main-annotation-generic.scala | 6 + .../neg/main-annotation-implicit-given.scala | 11 + .../neg/main-annotation-mainannotation.scala | 2 +- .../neg/main-annotation-multiple-annot.scala | 6 + tests/neg/main-annotation-nonmethod.scala | 9 + tests/neg/main-annotation-nonstatic.scala | 2 + .../main-annotation-unknown-parser-1.scala | 10 + .../main-annotation-unknown-parser-2.scala | 26 ++ tests/run/main-annotation-birthday.check | 1 + tests/run/main-annotation-birthday.scala | 30 ++ .../run/main-annotation-default-value-1.check | 3 + .../run/main-annotation-default-value-1.scala | 20 ++ .../run/main-annotation-default-value-2.check | 2 + .../run/main-annotation-default-value-2.scala | 31 ++ tests/run/main-annotation-help-override.check | 56 +++ tests/run/main-annotation-help-override.scala | 47 +++ tests/run/main-annotation-help.check | 158 ++++++++ tests/run/main-annotation-help.scala | 173 +++++++++ .../main-annotation-homemade-annot-1.scala | 2 +- .../main-annotation-homemade-annot-3.scala | 1 + .../main-annotation-homemade-parser-1.check | 1 + .../main-annotation-homemade-parser-1.scala | 26 ++ .../main-annotation-homemade-parser-2.check | 1 + .../main-annotation-homemade-parser-2.scala | 29 ++ .../main-annotation-homemade-parser-3.check | 1 + .../main-annotation-homemade-parser-3.scala | 25 ++ .../main-annotation-homemade-parser-4.check | 7 + .../main-annotation-homemade-parser-4.scala | 46 +++ .../main-annotation-homemade-parser-5.check | 2 + .../main-annotation-homemade-parser-5.scala | 25 ++ tests/run/main-annotation-multiple.check | 2 + tests/run/main-annotation-multiple.scala | 23 ++ tests/run/main-annotation-named-params.check | 9 + tests/run/main-annotation-named-params.scala | 25 ++ ...n-annotation-no-parameters-no-parens.check | 1 + ...n-annotation-no-parameters-no-parens.scala | 18 + tests/run/main-annotation-no-parameters.check | 1 + tests/run/main-annotation-no-parameters.scala | 18 + tests/run/main-annotation-overload.check | 1 + tests/run/main-annotation-overload.scala | 30 ++ tests/run/main-annotation-param-annot-1.check | 36 ++ tests/run/main-annotation-param-annot-1.scala | 105 ++++++ tests/run/main-annotation-param-annot-2.check | 9 + tests/run/main-annotation-param-annot-2.scala | 57 +++ ...nnotation-param-annot-invalid-params.check | 3 + ...nnotation-param-annot-invalid-params.scala | 42 +++ tests/run/main-annotation-return-type-1.check | 4 + tests/run/main-annotation-return-type-1.scala | 21 ++ tests/run/main-annotation-return-type-2.check | 4 + tests/run/main-annotation-return-type-2.scala | 24 ++ tests/run/main-annotation-short-name.check | 7 + tests/run/main-annotation-short-name.scala | 20 ++ tests/run/main-annotation-simple.check | 1 + tests/run/main-annotation-simple.scala | 17 + tests/run/main-annotation-top-level.check | 5 + tests/run/main-annotation-top-level.scala | 32 ++ tests/run/main-annotation-types.check | 18 + tests/run/main-annotation-types.scala | 30 ++ tests/run/main-annotation-vararg-1.check | 5 + tests/run/main-annotation-vararg-1.scala | 25 ++ tests/run/main-annotation-vararg-2.check | 10 + tests/run/main-annotation-vararg-2.scala | 28 ++ tests/run/main-annotation-wrong-param-1.check | 13 + tests/run/main-annotation-wrong-param-1.scala | 21 ++ .../main-annotation-wrong-param-names.check | 15 + .../main-annotation-wrong-param-names.scala | 21 ++ .../main-annotation-wrong-param-number.check | 16 + .../main-annotation-wrong-param-number.scala | 21 ++ .../main-annotation-wrong-param-type.check | 14 + .../main-annotation-wrong-param-type.scala | 23 ++ 77 files changed, 1900 insertions(+), 23 deletions(-) create mode 100644 tests/neg/main-annotation-by-name-param.scala create mode 100644 tests/neg/main-annotation-currying.scala create mode 100644 tests/neg/main-annotation-generic.scala create mode 100644 tests/neg/main-annotation-implicit-given.scala create mode 100644 tests/neg/main-annotation-multiple-annot.scala create mode 100644 tests/neg/main-annotation-nonmethod.scala create mode 100644 tests/neg/main-annotation-nonstatic.scala create mode 100644 tests/neg/main-annotation-unknown-parser-1.scala create mode 100644 tests/neg/main-annotation-unknown-parser-2.scala create mode 100644 tests/run/main-annotation-birthday.check create mode 100644 tests/run/main-annotation-birthday.scala create mode 100644 tests/run/main-annotation-default-value-1.check create mode 100644 tests/run/main-annotation-default-value-1.scala create mode 100644 tests/run/main-annotation-default-value-2.check create mode 100644 tests/run/main-annotation-default-value-2.scala create mode 100644 tests/run/main-annotation-help-override.check create mode 100644 tests/run/main-annotation-help-override.scala create mode 100644 tests/run/main-annotation-help.check create mode 100644 tests/run/main-annotation-help.scala create mode 100644 tests/run/main-annotation-homemade-parser-1.check create mode 100644 tests/run/main-annotation-homemade-parser-1.scala create mode 100644 tests/run/main-annotation-homemade-parser-2.check create mode 100644 tests/run/main-annotation-homemade-parser-2.scala create mode 100644 tests/run/main-annotation-homemade-parser-3.check create mode 100644 tests/run/main-annotation-homemade-parser-3.scala create mode 100644 tests/run/main-annotation-homemade-parser-4.check create mode 100644 tests/run/main-annotation-homemade-parser-4.scala create mode 100644 tests/run/main-annotation-homemade-parser-5.check create mode 100644 tests/run/main-annotation-homemade-parser-5.scala create mode 100644 tests/run/main-annotation-multiple.check create mode 100644 tests/run/main-annotation-multiple.scala create mode 100644 tests/run/main-annotation-named-params.check create mode 100644 tests/run/main-annotation-named-params.scala create mode 100644 tests/run/main-annotation-no-parameters-no-parens.check create mode 100644 tests/run/main-annotation-no-parameters-no-parens.scala create mode 100644 tests/run/main-annotation-no-parameters.check create mode 100644 tests/run/main-annotation-no-parameters.scala create mode 100644 tests/run/main-annotation-overload.check create mode 100644 tests/run/main-annotation-overload.scala create mode 100644 tests/run/main-annotation-param-annot-1.check create mode 100644 tests/run/main-annotation-param-annot-1.scala create mode 100644 tests/run/main-annotation-param-annot-2.check create mode 100644 tests/run/main-annotation-param-annot-2.scala create mode 100644 tests/run/main-annotation-param-annot-invalid-params.check create mode 100644 tests/run/main-annotation-param-annot-invalid-params.scala create mode 100644 tests/run/main-annotation-return-type-1.check create mode 100644 tests/run/main-annotation-return-type-1.scala create mode 100644 tests/run/main-annotation-return-type-2.check create mode 100644 tests/run/main-annotation-return-type-2.scala create mode 100644 tests/run/main-annotation-short-name.check create mode 100644 tests/run/main-annotation-short-name.scala create mode 100644 tests/run/main-annotation-simple.check create mode 100644 tests/run/main-annotation-simple.scala create mode 100644 tests/run/main-annotation-top-level.check create mode 100644 tests/run/main-annotation-top-level.scala create mode 100644 tests/run/main-annotation-types.check create mode 100644 tests/run/main-annotation-types.scala create mode 100644 tests/run/main-annotation-vararg-1.check create mode 100644 tests/run/main-annotation-vararg-1.scala create mode 100644 tests/run/main-annotation-vararg-2.check create mode 100644 tests/run/main-annotation-vararg-2.scala create mode 100644 tests/run/main-annotation-wrong-param-1.check create mode 100644 tests/run/main-annotation-wrong-param-1.scala create mode 100644 tests/run/main-annotation-wrong-param-names.check create mode 100644 tests/run/main-annotation-wrong-param-names.scala create mode 100644 tests/run/main-annotation-wrong-param-number.check create mode 100644 tests/run/main-annotation-wrong-param-number.scala create mode 100644 tests/run/main-annotation-wrong-param-type.check create mode 100644 tests/run/main-annotation-wrong-param-type.scala diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 040582476e96..d0e2fcf021d3 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -11,12 +11,6 @@ import NameKinds.DefaultGetterName import Annotations.Annotation object MainProxies { - - /** Generate proxy classes for @main functions and @myMain functions where myMain <:< MainAnnotation */ - def proxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { - mainAnnotationProxies(stats) ++ mainProxies(stats) - } - /** Generate proxy classes for @main functions. * A function like * @@ -35,7 +29,7 @@ object MainProxies { * catch case err: ParseError => showError(err) * } */ - private def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + def mainProxiesOld(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => @@ -45,11 +39,11 @@ object MainProxies { case _ => Nil } - mainMethods(stats).flatMap(mainProxy) + mainMethods(stats).flatMap(mainProxyOld) } import untpd._ - private def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { + def mainProxyOld(mainFun: Symbol)(using Context): List[TypeDef] = { val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span def pos = mainFun.sourcePos val argsRef = Ident(nme.args) @@ -171,7 +165,7 @@ object MainProxies { * } * } */ - private def mainAnnotationProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ /** @@ -194,12 +188,12 @@ object MainProxies { def mainMethods(scope: Tree, stats: List[Tree]): List[(Symbol, ParameterAnnotationss, DefaultValueSymbols, Option[Comment])] = stats.flatMap { case stat: DefDef => val sym = stat.symbol - sym.annotations.filter(_.matches(defn.MainAnnotationClass)) match { + sym.annotations.filter(_.matches(defn.MainAnnot)) match { case Nil => Nil case _ :: Nil => val paramAnnotations = stat.paramss.flatMap(_.map( - valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotationParameterAnnotation)) + valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotParameterAnnotation)) )) (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil case mainAnnot :: others => @@ -213,7 +207,7 @@ object MainProxies { } // Assuming that the top-level object was already generated, all main methods will have a scope - mainMethods(EmptyTree, stats).flatMap(mainAnnotationProxy) + mainMethods(EmptyTree, stats).flatMap(mainProxy) } private def mainAnnotationProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): Option[TypeDef] = { @@ -367,7 +361,7 @@ object MainProxies { case tree => super.transform(tree) } val annots = mainFun.annotations - .filterNot(_.matches(defn.MainAnnotationClass)) + .filterNot(_.matches(defn.MainAnnot)) .map(annot => insertTypeSplices.transform(annot.tree)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) .withFlags(JavaStatic) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f80b7970b324..d51136c398b0 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -954,7 +954,6 @@ class Definitions { @tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam") @tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam") @tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween") - @tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main") @tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration") @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index acbd4f65acc7..03562adfe0ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1382,10 +1382,9 @@ trait Checking { /** check that annotation `annot` is applicable to symbol `sym` */ def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = !ctx.reporter.reportsErrorsFor { - val annotCls = Annotations.annotClass(annot) val concreteAnnot = Annotations.ConcreteAnnotation(annot) val pos = annot.srcPos - if (annotCls == defn.MainAnnot || concreteAnnot.matches(defn.MainAnnotationClass)) { + if (concreteAnnot.matches(defn.MainAnnot)) { if (!sym.isRealMethod) report.error(em"main annotation cannot be applied to $sym", pos) if (!sym.owner.is(Module) || !sym.owner.isStatic) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 34eb2b7df41f..38c9d2bf77e0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2626,7 +2626,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted() var stats1 = typedStats(tree.stats, pkg.moduleClass)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1 + stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))._1 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) } case _ => diff --git a/library/src/scala/main.scala b/library/src/scala/main.scala index 057fdad4c2fb..0556a4ddec0f 100644 --- a/library/src/scala/main.scala +++ b/library/src/scala/main.scala @@ -8,6 +8,338 @@ package scala -/** An annotation that designates a main function - */ -class main extends scala.annotation.Annotation {} +import collection.mutable +import annotation._ + +/** + * The annotation that designates a main function. + * Main functions are entry points for Scala programs. They can be called through a command line interface by using + * the `scala` command, followed by their name and, optionally, their parameters. + * + * The parameters of a main function may have any type `T`, as long as there exists a + * `given util.CommandLineParser.FromString[T]` in the scope. It will be used for parsing the string given as input + * into the correct argument type. + * These types already have parsers defined: + * - String, + * - Boolean, + * - Byte, Short, Int, Long, Float, Double. + * + * The parameters of a main function may be passed either by position, or by name. Passing an argument positionaly + * means that you give the arguments in the same order as the function's signature. Passing an argument by name means + * that you give the argument right after giving its name. Considering the function + * `@main def foo(i: Int, str: String)`, we may have arguments passed: + * - by position: `scala foo 1 abc`, + * - by name: `scala foo -i 1 --str abc` or `scala foo --str abc -i 1`. + * + * A mixture of both is also possible: `scala foo --str abc 1` is equivalent to all previous examples. + * + * Note that main function overloading is not currently supported, i.e. you cannot define two main methods that have + * the same name in the same project. + * + * Special arguments are used to display help regarding a main function: `--help` and `-h`. If used as argument, the program + * will display some useful information about the main function. This help directly uses the ScalaDoc comment + * associated with the function, more precisely its description and the description of the parameters documented with + * `@param`. Note that if a parameter is named `help` or `h`, or if one of the parameters has as alias one of those names, + * the help displaying will be disabled for that argument. + * For example, for `@main def foo(help: Boolean)`, `scala foo -h` will display the help, but `scala foo --help` will fail, + * as it will expect a Boolean value after `--help`. + * + * Parameters may be given annotations to add functionalities to the main function: + * - `main.Alias` adds other names to a parameter. For example, if a parameter `node` has as aliases + * `otherNode` and `n`, it may be addressed using `--node`, `--otherNode` or `-n`. + * + * Here is an example of a main function with annotated parameters: + * `@main def foo(@main.Alias("x") number: Int, @main.Alias("explanation") s: String)`. The following commands are + * equivalent: + * - `scala foo --number 1 -s abc` + * - `scala foo -x 1 -s abc` + * - `scala foo --number 1 --explanation abc` + * - `scala foo -x 1 --explanation abc` + */ +final class main extends MainAnnotation: + import main._ + import MainAnnotation._ + + override type ArgumentParser[T] = util.CommandLineParser.FromString[T] + override type MainResultType = Any + + override def command(args: Array[String], commandName: String, documentation: String, parameterInfoss: ParameterInfos*) = + new Command[ArgumentParser, MainResultType]: + private enum ArgumentKind { + case SimpleArgument, OptionalArgument, VarArgument + } + + private val argMarker = "--" + private val shortArgMarker = "-" + + /** + * The name of the special argument to display the method's help. + * If one of the method's parameters is called the same, will be ignored. + */ + private val helpArg = "help" + private var helpIsOverridden = false + + /** + * The short name of the special argument to display the method's help. + * If one of the method's parameters uses the same short name, will be ignored. + */ + private val shortHelpArg = 'h' + private var shortHelpIsOverridden = false + + private val maxUsageLineLength = 120 + + /** A map from argument canonical name (the name of the parameter in the method definition) to parameter informations */ + private val nameToParameterInfos: Map[String, ParameterInfos] = parameterInfoss.map(infos => infos.name -> infos).toMap + + private val (positionalArgs, byNameArgs, invalidByNameArgs) = { + val namesToCanonicalName: Map[String, String] = parameterInfoss.flatMap( + infos => + var names = getAlternativeNames(infos) + val canonicalName = infos.name + if nameIsValid(canonicalName) then names = canonicalName +: names + names.map(_ -> canonicalName) + ).toMap + val shortNamesToCanonicalName: Map[Char, String] = parameterInfoss.flatMap( + infos => + var names = getShortNames(infos) + val canonicalName = infos.name + if shortNameIsValid(canonicalName) then names = canonicalName(0) +: names + names.map(_ -> canonicalName) + ).toMap + + helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) + shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) + + def getCanonicalArgName(arg: String): Option[String] = + if arg.startsWith(argMarker) && arg.length > argMarker.length then + namesToCanonicalName.get(arg.drop(argMarker.length)) + else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then + shortNamesToCanonicalName.get(arg(shortArgMarker.length)) + else + None + + def isArgName(arg: String): Boolean = + val isFullName = arg.startsWith(argMarker) + val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length)) + isFullName || isShortName + + def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = + remainingArgs match { + case Seq() => + (pa, bna, ia) + case argName +: argValue +: rest if isArgName(argName) => + getCanonicalArgName(argName) match { + case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) + case None => recurse(rest, pa, bna, ia :+ argName) + } + case arg +: rest => + recurse(rest, pa :+ arg, bna, ia) + } + + val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) + val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) + (pa, nameToArgValues, ia) + } + + /** The kind of the arguments. Used to display help about the main method. */ + private val argKinds = new mutable.ArrayBuffer[ArgumentKind] + + /** A buffer for all errors */ + private val errors = new mutable.ArrayBuffer[String] + + /** Issue an error, and return an uncallable getter */ + private def error(msg: String): () => Nothing = + errors += msg + () => throw new AssertionError("trying to get invalid argument") + + private inline def nameIsValid(name: String): Boolean = + name.length > 1 // TODO add more checks for illegal characters + + private inline def shortNameIsValid(name: String): Boolean = + name.length == 1 && shortNameIsValid(name(0)) + + private inline def shortNameIsValid(shortName: Char): Boolean = + ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') + + private def getNameWithMarker(name: String | Char): String = name match { + case c: Char => shortArgMarker + c + case s: String if shortNameIsValid(s) => shortArgMarker + s + case s => argMarker + s + } + + private def convert[T](argName: String, arg: String, p: ArgumentParser[T]): () => T = + p.fromStringOption(arg) match + case Some(t) => () => t + case None => error(s"invalid argument for $argName: $arg") + + private def usage(): Unit = + def argsUsage: Seq[String] = + for ((infos, kind) <- parameterInfoss.zip(argKinds)) + yield { + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") + + kind match { + case ArgumentKind.SimpleArgument => s"$namesPrint <${infos.typeName}>" + case ArgumentKind.OptionalArgument => s"[$namesPrint <${infos.typeName}>]" + case ArgumentKind.VarArgument => s"[<${infos.typeName}> [<${infos.typeName}> [...]]]" + } + } + + def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { + def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = + (args, currentLine) match { + case (Nil, "") => acc + case (Nil, l) => (acc :+ l) + case (arg +: t, "") => recurse(t, arg, acc) + case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) + case (arg +: t, l) => recurse(t, arg, acc :+ l) + } + + recurse(argsUsage, "", Vector()).toList + } + + val usageBeginning = s"Usage: $commandName " + val argsOffset = usageBeginning.length + val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) + + println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) + end usage + + private def explain(): Unit = + inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + + def wrapLongLine(line: String, maxLength: Int): List[String] = { + def recurse(s: String, acc: Vector[String]): Seq[String] = + val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) + if ((s.length <= maxLength) || (lastSpace < 0)) + acc :+ s + else { + val (shortLine, rest) = s.splitAt(lastSpace) + recurse(rest.trim.nn, acc :+ shortLine) + } + + recurse(line, Vector()).toList + } + + if (documentation.nonEmpty) + println(wrapLongLine(documentation, maxUsageLineLength).mkString("\n")) + if (nameToParameterInfos.nonEmpty) { + val argNameShift = 2 + val argDocShift = argNameShift + 2 + + println("Arguments:") + for ((infos, kind) <- parameterInfoss.zip(argKinds)) + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val otherNames = (alternativeNames ++: shortNames) match { + case Seq() => "" + case names => names.mkString("(", ", ", ") ") + } + val argDoc = StringBuilder(" " * argNameShift) + argDoc.append(s"$canonicalName $otherNames- ${infos.typeName}") + + kind match { + case ArgumentKind.OptionalArgument => argDoc.append(" (optional)") + case ArgumentKind.VarArgument => argDoc.append(" (vararg)") + case _ => + } + + infos.documentation.foreach( + doc => if (doc.nonEmpty) { + val shiftedDoc = + doc.split("\n").nn + .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) + .mkString("\n") + argDoc.append("\n").append(shiftedDoc) + } + ) + + println(argDoc) + } + end explain + + private def getAliases(paramInfos: ParameterInfos): Seq[String] = + paramInfos.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) + + private def getAlternativeNames(paramInfos: ParameterInfos): Seq[String] = + getAliases(paramInfos).filter(nameIsValid(_)) + + private def getShortNames(paramInfos: ParameterInfos): Seq[Char] = + getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0)) + + private def getInvalidNames(paramInfos: ParameterInfos): Seq[String | Char] = + getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) + + override def argGetter[T](name: String, optDefaultGetter: Option[() => T])(using p: ArgumentParser[T]): () => T = + argKinds += (if optDefaultGetter.nonEmpty then ArgumentKind.OptionalArgument else ArgumentKind.SimpleArgument) + val parameterInfos = nameToParameterInfos(name) + + byNameArgs.get(name) match { + case Some(Nil) => + throw AssertionError(s"$name present in byNameArgs, but it has no argument value") + case Some(argValues) => + if argValues.length > 1 then + // Do not accept multiple values + // Remove this test to take last given argument + error(s"more than one value for $name: ${argValues.mkString(", ")}") + else + convert(name, argValues.last, p) + case None => + if positionalArgs.length > 0 then + convert(name, positionalArgs.dequeue, p) + else if optDefaultGetter.nonEmpty then + optDefaultGetter.get + else + error(s"missing argument for $name") + } + end argGetter + + override def varargGetter[T](name: String)(using p: ArgumentParser[T]): () => Seq[T] = + argKinds += ArgumentKind.VarArgument + + val byNameGetters = byNameArgs.getOrElse(name, Seq()).map(arg => convert(name, arg, p)) + val positionalGetters = positionalArgs.removeAll.map(arg => convert(name, arg, p)) + // First take arguments passed by name, then those passed by position + () => (byNameGetters ++ positionalGetters).map(_()) + + override def run(f: => MainResultType): Unit = + // Check aliases unicity + val nameAndCanonicalName = nameToParameterInfos.toList.flatMap { + case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName) + } + val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) + + for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 + do throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + + // Check aliases validity + val problematicNames = nameToParameterInfos.toList.flatMap((_, infos) => getInvalidNames(infos)) + if problematicNames.length > 0 then throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") + + // Handle unused and invalid args + for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") + for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") + + val displayHelp = + (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) + + if displayHelp then + usage() + println() + explain() + else if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + else + f + end run + end command +end main + +object main: + final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation +end main diff --git a/tests/neg/main-annotation-by-name-param.scala b/tests/neg/main-annotation-by-name-param.scala new file mode 100644 index 000000000000..f2a7eb373b46 --- /dev/null +++ b/tests/neg/main-annotation-by-name-param.scala @@ -0,0 +1,17 @@ +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: => Int): Unit = // error + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/neg/main-annotation-currying.scala b/tests/neg/main-annotation-currying.scala new file mode 100644 index 000000000000..9fdae8dd0954 --- /dev/null +++ b/tests/neg/main-annotation-currying.scala @@ -0,0 +1,6 @@ +object myProgram: + + @main def add(num: Int)(inc: Int): Unit = // error + println(s"$num + $inc = ${num + inc}") + +end myProgram diff --git a/tests/neg/main-annotation-generic.scala b/tests/neg/main-annotation-generic.scala new file mode 100644 index 000000000000..a9d3a63c9aa3 --- /dev/null +++ b/tests/neg/main-annotation-generic.scala @@ -0,0 +1,6 @@ +object myProgram: + + @main def nop[T](t: T): T = // error + t + +end myProgram diff --git a/tests/neg/main-annotation-implicit-given.scala b/tests/neg/main-annotation-implicit-given.scala new file mode 100644 index 000000000000..3efd49a8e3cf --- /dev/null +++ b/tests/neg/main-annotation-implicit-given.scala @@ -0,0 +1,11 @@ +object myProgram: + implicit val x: Int = 2 + given Int = 3 + + @main def showImplicit(implicit num: Int): Unit = // error + println(num) + + @main def showUsing(using num: Int): Unit = // error + println(num) + +end myProgram diff --git a/tests/neg/main-annotation-mainannotation.scala b/tests/neg/main-annotation-mainannotation.scala index 21e37d1779af..dd35c7f0d639 100644 --- a/tests/neg/main-annotation-mainannotation.scala +++ b/tests/neg/main-annotation-mainannotation.scala @@ -1,3 +1,3 @@ import scala.annotation.MainAnnotation -@MainAnnotation def f(i: Int, n: Int) = () // error +@MainAnnotation def f(i: Int, n: Int) = () // error \ No newline at end of file diff --git a/tests/neg/main-annotation-multiple-annot.scala b/tests/neg/main-annotation-multiple-annot.scala new file mode 100644 index 000000000000..0c4dc310a8d6 --- /dev/null +++ b/tests/neg/main-annotation-multiple-annot.scala @@ -0,0 +1,6 @@ +object myProgram: + + @main @main def add1(num: Int, inc: Int): Unit = // error + println(s"$num + $inc = ${num + inc}") + +end myProgram diff --git a/tests/neg/main-annotation-nonmethod.scala b/tests/neg/main-annotation-nonmethod.scala new file mode 100644 index 000000000000..bd713ab39a8a --- /dev/null +++ b/tests/neg/main-annotation-nonmethod.scala @@ -0,0 +1,9 @@ +object myProgram: + + @main val n = 2 // error + + @main class A // error + + @main val f = ((s: String) => println(s)) // error + +end myProgram diff --git a/tests/neg/main-annotation-nonstatic.scala b/tests/neg/main-annotation-nonstatic.scala new file mode 100644 index 000000000000..fe49646e23ae --- /dev/null +++ b/tests/neg/main-annotation-nonstatic.scala @@ -0,0 +1,2 @@ +class A: + @main def foo(bar: Int) = () // error \ No newline at end of file diff --git a/tests/neg/main-annotation-unknown-parser-1.scala b/tests/neg/main-annotation-unknown-parser-1.scala new file mode 100644 index 000000000000..686d530d17de --- /dev/null +++ b/tests/neg/main-annotation-unknown-parser-1.scala @@ -0,0 +1,10 @@ +class MyNumber(val value: Int) { + def +(other: MyNumber): MyNumber = MyNumber(value + other.value) +} + +object myProgram: + + @main def add(num: MyNumber, inc: MyNumber): Unit = // error + println(s"$num + $inc = ${num + inc}") + +end myProgram diff --git a/tests/neg/main-annotation-unknown-parser-2.scala b/tests/neg/main-annotation-unknown-parser-2.scala new file mode 100644 index 000000000000..e1df3e16ab9a --- /dev/null +++ b/tests/neg/main-annotation-unknown-parser-2.scala @@ -0,0 +1,26 @@ +import scala.util.CommandLineParser.FromString + +object myProgram: + + @main def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = // error + val numV = Test.value(num) + val incV = Test.value(inc) + println(s"$numV + $incV = ${numV + incV}") + +end myProgram + + +object Test: + opaque type MyNumber = Int + + def create(n: Int): MyNumber = n + def value(n: MyNumber): Int = n + + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-birthday.check b/tests/run/main-annotation-birthday.check new file mode 100644 index 000000000000..71528f2e27a9 --- /dev/null +++ b/tests/run/main-annotation-birthday.check @@ -0,0 +1 @@ +Happy 23rd birthday, Lisa and Peter diff --git a/tests/run/main-annotation-birthday.scala b/tests/run/main-annotation-birthday.scala new file mode 100644 index 000000000000..d38f145e7b1b --- /dev/null +++ b/tests/run/main-annotation-birthday.scala @@ -0,0 +1,30 @@ +/** + * Wishes a happy birthday to lucky people! + * + * @param age the age of the people whose birthday it is + * @param name the name of the luckiest person! + * @param others all the other lucky people + */ +@main def happyBirthday(age: Int, name: String, others: String*) = + val suffix = + age % 100 match + case 11 | 12 | 13 => "th" + case _ => + age % 10 match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do bldr.append(" and ").append(other) + println(bldr) + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("happyBirthday") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("23", "Lisa", "Peter")) +end Test diff --git a/tests/run/main-annotation-default-value-1.check b/tests/run/main-annotation-default-value-1.check new file mode 100644 index 000000000000..f9f34ca3ad3e --- /dev/null +++ b/tests/run/main-annotation-default-value-1.check @@ -0,0 +1,3 @@ +2 + 3 = 5 +2 + 1 = 3 +0 + 1 = 1 diff --git a/tests/run/main-annotation-default-value-1.scala b/tests/run/main-annotation-default-value-1.scala new file mode 100644 index 000000000000..4bb712c86831 --- /dev/null +++ b/tests/run/main-annotation-default-value-1.scala @@ -0,0 +1,20 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int = 0, inc: Int = 1): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) + callMain(Array("2")) + callMain(Array()) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-default-value-2.check b/tests/run/main-annotation-default-value-2.check new file mode 100644 index 000000000000..d705e0b20e93 --- /dev/null +++ b/tests/run/main-annotation-default-value-2.check @@ -0,0 +1,2 @@ +42 +OK diff --git a/tests/run/main-annotation-default-value-2.scala b/tests/run/main-annotation-default-value-2.scala new file mode 100644 index 000000000000..33282bdc27fa --- /dev/null +++ b/tests/run/main-annotation-default-value-2.scala @@ -0,0 +1,31 @@ +// Sample main method +object myProgram: + + @main def alwaysPassParam(forbiddenParam: Int = throw new IllegalStateException("This should not be evaluated!")): Unit = + println(forbiddenParam) + +end myProgram + +object Test: + def hasCauseIllegalStateException(e: Throwable): Boolean = + e.getCause match { + case null => false + case _: IllegalStateException => true + case e: Throwable => hasCauseIllegalStateException(e) + } + + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("alwaysPassParam") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("42")) + try { + callMain(Array()) + println("This should not be printed") + } + catch { + case e: Exception if hasCauseIllegalStateException(e) => println("OK") + } +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-help-override.check b/tests/run/main-annotation-help-override.check new file mode 100644 index 000000000000..921f6a185fcf --- /dev/null +++ b/tests/run/main-annotation-help-override.check @@ -0,0 +1,56 @@ +##### --help +Usage: helpOverride1 [--notHelp] + +A method that should let --help and -h display help. +Arguments: + --notHelp - Int +Error: invalid argument for help: --help +Usage: helpOverride2 [--help] +Usage: helpOverride3 [-h] + +A method that should let --help display help, but not -h. +Arguments: + -h - Int +Error: invalid argument for help: --help +Error: missing argument for h +Usage: helpOverride4 [--help] [-h] +Error: invalid argument for notHelp: --help +Usage: helpOverride5 [--notHelp | --help] +Usage: helpOverride6 [--notHelp | -h] + +A method that should let --help display help, but not -h. +Arguments: + --notHelp (-h) - Int +Error: invalid argument for notHelp: --help +Error: missing argument for notH +Usage: helpOverride7 [--notHelp | --help] [--notH | -h] +Error: invalid argument for notHelp: --help +Usage: helpOverride8 [--notHelp | --help | -h] +##### -h +Usage: helpOverride1 [--notHelp] + +A method that should let --help and -h display help. +Arguments: + --notHelp - Int +Usage: helpOverride2 [--help] + +A method that should let -h display help, but not --help. +Arguments: + --help - Int +Error: invalid argument for h: -h +Usage: helpOverride3 [-h] +Error: invalid argument for help: -h +Error: missing argument for h +Usage: helpOverride4 [--help] [-h] +Usage: helpOverride5 [--notHelp | --help] + +A method that should let -h display help, but not --help. +Arguments: + --notHelp (--help) - Int +Error: invalid argument for notHelp: -h +Usage: helpOverride6 [--notHelp | -h] +Error: invalid argument for notHelp: -h +Error: missing argument for notH +Usage: helpOverride7 [--notHelp | --help] [--notH | -h] +Error: invalid argument for notHelp: -h +Usage: helpOverride8 [--notHelp | --help | -h] diff --git a/tests/run/main-annotation-help-override.scala b/tests/run/main-annotation-help-override.scala new file mode 100644 index 000000000000..18e3f038178f --- /dev/null +++ b/tests/run/main-annotation-help-override.scala @@ -0,0 +1,47 @@ +import scala.util.Try + +object myProgram: + + /** A method that should let --help and -h display help. */ + @main def helpOverride1(notHelp: Int) = ??? + + /** A method that should let -h display help, but not --help. */ + @main def helpOverride2(help: Int) = ??? + + /** A method that should let --help display help, but not -h. */ + @main def helpOverride3(h: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @main def helpOverride4(help: Int, h: Int) = ??? + + + /** A method that should let -h display help, but not --help. */ + @main def helpOverride5(@main.Alias("help") notHelp: Int) = ??? + + /** A method that should let --help display help, but not -h. */ + @main def helpOverride6(@main.Alias("h") notHelp: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @main def helpOverride7(@main.Alias("help") notHelp: Int, @main.Alias("h") notH: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @main def helpOverride8(@main.Alias("help", "h") notHelp: Int) = ??? + +end myProgram + +object Test: + val allClazzes: Seq[Class[?]] = + LazyList.from(1).map(i => Try(Class.forName("helpOverride" + i.toString))).takeWhile(_.isSuccess).map(_.get) + + def callAllMains(args: Array[String]): Unit = + for (clazz <- allClazzes) { + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + } + + def main(args: Array[String]): Unit = + println("##### --help") + callAllMains(Array("--help")) + println("##### -h") + callAllMains(Array("-h")) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-help.check b/tests/run/main-annotation-help.check new file mode 100644 index 000000000000..6410dd50de58 --- /dev/null +++ b/tests/run/main-annotation-help.check @@ -0,0 +1,158 @@ +Usage: doc1 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc1 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc1 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc1 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc2 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc3 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc4 [--num] [[--inc] ] + +Adds two numbers. +Arguments: + --num - Int + the first number + --inc - Int (optional) + the second number +Usage: doc5 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + the first number + --inc - Int +Usage: doc6 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + --inc - Int +Usage: doc7 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + the first number + --inc - Int + the second number +Usage: doc8 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + the first number + --inc - Int + the second number +Usage: doc9 [--num] [--inc] + +Adds two numbers. Same as doc1. +Arguments: + --num - Int + the first number + --inc - Int + the second number +Usage: doc10 [--num] [--inc] + +Adds two numbers. +This should be on another line. +And this also. +Arguments: + --num - Int + I might have to write this on two lines + --inc - Int + I might even have to write this one on three lines +Usage: doc11 [--num] [--inc] + +Adds two numbers. +Arguments: + --num - Int + the first number + Oh, a new line! + --inc - Int + the second number + And another one! +Usage: doc12 [--num] [--inc] + +Adds two numbers. It seems that I have a very long line of documentation and therefore might need to be cut at some +point to fit a small terminal screen. +Arguments: + --num - Int + --inc - Int +Usage: doc13 [--num] [--inc] + +Addstwonumbers.ItseemsthatIhaveaverylonglineofdocumentationandthereforemightneedtobecutatsomepointtofitasmallterminalscreen. +Arguments: + --num - Int + --inc - Int +Usage: doc14 [--arg1] [--arg2] [--arg3] [--arg4] [--arg5] [--arg6] + [--arg7] [--arg8] [[--arg9] ] [[--arg10] ] [[--arg11] ] + [[--arg12] ] [[--arg13] ] [[--arg14] ] [[--arg15] ] [ [ [...]]] + +Loudly judges the number of argument you gave to this poor function. +Arguments: + --arg1 - String + --arg2 - Int + --arg3 - String + --arg4 - Int + --arg5 - String + --arg6 - Int + --arg7 - String + --arg8 - Int + --arg9 - String (optional) + --arg10 - Int (optional) + --arg11 - String (optional) + --arg12 - Int (optional) + --arg13 - String (optional) + --arg14 - Int (optional) + --arg15 - String (optional) + --arg16 - Int (vararg) +Usage: doc15 [--myNum] [--myInc] + +Adds two instances of MyNumber. +Arguments: + --myNum - MyNumber + my first number to add + --myInc - MyNumber + my second number to add +Usage: doc16 [--first] [--second] + +Compares two instances of MyGeneric. +Arguments: + --first - MyGeneric[Int] + my first element + --second - MyGeneric[Int] + my second element +Usage: doc17 [-a] [-b] [-c] + +Arguments: + -a - Int + -b - Int + -c - String diff --git a/tests/run/main-annotation-help.scala b/tests/run/main-annotation-help.scala new file mode 100644 index 000000000000..cef057789f25 --- /dev/null +++ b/tests/run/main-annotation-help.scala @@ -0,0 +1,173 @@ +import scala.util.CommandLineParser.FromString +import scala.util.Try + +class MyNumber(val value: Int): + def +(other: MyNumber): MyNumber = MyNumber(value + other.value) + +class MyGeneric[T](val value: T) + +given FromString[MyNumber] with + override def fromString(s: String): MyNumber = MyNumber(summon[FromString[Int]].fromString(s)) + +given FromString[MyGeneric[Int]] with + override def fromString(s: String): MyGeneric[Int] = MyGeneric(summon[FromString[Int]].fromString(s)) + +object myProgram: + + /** + * Adds two numbers. + */ + @main def doc1(num: Int, inc: Int): Unit = () + + /** Adds two numbers. */ + @main def doc2(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + */ + @main def doc3(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + * @param inc the second number + */ + @main def doc4(num: Int, inc: Int = 1): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + */ + @main def doc5(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num + * @param inc + */ + @main def doc6(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + * @param inc the second number + * @return the sum of the two numbers (not really) + */ + @main def doc7(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + * @param inc the second number + * @return the sum of the two numbers (not really) + */ + @main def doc8(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. Same as [[doc1]]. + * + * @param num the first number + * @param inc the second number + * @return the sum of the two numbers (not really) + * @see [[doc1]] + */ + @main def doc9(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * This should be on another line. + * + * + * + * + * And this also. + * + * + * @param num I might have to write this + * on two lines + * @param inc I might even + * have to write this one + * on three lines + */ + @main def doc10(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + * + * Oh, a new line! + * + * @param inc the second number + * + * And another one! + */ + @main def doc11(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. It seems that I have a very long line of documentation and therefore might need to be cut at some point to fit a small terminal screen. + */ + @main def doc12(num: Int, inc: Int): Unit = () + + /** + * Addstwonumbers.ItseemsthatIhaveaverylonglineofdocumentationandthereforemightneedtobecutatsomepointtofitasmallterminalscreen. + */ + @main def doc13(num: Int, inc: Int): Unit = () + + /** + * Loudly judges the number of argument you gave to this poor function. + */ + @main def doc14( + arg1: String, arg2: Int, arg3: String, arg4: Int, + arg5: String, arg6: Int, arg7: String, arg8: Int, + arg9: String = "I", arg10: Int = 42, arg11: String = "used", arg12: Int = 0, + arg13: String = "to", arg14: Int = 34, arg15: String = "wonder", arg16: Int* + ): Unit = () + + /** + * Adds two instances of [[MyNumber]]. + * @param myNum my first number to add + * @param myInc my second number to add + */ + @main def doc15(myNum: MyNumber, myInc: MyNumber): Unit = () + + /** + * Compares two instances of [[MyGeneric]]. + * @param first my first element + * @param second my second element + */ + @main def doc16(first: MyGeneric[Int], second: MyGeneric[Int]): Unit = () + + // This should not be printed in explain! + @main def doc17(a: Int, b: Int, c: String): Unit = () + +end myProgram + +object Test: + def callMain1(args: Array[String]): Unit = + val clazz = Class.forName("doc1") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + val allClazzes: Seq[Class[?]] = + LazyList.from(1).map(i => Try(Class.forName("doc" + i.toString))).takeWhile(_.isSuccess).map(_.get) + + def callAllMains(args: Array[String]): Unit = + for (clazz <- allClazzes) { + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + } + + def main(args: Array[String]): Unit = + callMain1(Array("--help")) + callMain1(Array("Some", "garbage", "before", "--help")) + callMain1(Array("--help", "and", "some", "stuff", "after")) + + callAllMains(Array("--help")) +end Test diff --git a/tests/run/main-annotation-homemade-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala index daf27b944d99..4719622e3ab5 100644 --- a/tests/run/main-annotation-homemade-annot-1.scala +++ b/tests/run/main-annotation-homemade-annot-1.scala @@ -1,5 +1,5 @@ import scala.concurrent._ -import scala.annotation.* +import scala.annotation.MainAnnotation import scala.collection.mutable import ExecutionContext.Implicits.global import duration._ diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala index 3fc42abcce79..602184be7177 100644 --- a/tests/run/main-annotation-homemade-annot-3.scala +++ b/tests/run/main-annotation-homemade-annot-3.scala @@ -21,3 +21,4 @@ class mainNoArgs extends MainAnnotation[FromString, Any]: def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? def run(program: () => Any): Unit = program() + diff --git a/tests/run/main-annotation-homemade-parser-1.check b/tests/run/main-annotation-homemade-parser-1.check new file mode 100644 index 000000000000..56acd5c7a7ab --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-1.check @@ -0,0 +1 @@ +2 + 3 = 5 diff --git a/tests/run/main-annotation-homemade-parser-1.scala b/tests/run/main-annotation-homemade-parser-1.scala new file mode 100644 index 000000000000..c4d55e879c6d --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-1.scala @@ -0,0 +1,26 @@ +import scala.util.CommandLineParser.FromString + +class MyNumber(val value: Int) { + def +(other: MyNumber): MyNumber = MyNumber(value + other.value) +} + +given FromString[MyNumber] with + override def fromString(s: String): MyNumber = MyNumber(summon[FromString[Int]].fromString(s)) + +object myProgram: + + @main def add(num: MyNumber, inc: MyNumber): Unit = + println(s"${num.value} + ${inc.value} = ${num.value + inc.value}") + +end myProgram + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-homemade-parser-2.check b/tests/run/main-annotation-homemade-parser-2.check new file mode 100644 index 000000000000..56acd5c7a7ab --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-2.check @@ -0,0 +1 @@ +2 + 3 = 5 diff --git a/tests/run/main-annotation-homemade-parser-2.scala b/tests/run/main-annotation-homemade-parser-2.scala new file mode 100644 index 000000000000..c79b598bcf86 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-2.scala @@ -0,0 +1,29 @@ +import scala.util.CommandLineParser.FromString + +given FromString[Test.MyNumber] with + override def fromString(s: String) = Test.create(summon[FromString[Int]].fromString(s)) + +object myProgram: + + @main def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = + val numV = Test.value(num) + val incV = Test.value(inc) + println(s"$numV + $incV = ${numV + incV}") + +end myProgram + + +object Test: + opaque type MyNumber = Int + + def create(n: Int): MyNumber = n + def value(n: MyNumber): Int = n + + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-homemade-parser-3.check b/tests/run/main-annotation-homemade-parser-3.check new file mode 100644 index 000000000000..8a34cb5bfa7a --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-3.check @@ -0,0 +1 @@ +44 + 45 = 89 diff --git a/tests/run/main-annotation-homemade-parser-3.scala b/tests/run/main-annotation-homemade-parser-3.scala new file mode 100644 index 000000000000..d74588c227f5 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-3.scala @@ -0,0 +1,25 @@ +import scala.util.CommandLineParser.FromString + +given FromString[Int] with + override def fromString(s: String) = s.toInt + 42 + +object myProgram: + + given FromString[Int] with + override def fromString(s: String) = -1 * s.toInt // Should be ignored, because not top-level + + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-homemade-parser-4.check b/tests/run/main-annotation-homemade-parser-4.check new file mode 100644 index 000000000000..d1e1a6bdd0cb --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-4.check @@ -0,0 +1,7 @@ +Some(7) +Some(42) +None + +Left(7) +Right(No argument given) +Right(Unable to parse argument abc) diff --git a/tests/run/main-annotation-homemade-parser-4.scala b/tests/run/main-annotation-homemade-parser-4.scala new file mode 100644 index 000000000000..0bc826d0258e --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-4.scala @@ -0,0 +1,46 @@ +import scala.util.CommandLineParser.FromString + +given [T : FromString]: FromString[Option[T]] with + override def fromString(s: String) = Some(summon[FromString[T]].fromString(s)) + override def fromStringOption(s: String) = + try { + Some(fromString(s)) + } + catch { + case _: IllegalArgumentException => Some(None) + } + +given [T : FromString]: FromString[Either[T, String]] with + override def fromString(s: String) = Left(summon[FromString[T]].fromString(s)) + override def fromStringOption(s: String) = + try { + Some(fromString(s)) + } + catch { + case _: IllegalArgumentException => Some(Right(s"Unable to parse argument $s")) + } + +object myProgram: + + @main def getOption(o: Option[Int] = Some(42)) = println(o) + + @main def getEither(e: Either[Int, String] = Right("No argument given")) = println(e) + +end myProgram + + +object Test: + def call(className: String, args: Array[String]): Unit = + val clazz = Class.forName(className) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + call("getOption", Array("7")) + call("getOption", Array()) + call("getOption", Array("abc")) + println + call("getEither", Array("7")) + call("getEither", Array()) + call("getEither", Array("abc")) +end Test diff --git a/tests/run/main-annotation-homemade-parser-5.check b/tests/run/main-annotation-homemade-parser-5.check new file mode 100644 index 000000000000..8925cc4f276b --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-5.check @@ -0,0 +1,2 @@ +42 +Hello world! diff --git a/tests/run/main-annotation-homemade-parser-5.scala b/tests/run/main-annotation-homemade-parser-5.scala new file mode 100644 index 000000000000..4621716b537e --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-5.scala @@ -0,0 +1,25 @@ +import scala.util.CommandLineParser.FromString + +given intParser: FromString[Int => Int] with + override def fromString(s: String) = n => summon[FromString[Int]].fromString(s) + n + +given stringParser: FromString[String => String] with + override def fromString(s: String) = s1 => summon[FromString[String]].fromString(s) + s1 + +object myProgram: + + @main def show(getI: Int => Int, getS: String => String) = + println(getI(3)) + println(getS(" world!")) + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("show") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("39", "Hello")) +end Test diff --git a/tests/run/main-annotation-multiple.check b/tests/run/main-annotation-multiple.check new file mode 100644 index 000000000000..eba187d5c3a3 --- /dev/null +++ b/tests/run/main-annotation-multiple.check @@ -0,0 +1,2 @@ +2 + 3 = 5 +2 - 3 = -1 diff --git a/tests/run/main-annotation-multiple.scala b/tests/run/main-annotation-multiple.scala new file mode 100644 index 000000000000..aebb2cb059dc --- /dev/null +++ b/tests/run/main-annotation-multiple.scala @@ -0,0 +1,23 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + + /** Subtracts two numbers */ + @main def sub(num: Int, inc: Int): Unit = + println(s"$num - $inc = ${num - inc}") + +end myProgram + +object Test: + def callMain(mainMeth: String, args: Array[String]): Unit = + val clazz = Class.forName(mainMeth) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain("add", Array("2", "3")) + callMain("sub", Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-named-params.check b/tests/run/main-annotation-named-params.check new file mode 100644 index 000000000000..743f61642a0c --- /dev/null +++ b/tests/run/main-annotation-named-params.check @@ -0,0 +1,9 @@ +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +Error: more than one value for num: 2, 1 +Usage: add [--num] [--inc] +Error: more than one value for num: 2, 1 +Error: more than one value for inc: 1, 3 +Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-named-params.scala b/tests/run/main-annotation-named-params.scala new file mode 100644 index 000000000000..f4978bafe480 --- /dev/null +++ b/tests/run/main-annotation-named-params.scala @@ -0,0 +1,25 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("--num", "2", "--inc", "3")) + callMain(Array("--inc", "3", "--num", "2")) + + callMain(Array("2", "--inc", "3")) + callMain(Array("--num", "2", "3")) + + callMain(Array("--num", "2", "--num", "1", "--inc", "3")) + callMain(Array("--inc", "1", "--num", "2", "--num", "1", "--inc", "3")) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-no-parameters-no-parens.check b/tests/run/main-annotation-no-parameters-no-parens.check new file mode 100644 index 000000000000..020c8e62064f --- /dev/null +++ b/tests/run/main-annotation-no-parameters-no-parens.check @@ -0,0 +1 @@ +I run properly! diff --git a/tests/run/main-annotation-no-parameters-no-parens.scala b/tests/run/main-annotation-no-parameters-no-parens.scala new file mode 100644 index 000000000000..9ba6f150f21b --- /dev/null +++ b/tests/run/main-annotation-no-parameters-no-parens.scala @@ -0,0 +1,18 @@ +// Sample main method +object myProgram: + + /** Does nothing, except confirming that it runs */ + @main def run: Unit = + println("I run properly!") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("run") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array()) +end Test diff --git a/tests/run/main-annotation-no-parameters.check b/tests/run/main-annotation-no-parameters.check new file mode 100644 index 000000000000..020c8e62064f --- /dev/null +++ b/tests/run/main-annotation-no-parameters.check @@ -0,0 +1 @@ +I run properly! diff --git a/tests/run/main-annotation-no-parameters.scala b/tests/run/main-annotation-no-parameters.scala new file mode 100644 index 000000000000..978ec6e6373e --- /dev/null +++ b/tests/run/main-annotation-no-parameters.scala @@ -0,0 +1,18 @@ +// Sample main method +object myProgram: + + /** Does nothing, except confirming that it runs */ + @main def run(): Unit = + println("I run properly!") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("run") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array()) +end Test diff --git a/tests/run/main-annotation-overload.check b/tests/run/main-annotation-overload.check new file mode 100644 index 000000000000..56acd5c7a7ab --- /dev/null +++ b/tests/run/main-annotation-overload.check @@ -0,0 +1 @@ +2 + 3 = 5 diff --git a/tests/run/main-annotation-overload.scala b/tests/run/main-annotation-overload.scala new file mode 100644 index 000000000000..5688edc1bfc2 --- /dev/null +++ b/tests/run/main-annotation-overload.scala @@ -0,0 +1,30 @@ +// Sample main method +object myProgram: + + /** Adds three numbers (malformed, doesn't work) */ + def add(num1: Int, num2: Int, num3: Int): Unit = + ??? + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + + /** Adds one number (malformed, doesn't work) */ + def add(num: Int): Unit = + ??? + + /** Adds zero numbers (malformed, doesn't work) */ + def add(): Int = + ??? + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-param-annot-1.check b/tests/run/main-annotation-param-annot-1.check new file mode 100644 index 000000000000..13d4bb969d18 --- /dev/null +++ b/tests/run/main-annotation-param-annot-1.check @@ -0,0 +1,36 @@ +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 +2 + 3 = 5 diff --git a/tests/run/main-annotation-param-annot-1.scala b/tests/run/main-annotation-param-annot-1.scala new file mode 100644 index 000000000000..27125d052567 --- /dev/null +++ b/tests/run/main-annotation-param-annot-1.scala @@ -0,0 +1,105 @@ +object myProgram: + @main def altName1( + @main.Alias("myNum") num: Int, + inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @main def altName2( + @main.Alias("myNum") num: Int, + @main.Alias("myInc") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @main def shortName1( + @main.Alias("n") num: Int, + inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @main def shortName2( + @main.Alias("n") num: Int, + @main.Alias("i") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @main def mix1( + @main.Alias("myNum") @main.Alias("n") num: Int, + @main.Alias("i") @main.Alias("myInc") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + def myNum: String = "myNum" + def myShortNum = { + var short = 'a' + for i <- 0 until 'n' - 'a' + do + short = (short.toInt + 1).toChar + short.toString + } + def myInc = {new Exception("myInc")}.getMessage + def myShortInc = () => "i" + + @main def mix2( + @main.Alias(myNum) @main.Alias(myShortNum) num: Int, + @main.Alias(myShortInc()) @main.Alias(myInc) inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @main def multiple( + @main.Alias("myNum", "n") num: Int, + @main.Alias("i", "myInc") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") +end myProgram + + +object Test: + def callMain(className: String, args: Array[String]) = + val clazz = Class.forName(className) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain("altName1", Array("--num", "2", "--inc", "3")) + callMain("altName1", Array("--myNum", "2", "--inc", "3")) + + callMain("altName2", Array("--num", "2", "--inc", "3")) + callMain("altName2", Array("--myNum", "2", "--inc", "3")) + callMain("altName2", Array("--num", "2", "--myInc", "3")) + callMain("altName2", Array("--myNum", "2", "--myInc", "3")) + + callMain("shortName1", Array("--num", "2", "--inc", "3")) + callMain("shortName1", Array("-n", "2", "--inc", "3")) + + callMain("shortName2", Array("--num", "2", "--inc", "3")) + callMain("shortName2", Array("-n", "2", "--inc", "3")) + callMain("shortName2", Array("--num", "2", "-i", "3")) + callMain("shortName2", Array("-n", "2", "-i", "3")) + + callMain("mix1", Array("--num", "2", "--inc", "3")) + callMain("mix1", Array("-n", "2", "--inc", "3")) + callMain("mix1", Array("--num", "2", "-i", "3")) + callMain("mix1", Array("-n", "2", "-i", "3")) + callMain("mix1", Array("--myNum", "2", "--myInc", "3")) + callMain("mix1", Array("-n", "2", "--myInc", "3")) + callMain("mix1", Array("--myNum", "2", "-i", "3")) + callMain("mix1", Array("-n", "2", "-i", "3")) + callMain("mix2", Array("--num", "2", "--inc", "3")) + callMain("mix2", Array("-n", "2", "--inc", "3")) + callMain("mix2", Array("--num", "2", "-i", "3")) + callMain("mix2", Array("-n", "2", "-i", "3")) + callMain("mix2", Array("--myNum", "2", "--myInc", "3")) + callMain("mix2", Array("-n", "2", "--myInc", "3")) + callMain("mix2", Array("--myNum", "2", "-i", "3")) + callMain("mix2", Array("-n", "2", "-i", "3")) + + callMain("multiple", Array("--num", "2", "--inc", "3")) + callMain("multiple", Array("-n", "2", "--inc", "3")) + callMain("multiple", Array("--num", "2", "-i", "3")) + callMain("multiple", Array("-n", "2", "-i", "3")) + callMain("multiple", Array("--myNum", "2", "--myInc", "3")) + callMain("multiple", Array("-n", "2", "--myInc", "3")) + callMain("multiple", Array("--myNum", "2", "-i", "3")) + callMain("multiple", Array("-n", "2", "-i", "3")) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-param-annot-2.check b/tests/run/main-annotation-param-annot-2.check new file mode 100644 index 000000000000..6bf25043399c --- /dev/null +++ b/tests/run/main-annotation-param-annot-2.check @@ -0,0 +1,9 @@ +OK +OK +OK +OK +OK +OK +OK +OK +OK diff --git a/tests/run/main-annotation-param-annot-2.scala b/tests/run/main-annotation-param-annot-2.scala new file mode 100644 index 000000000000..1c3699f2ab59 --- /dev/null +++ b/tests/run/main-annotation-param-annot-2.scala @@ -0,0 +1,57 @@ +object myProgram: + @main def multipleSameShortNames1( + @main.Alias("n") num: Int, + @main.Alias("n") inc: Int + ): Unit = () + + @main def multipleSameShortNames2( + @main.Alias("n") @main.Alias("n") num: Int, + inc: Int + ): Unit = () + + @main def multipleSameNames1( + @main.Alias("arg") num: Int, + @main.Alias("arg") inc: Int + ): Unit = () + + @main def multipleSameNames2( + @main.Alias("arg") @main.Alias("arg") num: Int, + inc: Int + ): Unit = () + + @main def multipleSameNames3( + num: Int, + @main.Alias("num") inc: Int + ): Unit = () +end myProgram + + +object Test: + def hasCauseIllegalArgumentException(e: Throwable): Boolean = + e.getCause match { + case null => false + case _: IllegalArgumentException => true + case e: Throwable => hasCauseIllegalArgumentException(e) + } + + def callMain(className: String, args: Array[String]) = + val clazz = Class.forName(className) + val method = clazz.getMethod("main", classOf[Array[String]]) + + try { method.invoke(null, args) } + catch { + case e: Exception if hasCauseIllegalArgumentException(e) => println("OK") + } + + def main(args: Array[String]): Unit = + callMain("multipleSameShortNames1", Array("--num", "2", "--inc", "3")) + callMain("multipleSameShortNames1", Array("-n", "2", "--inc", "3")) + callMain("multipleSameShortNames2", Array("--num", "2", "--inc", "3")) + callMain("multipleSameShortNames2", Array("-n", "2", "--inc", "3")) + + callMain("multipleSameNames1", Array("--num", "2", "--inc", "3")) + callMain("multipleSameNames1", Array("--arg", "2", "--inc", "3")) + callMain("multipleSameNames2", Array("--num", "2", "--inc", "3")) + callMain("multipleSameNames2", Array("--arg", "2", "--inc", "3")) + callMain("multipleSameNames3", Array("--num", "2", "--inc", "3")) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-param-annot-invalid-params.check b/tests/run/main-annotation-param-annot-invalid-params.check new file mode 100644 index 000000000000..0eabe3671300 --- /dev/null +++ b/tests/run/main-annotation-param-annot-invalid-params.check @@ -0,0 +1,3 @@ +OK +OK +OK diff --git a/tests/run/main-annotation-param-annot-invalid-params.scala b/tests/run/main-annotation-param-annot-invalid-params.scala new file mode 100644 index 000000000000..4cfa15705320 --- /dev/null +++ b/tests/run/main-annotation-param-annot-invalid-params.scala @@ -0,0 +1,42 @@ +import java.lang.reflect.InvocationTargetException + +object myProgram: + + @main def empty( + @main.Alias("") i: Int, + ): Unit = () + + @main def space( + @main.Alias(" ") i: Int, + ): Unit = () + + @main def nonLetter( + @main.Alias("1") i: Int, + ): Unit = () + +end myProgram + +object Test: + def hasCauseIllegalArgumentException(e: Throwable): Boolean = + e.getCause match { + case null => false + case _: IllegalArgumentException => true + case e: Throwable => hasCauseIllegalArgumentException(e) + } + + def callMain(className: String, args: Array[String]) = + val clazz = Class.forName(className) + val method = clazz.getMethod("main", classOf[Array[String]]) + try { + method.invoke(null, args) + println(s"Calling $className should result in an IllegalArgumentException being thrown") + } + catch { + case e: InvocationTargetException if hasCauseIllegalArgumentException(e) => println("OK") + } + + def main(args: Array[String]): Unit = + callMain("empty", Array("3")) + callMain("space", Array("3")) + callMain("nonLetter", Array("3")) +end Test \ No newline at end of file diff --git a/tests/run/main-annotation-return-type-1.check b/tests/run/main-annotation-return-type-1.check new file mode 100644 index 000000000000..2239817268f0 --- /dev/null +++ b/tests/run/main-annotation-return-type-1.check @@ -0,0 +1,4 @@ +Direct call +5 +Main call +5 diff --git a/tests/run/main-annotation-return-type-1.scala b/tests/run/main-annotation-return-type-1.scala new file mode 100644 index 000000000000..8ddc1a07539f --- /dev/null +++ b/tests/run/main-annotation-return-type-1.scala @@ -0,0 +1,21 @@ +// Sample main method +object myProgram: + + /** Adds two numbers and returns them */ + @main def add(num: Int, inc: Int) = + println(num + inc) + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + println("Direct call") + myProgram.add(2, 3) + println("Main call") + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-return-type-2.check b/tests/run/main-annotation-return-type-2.check new file mode 100644 index 000000000000..2239817268f0 --- /dev/null +++ b/tests/run/main-annotation-return-type-2.check @@ -0,0 +1,4 @@ +Direct call +5 +Main call +5 diff --git a/tests/run/main-annotation-return-type-2.scala b/tests/run/main-annotation-return-type-2.scala new file mode 100644 index 000000000000..dbf1503f38a1 --- /dev/null +++ b/tests/run/main-annotation-return-type-2.scala @@ -0,0 +1,24 @@ +class MyResult(val result: Int): + override def toString: String = result.toString + +// Sample main method +object myProgram: + + /** Adds two numbers and returns them */ + @main def add(num: Int, inc: Int) = + println(MyResult(num + inc)) + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + println("Direct call") + myProgram.add(2, 3) + println("Main call") + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-short-name.check b/tests/run/main-annotation-short-name.check new file mode 100644 index 000000000000..930ac62b97ee --- /dev/null +++ b/tests/run/main-annotation-short-name.check @@ -0,0 +1,7 @@ +2 + 3 = 5 +2 + 3 = 5 +Error: missing argument for n +Error: missing argument for i +Error: unknown argument name: --n +Error: unknown argument name: --i +Usage: add [-n] [-i] diff --git a/tests/run/main-annotation-short-name.scala b/tests/run/main-annotation-short-name.scala new file mode 100644 index 000000000000..8efa51c55354 --- /dev/null +++ b/tests/run/main-annotation-short-name.scala @@ -0,0 +1,20 @@ +object myProgram: + + /** Adds two numbers */ + @main def add(n: Int, i: Int): Unit = + println(s"$n + $i = ${n + i}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("-n", "2", "-i", "3")) + callMain(Array("-i", "3", "-n", "2")) + + callMain(Array("--n", "2", "--i", "3")) +end Test diff --git a/tests/run/main-annotation-simple.check b/tests/run/main-annotation-simple.check new file mode 100644 index 000000000000..56acd5c7a7ab --- /dev/null +++ b/tests/run/main-annotation-simple.check @@ -0,0 +1 @@ +2 + 3 = 5 diff --git a/tests/run/main-annotation-simple.scala b/tests/run/main-annotation-simple.scala new file mode 100644 index 000000000000..33eb4cbb0308 --- /dev/null +++ b/tests/run/main-annotation-simple.scala @@ -0,0 +1,17 @@ +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) +end Test diff --git a/tests/run/main-annotation-top-level.check b/tests/run/main-annotation-top-level.check new file mode 100644 index 000000000000..1e5864820d78 --- /dev/null +++ b/tests/run/main-annotation-top-level.check @@ -0,0 +1,5 @@ +2 + 3 = 5 +2 + 3 = 5 +2 = 2 +0 = 0 +1 + 2 + 3 + 4 = 10 diff --git a/tests/run/main-annotation-top-level.scala b/tests/run/main-annotation-top-level.scala new file mode 100644 index 000000000000..932b74b690be --- /dev/null +++ b/tests/run/main-annotation-top-level.scala @@ -0,0 +1,32 @@ +/** Adds two numbers */ +@main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +/** Adds any amount of numbers */ +@main def addAll(num: Int = 0, incs: Int*): Unit = + print(num) + if (incs.length > 0) { + print(" + ") + print(incs.mkString(" + ")) + } + println(s" = ${num + incs.sum}") + +object Test: + def callMainAdd(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def callMainAddAll(args: Array[String]): Unit = + val clazz = Class.forName("addAll") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMainAdd(Array("2", "3")) + + callMainAddAll(Array("2", "3")) + callMainAddAll(Array("2")) + callMainAddAll(Array()) + callMainAddAll(Array("1", "2", "3", "4")) +end Test diff --git a/tests/run/main-annotation-types.check b/tests/run/main-annotation-types.check new file mode 100644 index 000000000000..0b7f741518c7 --- /dev/null +++ b/tests/run/main-annotation-types.check @@ -0,0 +1,18 @@ +Here's what I got: +int - 2 +double - 3.0 +string - 4 +boolean - true + +Here's what I got: +int - -1 +double - 3.4567890987654456E18 +string - false +boolean - false + +Here's what I got: +int - 2147483647 +double - 3.1415926535 +string - Hello world! +boolean - true + diff --git a/tests/run/main-annotation-types.scala b/tests/run/main-annotation-types.scala new file mode 100644 index 000000000000..8d4fa14761ab --- /dev/null +++ b/tests/run/main-annotation-types.scala @@ -0,0 +1,30 @@ +// Sample main method +object myProgram: + + /** Displays some parameters */ + @main def show( + int: Int, + double: Double, + string: String, + boolean: Boolean + ): Unit = + println("Here's what I got:") + println(s"int - $int") + println(s"double - $double") + println(s"string - $string") + println(s"boolean - $boolean") + println() + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("show") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3", "4", "true")) + callMain(Array("-1", "3456789098765445678", "false", "FALSE")) + callMain(Array("2147483647", "3.1415926535", "Hello world!", "True")) +end Test diff --git a/tests/run/main-annotation-vararg-1.check b/tests/run/main-annotation-vararg-1.check new file mode 100644 index 000000000000..c35a83bfd5fa --- /dev/null +++ b/tests/run/main-annotation-vararg-1.check @@ -0,0 +1,5 @@ +2 + 3 = 5 +2 + 3 + -4 = 1 +1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55 +0 = 0 +No number input diff --git a/tests/run/main-annotation-vararg-1.scala b/tests/run/main-annotation-vararg-1.scala new file mode 100644 index 000000000000..bc8f84a3c6a9 --- /dev/null +++ b/tests/run/main-annotation-vararg-1.scala @@ -0,0 +1,25 @@ +// Sample main method +object myProgram: + + /** Adds any amount of numbers */ + @main def add(nums: Int*): Unit = + if (nums.isEmpty) + println("No number input") + else + println(s"${nums.mkString(" + ")} = ${nums.sum}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "3")) + callMain(Array("2", "3", "-4")) + callMain((1 to 10).toArray.map(_.toString)) + callMain(Array("0")) + callMain(Array()) +end Test diff --git a/tests/run/main-annotation-vararg-2.check b/tests/run/main-annotation-vararg-2.check new file mode 100644 index 000000000000..7a7c9798fab4 --- /dev/null +++ b/tests/run/main-annotation-vararg-2.check @@ -0,0 +1,10 @@ +Correct +Correct +Expected 3 arguments, but got 1 + No 3 elements +Correct +Expected 0 arguments, but got 4 + I, shouldn't, be, here +Expected -2 arguments, but got 1 + How does that make sense? +Correct diff --git a/tests/run/main-annotation-vararg-2.scala b/tests/run/main-annotation-vararg-2.scala new file mode 100644 index 000000000000..4f0507268133 --- /dev/null +++ b/tests/run/main-annotation-vararg-2.scala @@ -0,0 +1,28 @@ +// Sample main method +object myProgram: + + /** Checks that the correct amount of parameters were passed */ + @main def count(count: Int, elems: String*): Unit = + if (elems.length == count) + println("Correct") + else + println(s"Expected $count argument${if (count != 1) "s" else ""}, but got ${elems.length}") + println(s" ${elems.mkString(", ")}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("count") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("1", "Hello")) + callMain(Array("2", "Hello", "world!")) + callMain(Array("3", "No 3 elements")) + callMain(Array("0")) + callMain(Array("0", "I", "shouldn't", "be", "here")) + callMain(Array("-2", "How does that make sense?")) + callMain(Array("26") ++ ('a' to 'z').toArray.map(_.toString)) +end Test diff --git a/tests/run/main-annotation-wrong-param-1.check b/tests/run/main-annotation-wrong-param-1.check new file mode 100644 index 000000000000..a51e31141d4f --- /dev/null +++ b/tests/run/main-annotation-wrong-param-1.check @@ -0,0 +1,13 @@ +Error: invalid argument for inc: true +Error: unused argument: SPAAAAACE +Usage: add [--num] [--inc] +Error: invalid argument for num: add +Error: unused argument: 3 +Usage: add [--num] [--inc] +Error: invalid argument for num: true +Error: invalid argument for inc: false +Error: unused argument: 10 +Usage: add [--num] [--inc] +Error: invalid argument for num: binary +Error: unused argument: 01 +Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-1.scala b/tests/run/main-annotation-wrong-param-1.scala new file mode 100644 index 000000000000..88e7e4c67937 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-1.scala @@ -0,0 +1,21 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "true", "SPAAAAACE")) + callMain(Array("add", "2", "3")) + callMain(Array("true", "false", "10")) + callMain(Array("binary", "10", "01")) +end Test diff --git a/tests/run/main-annotation-wrong-param-names.check b/tests/run/main-annotation-wrong-param-names.check new file mode 100644 index 000000000000..d9bbcc049196 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-names.check @@ -0,0 +1,15 @@ +Error: missing argument for num +Error: missing argument for inc +Error: unknown argument name: --n +Error: unknown argument name: --i +Usage: add [--num] [--inc] +Error: invalid argument for num: num +Error: unused argument: inc +Error: unused argument: 10 +Usage: add [--num] [--inc] +Error: missing argument for inc +Error: unknown argument name: --something +Usage: add [--num] [--inc] +Error: missing argument for inc +Error: unknown argument name: --else +Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-names.scala b/tests/run/main-annotation-wrong-param-names.scala new file mode 100644 index 000000000000..548bfb3521b5 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-names.scala @@ -0,0 +1,21 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("--n", "1", "--i", "10")) + callMain(Array("num", "1", "inc", "10")) + callMain(Array("--something", "1", "10")) + callMain(Array("1", "--else", "10")) +end Test diff --git a/tests/run/main-annotation-wrong-param-number.check b/tests/run/main-annotation-wrong-param-number.check new file mode 100644 index 000000000000..177b4e0069be --- /dev/null +++ b/tests/run/main-annotation-wrong-param-number.check @@ -0,0 +1,16 @@ +Error: missing argument for num +Error: missing argument for inc +Usage: add [--num] [--inc] +Error: missing argument for inc +Usage: add [--num] [--inc] +Error: unused argument: 3 +Usage: add [--num] [--inc] +Error: unused argument: 3 +Error: unused argument: 4 +Error: unused argument: 5 +Error: unused argument: 6 +Error: unused argument: 7 +Error: unused argument: 8 +Error: unused argument: 9 +Error: unused argument: 10 +Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-number.scala b/tests/run/main-annotation-wrong-param-number.scala new file mode 100644 index 000000000000..d2025faffd21 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-number.scala @@ -0,0 +1,21 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array()) + callMain(Array("1")) + callMain(Array("1", "2", "3")) + callMain(Array((1 to 10).toArray.map(_.toString): _*)) +end Test diff --git a/tests/run/main-annotation-wrong-param-type.check b/tests/run/main-annotation-wrong-param-type.check new file mode 100644 index 000000000000..db8f007cc823 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-type.check @@ -0,0 +1,14 @@ +Error: invalid argument for inc: true +Usage: add [--num] [--inc] +Error: invalid argument for num: 2.1 +Usage: add [--num] [--inc] +Error: invalid argument for inc: 3.1415921535 +Usage: add [--num] [--inc] +Error: invalid argument for num: 192.168.1.1 +Usage: add [--num] [--inc] +Error: invalid argument for num: false +Error: invalid argument for inc: true +Usage: add [--num] [--inc] +Error: invalid argument for num: Hello +Error: invalid argument for inc: world! +Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-type.scala b/tests/run/main-annotation-wrong-param-type.scala new file mode 100644 index 000000000000..e4e4b3893334 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-type.scala @@ -0,0 +1,23 @@ +// Sample main method +object myProgram: + + /** Adds two numbers */ + @main def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("add") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("2", "true")) + callMain(Array("2.1", "3")) + callMain(Array("2", "3.1415921535")) + callMain(Array("192.168.1.1", "3")) + callMain(Array("false", "true")) + callMain(Array("Hello", "world!")) +end Test From 83869113857c0a301ab9c7f858c4914a35ffda53 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Apr 2022 13:12:31 +0200 Subject: [PATCH 2/2] Update implementation of newMain annotation --- .../dotty/tools/dotc/ast/MainProxies.scala | 22 +- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/typer/Checking.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- library/src/scala/annotation/newMain.scala | 388 ++++++++++++++++++ library/src/scala/main.scala | 338 +-------------- tests/neg/main-annotation-currying.scala | 4 +- tests/neg/main-annotation-generic.scala | 4 +- .../neg/main-annotation-implicit-given.scala | 6 +- .../neg/main-annotation-mainannotation.scala | 2 +- .../neg/main-annotation-multiple-annot.scala | 4 +- tests/neg/main-annotation-nonmethod.scala | 8 +- tests/neg/main-annotation-nonstatic.scala | 4 +- .../main-annotation-unknown-parser-1.scala | 4 +- .../main-annotation-unknown-parser-2.scala | 3 +- .../stdlibExperimentalDefinitions.scala | 7 + tests/run/main-annotation-birthday.scala | 4 +- tests/run/main-annotation-dash-dash.check | 17 + tests/run/main-annotation-dash-dash.scala | 25 ++ .../run/main-annotation-default-value-1.scala | 6 +- .../run/main-annotation-default-value-2.scala | 6 +- tests/run/main-annotation-flags.check | 16 + tests/run/main-annotation-flags.scala | 41 ++ tests/run/main-annotation-help-override.check | 36 +- tests/run/main-annotation-help-override.scala | 24 +- tests/run/main-annotation-help.scala | 36 +- .../main-annotation-homemade-annot-1.scala | 2 +- .../main-annotation-homemade-annot-3.scala | 1 - .../main-annotation-homemade-parser-1.scala | 3 +- .../main-annotation-homemade-parser-2.scala | 3 +- .../main-annotation-homemade-parser-3.scala | 3 +- .../main-annotation-homemade-parser-4.scala | 5 +- .../main-annotation-homemade-parser-5.scala | 3 +- tests/run/main-annotation-multiple.scala | 6 +- tests/run/main-annotation-named-params.scala | 6 +- ...n-annotation-no-parameters-no-parens.scala | 4 +- tests/run/main-annotation-no-parameters.scala | 4 +- tests/run/main-annotation-overload.scala | 4 +- tests/run/main-annotation-param-annot-1.scala | 43 +- tests/run/main-annotation-param-annot-2.scala | 29 +- ...nnotation-param-annot-invalid-params.scala | 17 +- tests/run/main-annotation-return-type-1.scala | 4 +- tests/run/main-annotation-return-type-2.scala | 4 +- tests/run/main-annotation-short-name.check | 6 +- tests/run/main-annotation-short-name.scala | 4 +- tests/run/main-annotation-simple.scala | 4 +- tests/run/main-annotation-top-level.scala | 6 +- tests/run/main-annotation-types.check | 6 +- tests/run/main-annotation-types.scala | 14 +- tests/run/main-annotation-vararg-1.scala | 4 +- tests/run/main-annotation-vararg-2.scala | 4 +- tests/run/main-annotation-wrong-param-1.check | 5 - tests/run/main-annotation-wrong-param-1.scala | 4 +- .../main-annotation-wrong-param-names.check | 11 +- .../main-annotation-wrong-param-names.scala | 4 +- .../main-annotation-wrong-param-number.scala | 4 +- .../main-annotation-wrong-param-type.check | 22 +- .../main-annotation-wrong-param-type.scala | 4 +- 58 files changed, 748 insertions(+), 506 deletions(-) create mode 100644 library/src/scala/annotation/newMain.scala create mode 100644 tests/run/main-annotation-dash-dash.check create mode 100644 tests/run/main-annotation-dash-dash.scala create mode 100644 tests/run/main-annotation-flags.check create mode 100644 tests/run/main-annotation-flags.scala diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index d0e2fcf021d3..040582476e96 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -11,6 +11,12 @@ import NameKinds.DefaultGetterName import Annotations.Annotation object MainProxies { + + /** Generate proxy classes for @main functions and @myMain functions where myMain <:< MainAnnotation */ + def proxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + mainAnnotationProxies(stats) ++ mainProxies(stats) + } + /** Generate proxy classes for @main functions. * A function like * @@ -29,7 +35,7 @@ object MainProxies { * catch case err: ParseError => showError(err) * } */ - def mainProxiesOld(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + private def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => @@ -39,11 +45,11 @@ object MainProxies { case _ => Nil } - mainMethods(stats).flatMap(mainProxyOld) + mainMethods(stats).flatMap(mainProxy) } import untpd._ - def mainProxyOld(mainFun: Symbol)(using Context): List[TypeDef] = { + private def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span def pos = mainFun.sourcePos val argsRef = Ident(nme.args) @@ -165,7 +171,7 @@ object MainProxies { * } * } */ - def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + private def mainAnnotationProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ /** @@ -188,12 +194,12 @@ object MainProxies { def mainMethods(scope: Tree, stats: List[Tree]): List[(Symbol, ParameterAnnotationss, DefaultValueSymbols, Option[Comment])] = stats.flatMap { case stat: DefDef => val sym = stat.symbol - sym.annotations.filter(_.matches(defn.MainAnnot)) match { + sym.annotations.filter(_.matches(defn.MainAnnotationClass)) match { case Nil => Nil case _ :: Nil => val paramAnnotations = stat.paramss.flatMap(_.map( - valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotParameterAnnotation)) + valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotationParameterAnnotation)) )) (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil case mainAnnot :: others => @@ -207,7 +213,7 @@ object MainProxies { } // Assuming that the top-level object was already generated, all main methods will have a scope - mainMethods(EmptyTree, stats).flatMap(mainProxy) + mainMethods(EmptyTree, stats).flatMap(mainAnnotationProxy) } private def mainAnnotationProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): Option[TypeDef] = { @@ -361,7 +367,7 @@ object MainProxies { case tree => super.transform(tree) } val annots = mainFun.annotations - .filterNot(_.matches(defn.MainAnnot)) + .filterNot(_.matches(defn.MainAnnotationClass)) .map(annot => insertTypeSplices.transform(annot.tree)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) .withFlags(JavaStatic) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d51136c398b0..f80b7970b324 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -954,6 +954,7 @@ class Definitions { @tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam") @tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam") @tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween") + @tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main") @tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration") @tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn") @tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait") diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 03562adfe0ad..acbd4f65acc7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1382,9 +1382,10 @@ trait Checking { /** check that annotation `annot` is applicable to symbol `sym` */ def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = !ctx.reporter.reportsErrorsFor { + val annotCls = Annotations.annotClass(annot) val concreteAnnot = Annotations.ConcreteAnnotation(annot) val pos = annot.srcPos - if (concreteAnnot.matches(defn.MainAnnot)) { + if (annotCls == defn.MainAnnot || concreteAnnot.matches(defn.MainAnnotationClass)) { if (!sym.isRealMethod) report.error(em"main annotation cannot be applied to $sym", pos) if (!sym.owner.is(Module) || !sym.owner.isStatic) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 38c9d2bf77e0..34eb2b7df41f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2626,7 +2626,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted() var stats1 = typedStats(tree.stats, pkg.moduleClass)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))._1 + stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) } case _ => diff --git a/library/src/scala/annotation/newMain.scala b/library/src/scala/annotation/newMain.scala new file mode 100644 index 000000000000..b59748af60b2 --- /dev/null +++ b/library/src/scala/annotation/newMain.scala @@ -0,0 +1,388 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.annotation + +import scala.collection.mutable +import scala.util.CommandLineParser.FromString +import scala.annotation.meta.param + +/** + * The annotation that designates a main function. + * Main functions are entry points for Scala programs. They can be called through a command line interface by using + * the `scala` command, followed by their name and, optionally, their parameters. + * + * The parameters of a main function may have any type `T`, as long as there exists a + * `given util.CommandLineParser.FromString[T]` in the scope. It will be used for parsing the string given as input + * into the correct argument type. + * These types already have parsers defined: + * - String, + * - Boolean, + * - Byte, Short, Int, Long, Float, Double. + * + * The parameters of a main function may be passed either by position, or by name. Passing an argument positionally + * means that you give the arguments in the same order as the function's signature. Passing an argument by name means + * that you give the argument right after giving its name. Considering the function + * `@newMain def foo(i: Int, str: String)`, we may have arguments passed: + * - by position: `scala foo 1 abc`, + * - by name: `scala foo -i 1 --str abc` or `scala foo --str abc -i 1`. + * + * A mixture of both is also possible: `scala foo --str abc 1` is equivalent to all previous examples. + * + * Note that main function overloading is not currently supported, i.e. you cannot define two main methods that have + * the same name in the same project. + * + * Special arguments are used to display help regarding a main function: `--help` and `-h`. If used as argument, the program + * will display some useful information about the main function. This help directly uses the ScalaDoc comment + * associated with the function, more precisely its description and the description of the parameters documented with + * `@param`. Note that if a parameter is named `help` or `h`, or if one of the parameters has as alias one of those names, + * the help displaying will be disabled for that argument. + * For example, for `@newMain def foo(help: Boolean)`, `scala foo -h` will display the help, but `scala foo --help` will fail, + * as it will expect a Boolean value after `--help`. + * + * Parameters may be given annotations to add functionalities to the main function: + * - `main.alias` adds other names to a parameter. For example, if a parameter `node` has as aliases + * `otherNode` and `n`, it may be addressed using `--node`, `--otherNode` or `-n`. + * + * Here is an example of a main function with annotated parameters: + * `@newMain def foo(@newMain.alias("x") number: Int, @newMain.alias("explanation") s: String)`. The following commands are + * equivalent: + * - `scala foo --number 1 -s abc` + * - `scala foo -x 1 -s abc` + * - `scala foo --number 1 --explanation abc` + * - `scala foo -x 1 --explanation abc` + * + * Boolean parameters are considered flags that do not require the "true" or "false" value to be passed. + * For example, `@newMain def foo(i: Boolean)` can be called as `foo` (where `i=false`) or `foo -i` (where `i=true`). + * + * The special `--` marker can be used to indicate that all following arguments are passed verbatim as positional parameters. + * For example, `@newMain def foo(args: String*)` can be called as `scala foo a b -- -c -d` which implies that `args=Seq("a", "b", "-c", "-d")`. + */ +@experimental +final class newMain extends MainAnnotation[FromString, Any]: + import newMain._ + import MainAnnotation._ + + private val longArgRegex = "--[a-zA-Z][a-zA-Z0-9]+".r + private val shortArgRegex = "-[a-zA-Z]".r + // TODO: what should be considered as an invalid argument? + // Consider argument `-3.14`, `--i`, `-name` + private val illFormedName = "--[a-zA-Z]|-[a-zA-Z][a-zA-Z0-9]+".r + /** After this marker, all arguments are positional */ + private inline val positionArgsMarker = "--" + + extension (param: Parameter) + private def aliasNames: Seq[String] = + param.annotations.collect{ case alias: alias => getNameWithMarker(alias.name) } + private def isFlag: Boolean = + param.typeName == "scala.Boolean" + + private def getNameWithMarker(name: String): String = + if name.length > 1 then s"--$name" + else if name.length == 1 then s"-$name" + else assert(false, "invalid name") + + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + val names = Names(info) + if Help.shouldPrintDefaultHelp(names, args) then + Help.printUsage(info) + Help.printExplain(info) + None + else + preProcessArgs(info, names, args).orElse { + Help.printUsage(info) + None + } + end command + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = { + if arg.nonEmpty then parse[T](param, arg) + else + assert(param.hasDefault) + + defaultArgument.get + } + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = { + val getters = args.map(arg => parse[T](param, arg)) + () => getters.map(_()) + } + + def run(execProgram: () => Any): Unit = + if !hasParseErrors then execProgram() + + private def preProcessArgs(info: Info, names: Names, args: Seq[String]): Option[Seq[String]] = + var hasError: Boolean = false + def error(msg: String): Unit = { + hasError = true + println(s"Error: $msg") + } + + val (positionalArgs, byNameArgsMap) = + val positionalArgs = List.newBuilder[String] + val byNameArgs = List.newBuilder[(String, String)] + val flagsAdded = mutable.Set.empty[String] + // TODO: once we settle on a spec, we should implement this in a more elegant way + var i = 0 + while i < args.length do + args(i) match + case name @ (longArgRegex() | shortArgRegex()) => + if names.isFlagName(name) then + val canonicalName = names.canonicalName(name).get + flagsAdded += canonicalName + byNameArgs += ((canonicalName, "true")) + else if i == args.length - 1 then // last argument -x ot --xyz + error(s"missing argument for ${name}") + else args(i + 1) match + case longArgRegex() | shortArgRegex() | `positionArgsMarker` => + error(s"missing argument for ${name}") + case value => + names.canonicalName(name) match + case Some(canonicalName) => + byNameArgs += ((canonicalName, value)) + case None => + error(s"unknown argument name: $name") + i += 1 // consume `value` + case name @ illFormedName() => + error(s"ill-formed argument name: $name") + case `positionArgsMarker` => + i += 1 // skip `--` + // all args after `--` are positional args + while i < args.length do + positionalArgs += args(i) + i += 1 + case value => + positionalArgs += value + i += 1 + end while + + // Add "false" for all flags not present in the arguments + for + param <- info.parameters + if param.isFlag + name = getNameWithMarker(param.name) + if !flagsAdded.contains(name) + do + byNameArgs += ((name, "false")) + + (positionalArgs.result(), byNameArgs.result().groupMap(_._1)(_._2)) + + // List of arguments in the order they should be passed to the main function + val orderedArgs: List[String] = + def rec(params: List[Parameter], acc: List[String], remainingArgs: List[String]): List[String] = + params match + case Nil => + for (remainingArg <- remainingArgs) error(s"unused argument: $remainingArg") + acc.reverse + case param :: tailParams => + if param.isVarargs then // also last arguments + byNameArgsMap.get(param.name) match + case Some(byNameVarargs) => acc.reverse ::: byNameVarargs.toList ::: remainingArgs + case None => acc.reverse ::: remainingArgs + else byNameArgsMap.get(getNameWithMarker(param.name)) match + case Some(argValues) => + assert(argValues.nonEmpty, s"${param.name} present in byNameArgsMap, but it has no argument value") + if argValues.length > 1 then + error(s"more than one value for ${param.name}: ${argValues.mkString(", ")}") + rec(tailParams, argValues.last :: acc, remainingArgs) + + case None => + remainingArgs match + case arg :: rest => + rec(tailParams, arg :: acc, rest) + case Nil => + if !param.hasDefault then + error(s"missing argument for ${param.name}") + rec(tailParams, "" :: acc, Nil) + rec(info.parameters.toList, Nil, positionalArgs) + + if hasError then None + else Some(orderedArgs) + end preProcessArgs + + private var hasParseErrors: Boolean = false + + private def parse[T](param: Parameter, arg: String)(using p: FromString[T]): () => T = + p.fromStringOption(arg) match + case Some(t) => + () => t + case None => + /** Issue an error, and return an uncallable getter */ + println(s"Error: could not parse argument for `${param.name}` of type ${param.typeName.split('.').last}: $arg") + hasParseErrors = true + () => throw new AssertionError("trying to get invalid argument") + + + private object Help: + + /** The name of the special argument to display the method's help. + * If one of the method's parameters is called the same, will be ignored. + */ + private inline val helpArg = "--help" + + /** The short name of the special argument to display the method's help. + * If one of the method's parameters uses the same short name, will be ignored. + */ + private inline val shortHelpArg = "-h" + + private inline val maxUsageLineLength = 120 + + def printUsage(info: Info): Unit = + def argsUsage: Seq[String] = + for (param <- info.parameters) + yield { + val canonicalName = getNameWithMarker(param.name) + val namesPrint = (canonicalName +: param.aliasNames).mkString("[", " | ", "]") + val shortTypeName = param.typeName.split('.').last + if param.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]" + else if param.hasDefault then s"[$namesPrint <$shortTypeName>]" + else if param.isFlag then s"$namesPrint" + else s"$namesPrint <$shortTypeName>" + } + + def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { + def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = + (args, currentLine) match { + case (Nil, "") => acc + case (Nil, l) => (acc :+ l) + case (arg +: t, "") => recurse(t, arg, acc) + case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) + case (arg +: t, l) => recurse(t, arg, acc :+ l) + } + + recurse(argsUsage, "", Vector()).toList + } + + val printUsageBeginning = s"Usage: ${info.name} " + val argsOffset = printUsageBeginning.length + val printUsages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) + + println(printUsageBeginning + printUsages.mkString("\n" + " " * argsOffset)) + end printUsage + + def printExplain(info: Info): Unit = + def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + + def wrapLongLine(line: String, maxLength: Int): List[String] = { + def recurse(s: String, acc: Vector[String]): Seq[String] = + val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) + if ((s.length <= maxLength) || (lastSpace < 0)) + acc :+ s + else { + val (shortLine, rest) = s.splitAt(lastSpace) + recurse(rest.trim.nn, acc :+ shortLine) + } + + recurse(line, Vector()).toList + } + + println() + + if (info.documentation.nonEmpty) + println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) + if (info.parameters.nonEmpty) { + val argNameShift = 2 + val argDocShift = argNameShift + 2 + + println("Arguments:") + for param <- info.parameters do + val canonicalName = getNameWithMarker(param.name) + val otherNames = param.aliasNames match { + case Seq() => "" + case names => names.mkString("(", ", ", ") ") + } + val argDoc = StringBuilder(" " * argNameShift) + argDoc.append(s"$canonicalName $otherNames- ${param.typeName.split('.').last}") + if param.isVarargs then argDoc.append(" (vararg)") + else if param.hasDefault then argDoc.append(" (optional)") + + if (param.documentation.nonEmpty) { + val shiftedDoc = + param.documentation.split("\n").nn + .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) + .mkString("\n") + argDoc.append("\n").append(shiftedDoc) + } + + println(argDoc) + } + end printExplain + + def shouldPrintDefaultHelp(names: Names, args: Seq[String]): Boolean = + val helpIsOverridden = names.canonicalName(helpArg).isDefined + val shortHelpIsOverridden = names.canonicalName(shortHelpArg).isDefined + (!helpIsOverridden && args.contains(helpArg)) || + (!shortHelpIsOverridden && args.contains(shortHelpArg)) + + end Help + + private class Names(info: Info): + + checkNames() + checkFlags() + + private lazy val namesToCanonicalName: Map[String, String] = + info.parameters.flatMap(param => + val canonicalName = getNameWithMarker(param.name) + (canonicalName -> canonicalName) +: param.aliasNames.map(_ -> canonicalName) + ).toMap + + private lazy val canonicalFlagsNames: Set[String] = + info.parameters.collect { + case param if param.isFlag => getNameWithMarker(param.name) + }.toSet + + def canonicalName(name: String): Option[String] = namesToCanonicalName.get(name) + + def isFlagName(name: String): Boolean = + namesToCanonicalName.get(name).map(canonicalFlagsNames.contains).contains(true) + + override def toString(): String = s"Names($namesToCanonicalName)" + + private def checkNames(): Unit = + def checkDuplicateNames() = + val nameAndCanonicalName = info.parameters.flatMap { paramInfo => + (getNameWithMarker(paramInfo.name) +: paramInfo.aliasNames).map(_ -> paramInfo.name) + } + val nameToNames = nameAndCanonicalName.groupMap(_._1)(_._2) + for (name, canonicalNames) <- nameToNames if canonicalNames.length > 1 do + throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + def checkValidNames() = + def isValidArgName(name: String): Boolean = + longArgRegex.matches(s"--$name") || shortArgRegex.matches(s"-$name") + for param <- info.parameters do + if !isValidArgName(param.name) then + throw IllegalArgumentException(s"The following argument name is invalid: ${param.name}") + for annot <- param.annotations do + annot match + case alias: alias if !isValidArgName(alias.name) => + throw IllegalArgumentException(s"The following alias is invalid: ${alias.name}") + case _ => + + checkValidNames() + checkDuplicateNames() + + private def checkFlags(): Unit = + for param <- info.parameters if param.isFlag && param.hasDefault do + throw IllegalArgumentException(s"@newMain flag parameters cannot have a default value. `${param.name}` has a default value.") + + end Names + +end newMain + +object newMain: + + /** Alias name for the parameter. + * + * If the name has one character, then it is a short name (e.g. `-i`). + * If the name has more than one characters, then it is a long name (e.g. `--input`). + */ + @experimental + final class alias(val name: String) extends MainAnnotation.ParameterAnnotation + +end newMain diff --git a/library/src/scala/main.scala b/library/src/scala/main.scala index 0556a4ddec0f..057fdad4c2fb 100644 --- a/library/src/scala/main.scala +++ b/library/src/scala/main.scala @@ -8,338 +8,6 @@ package scala -import collection.mutable -import annotation._ - -/** - * The annotation that designates a main function. - * Main functions are entry points for Scala programs. They can be called through a command line interface by using - * the `scala` command, followed by their name and, optionally, their parameters. - * - * The parameters of a main function may have any type `T`, as long as there exists a - * `given util.CommandLineParser.FromString[T]` in the scope. It will be used for parsing the string given as input - * into the correct argument type. - * These types already have parsers defined: - * - String, - * - Boolean, - * - Byte, Short, Int, Long, Float, Double. - * - * The parameters of a main function may be passed either by position, or by name. Passing an argument positionaly - * means that you give the arguments in the same order as the function's signature. Passing an argument by name means - * that you give the argument right after giving its name. Considering the function - * `@main def foo(i: Int, str: String)`, we may have arguments passed: - * - by position: `scala foo 1 abc`, - * - by name: `scala foo -i 1 --str abc` or `scala foo --str abc -i 1`. - * - * A mixture of both is also possible: `scala foo --str abc 1` is equivalent to all previous examples. - * - * Note that main function overloading is not currently supported, i.e. you cannot define two main methods that have - * the same name in the same project. - * - * Special arguments are used to display help regarding a main function: `--help` and `-h`. If used as argument, the program - * will display some useful information about the main function. This help directly uses the ScalaDoc comment - * associated with the function, more precisely its description and the description of the parameters documented with - * `@param`. Note that if a parameter is named `help` or `h`, or if one of the parameters has as alias one of those names, - * the help displaying will be disabled for that argument. - * For example, for `@main def foo(help: Boolean)`, `scala foo -h` will display the help, but `scala foo --help` will fail, - * as it will expect a Boolean value after `--help`. - * - * Parameters may be given annotations to add functionalities to the main function: - * - `main.Alias` adds other names to a parameter. For example, if a parameter `node` has as aliases - * `otherNode` and `n`, it may be addressed using `--node`, `--otherNode` or `-n`. - * - * Here is an example of a main function with annotated parameters: - * `@main def foo(@main.Alias("x") number: Int, @main.Alias("explanation") s: String)`. The following commands are - * equivalent: - * - `scala foo --number 1 -s abc` - * - `scala foo -x 1 -s abc` - * - `scala foo --number 1 --explanation abc` - * - `scala foo -x 1 --explanation abc` - */ -final class main extends MainAnnotation: - import main._ - import MainAnnotation._ - - override type ArgumentParser[T] = util.CommandLineParser.FromString[T] - override type MainResultType = Any - - override def command(args: Array[String], commandName: String, documentation: String, parameterInfoss: ParameterInfos*) = - new Command[ArgumentParser, MainResultType]: - private enum ArgumentKind { - case SimpleArgument, OptionalArgument, VarArgument - } - - private val argMarker = "--" - private val shortArgMarker = "-" - - /** - * The name of the special argument to display the method's help. - * If one of the method's parameters is called the same, will be ignored. - */ - private val helpArg = "help" - private var helpIsOverridden = false - - /** - * The short name of the special argument to display the method's help. - * If one of the method's parameters uses the same short name, will be ignored. - */ - private val shortHelpArg = 'h' - private var shortHelpIsOverridden = false - - private val maxUsageLineLength = 120 - - /** A map from argument canonical name (the name of the parameter in the method definition) to parameter informations */ - private val nameToParameterInfos: Map[String, ParameterInfos] = parameterInfoss.map(infos => infos.name -> infos).toMap - - private val (positionalArgs, byNameArgs, invalidByNameArgs) = { - val namesToCanonicalName: Map[String, String] = parameterInfoss.flatMap( - infos => - var names = getAlternativeNames(infos) - val canonicalName = infos.name - if nameIsValid(canonicalName) then names = canonicalName +: names - names.map(_ -> canonicalName) - ).toMap - val shortNamesToCanonicalName: Map[Char, String] = parameterInfoss.flatMap( - infos => - var names = getShortNames(infos) - val canonicalName = infos.name - if shortNameIsValid(canonicalName) then names = canonicalName(0) +: names - names.map(_ -> canonicalName) - ).toMap - - helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) - shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) - - def getCanonicalArgName(arg: String): Option[String] = - if arg.startsWith(argMarker) && arg.length > argMarker.length then - namesToCanonicalName.get(arg.drop(argMarker.length)) - else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then - shortNamesToCanonicalName.get(arg(shortArgMarker.length)) - else - None - - def isArgName(arg: String): Boolean = - val isFullName = arg.startsWith(argMarker) - val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length)) - isFullName || isShortName - - def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = - remainingArgs match { - case Seq() => - (pa, bna, ia) - case argName +: argValue +: rest if isArgName(argName) => - getCanonicalArgName(argName) match { - case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) - case None => recurse(rest, pa, bna, ia :+ argName) - } - case arg +: rest => - recurse(rest, pa :+ arg, bna, ia) - } - - val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) - val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) - (pa, nameToArgValues, ia) - } - - /** The kind of the arguments. Used to display help about the main method. */ - private val argKinds = new mutable.ArrayBuffer[ArgumentKind] - - /** A buffer for all errors */ - private val errors = new mutable.ArrayBuffer[String] - - /** Issue an error, and return an uncallable getter */ - private def error(msg: String): () => Nothing = - errors += msg - () => throw new AssertionError("trying to get invalid argument") - - private inline def nameIsValid(name: String): Boolean = - name.length > 1 // TODO add more checks for illegal characters - - private inline def shortNameIsValid(name: String): Boolean = - name.length == 1 && shortNameIsValid(name(0)) - - private inline def shortNameIsValid(shortName: Char): Boolean = - ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') - - private def getNameWithMarker(name: String | Char): String = name match { - case c: Char => shortArgMarker + c - case s: String if shortNameIsValid(s) => shortArgMarker + s - case s => argMarker + s - } - - private def convert[T](argName: String, arg: String, p: ArgumentParser[T]): () => T = - p.fromStringOption(arg) match - case Some(t) => () => t - case None => error(s"invalid argument for $argName: $arg") - - private def usage(): Unit = - def argsUsage: Seq[String] = - for ((infos, kind) <- parameterInfoss.zip(argKinds)) - yield { - val canonicalName = getNameWithMarker(infos.name) - val shortNames = getShortNames(infos).map(getNameWithMarker) - val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) - val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") - - kind match { - case ArgumentKind.SimpleArgument => s"$namesPrint <${infos.typeName}>" - case ArgumentKind.OptionalArgument => s"[$namesPrint <${infos.typeName}>]" - case ArgumentKind.VarArgument => s"[<${infos.typeName}> [<${infos.typeName}> [...]]]" - } - } - - def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { - def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = - (args, currentLine) match { - case (Nil, "") => acc - case (Nil, l) => (acc :+ l) - case (arg +: t, "") => recurse(t, arg, acc) - case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) - case (arg +: t, l) => recurse(t, arg, acc :+ l) - } - - recurse(argsUsage, "", Vector()).toList - } - - val usageBeginning = s"Usage: $commandName " - val argsOffset = usageBeginning.length - val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) - - println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) - end usage - - private def explain(): Unit = - inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") - - def wrapLongLine(line: String, maxLength: Int): List[String] = { - def recurse(s: String, acc: Vector[String]): Seq[String] = - val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) - if ((s.length <= maxLength) || (lastSpace < 0)) - acc :+ s - else { - val (shortLine, rest) = s.splitAt(lastSpace) - recurse(rest.trim.nn, acc :+ shortLine) - } - - recurse(line, Vector()).toList - } - - if (documentation.nonEmpty) - println(wrapLongLine(documentation, maxUsageLineLength).mkString("\n")) - if (nameToParameterInfos.nonEmpty) { - val argNameShift = 2 - val argDocShift = argNameShift + 2 - - println("Arguments:") - for ((infos, kind) <- parameterInfoss.zip(argKinds)) - val canonicalName = getNameWithMarker(infos.name) - val shortNames = getShortNames(infos).map(getNameWithMarker) - val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) - val otherNames = (alternativeNames ++: shortNames) match { - case Seq() => "" - case names => names.mkString("(", ", ", ") ") - } - val argDoc = StringBuilder(" " * argNameShift) - argDoc.append(s"$canonicalName $otherNames- ${infos.typeName}") - - kind match { - case ArgumentKind.OptionalArgument => argDoc.append(" (optional)") - case ArgumentKind.VarArgument => argDoc.append(" (vararg)") - case _ => - } - - infos.documentation.foreach( - doc => if (doc.nonEmpty) { - val shiftedDoc = - doc.split("\n").nn - .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) - .mkString("\n") - argDoc.append("\n").append(shiftedDoc) - } - ) - - println(argDoc) - } - end explain - - private def getAliases(paramInfos: ParameterInfos): Seq[String] = - paramInfos.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) - - private def getAlternativeNames(paramInfos: ParameterInfos): Seq[String] = - getAliases(paramInfos).filter(nameIsValid(_)) - - private def getShortNames(paramInfos: ParameterInfos): Seq[Char] = - getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0)) - - private def getInvalidNames(paramInfos: ParameterInfos): Seq[String | Char] = - getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) - - override def argGetter[T](name: String, optDefaultGetter: Option[() => T])(using p: ArgumentParser[T]): () => T = - argKinds += (if optDefaultGetter.nonEmpty then ArgumentKind.OptionalArgument else ArgumentKind.SimpleArgument) - val parameterInfos = nameToParameterInfos(name) - - byNameArgs.get(name) match { - case Some(Nil) => - throw AssertionError(s"$name present in byNameArgs, but it has no argument value") - case Some(argValues) => - if argValues.length > 1 then - // Do not accept multiple values - // Remove this test to take last given argument - error(s"more than one value for $name: ${argValues.mkString(", ")}") - else - convert(name, argValues.last, p) - case None => - if positionalArgs.length > 0 then - convert(name, positionalArgs.dequeue, p) - else if optDefaultGetter.nonEmpty then - optDefaultGetter.get - else - error(s"missing argument for $name") - } - end argGetter - - override def varargGetter[T](name: String)(using p: ArgumentParser[T]): () => Seq[T] = - argKinds += ArgumentKind.VarArgument - - val byNameGetters = byNameArgs.getOrElse(name, Seq()).map(arg => convert(name, arg, p)) - val positionalGetters = positionalArgs.removeAll.map(arg => convert(name, arg, p)) - // First take arguments passed by name, then those passed by position - () => (byNameGetters ++ positionalGetters).map(_()) - - override def run(f: => MainResultType): Unit = - // Check aliases unicity - val nameAndCanonicalName = nameToParameterInfos.toList.flatMap { - case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName) - } - val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) - - for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 - do throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") - - // Check aliases validity - val problematicNames = nameToParameterInfos.toList.flatMap((_, infos) => getInvalidNames(infos)) - if problematicNames.length > 0 then throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") - - // Handle unused and invalid args - for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") - for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") - - val displayHelp = - (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) - - if displayHelp then - usage() - println() - explain() - else if errors.nonEmpty then - for msg <- errors do println(s"Error: $msg") - usage() - else - f - end run - end command -end main - -object main: - final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation -end main +/** An annotation that designates a main function + */ +class main extends scala.annotation.Annotation {} diff --git a/tests/neg/main-annotation-currying.scala b/tests/neg/main-annotation-currying.scala index 9fdae8dd0954..fa8e9593849c 100644 --- a/tests/neg/main-annotation-currying.scala +++ b/tests/neg/main-annotation-currying.scala @@ -1,6 +1,8 @@ +import scala.annotation.newMain + object myProgram: - @main def add(num: Int)(inc: Int): Unit = // error + @newMain def add(num: Int)(inc: Int): Unit = // error println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/neg/main-annotation-generic.scala b/tests/neg/main-annotation-generic.scala index a9d3a63c9aa3..6f951056f1b2 100644 --- a/tests/neg/main-annotation-generic.scala +++ b/tests/neg/main-annotation-generic.scala @@ -1,6 +1,8 @@ +import scala.annotation.newMain + object myProgram: - @main def nop[T](t: T): T = // error + @newMain def nop[T](t: T): T = // error t end myProgram diff --git a/tests/neg/main-annotation-implicit-given.scala b/tests/neg/main-annotation-implicit-given.scala index 3efd49a8e3cf..2a7d8202acf5 100644 --- a/tests/neg/main-annotation-implicit-given.scala +++ b/tests/neg/main-annotation-implicit-given.scala @@ -1,11 +1,13 @@ +import scala.annotation.newMain + object myProgram: implicit val x: Int = 2 given Int = 3 - @main def showImplicit(implicit num: Int): Unit = // error + @newMain def showImplicit(implicit num: Int): Unit = // error println(num) - @main def showUsing(using num: Int): Unit = // error + @newMain def showUsing(using num: Int): Unit = // error println(num) end myProgram diff --git a/tests/neg/main-annotation-mainannotation.scala b/tests/neg/main-annotation-mainannotation.scala index dd35c7f0d639..21e37d1779af 100644 --- a/tests/neg/main-annotation-mainannotation.scala +++ b/tests/neg/main-annotation-mainannotation.scala @@ -1,3 +1,3 @@ import scala.annotation.MainAnnotation -@MainAnnotation def f(i: Int, n: Int) = () // error \ No newline at end of file +@MainAnnotation def f(i: Int, n: Int) = () // error diff --git a/tests/neg/main-annotation-multiple-annot.scala b/tests/neg/main-annotation-multiple-annot.scala index 0c4dc310a8d6..faec8162e9c4 100644 --- a/tests/neg/main-annotation-multiple-annot.scala +++ b/tests/neg/main-annotation-multiple-annot.scala @@ -1,6 +1,8 @@ +import scala.annotation.newMain + object myProgram: - @main @main def add1(num: Int, inc: Int): Unit = // error + @newMain @newMain def add1(num: Int, inc: Int): Unit = // error println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/neg/main-annotation-nonmethod.scala b/tests/neg/main-annotation-nonmethod.scala index bd713ab39a8a..2e46098a9ac5 100644 --- a/tests/neg/main-annotation-nonmethod.scala +++ b/tests/neg/main-annotation-nonmethod.scala @@ -1,9 +1,11 @@ +import scala.annotation.newMain + object myProgram: - @main val n = 2 // error + @newMain val n = 2 // error - @main class A // error + @newMain class A // error - @main val f = ((s: String) => println(s)) // error + @newMain val f = ((s: String) => println(s)) // error end myProgram diff --git a/tests/neg/main-annotation-nonstatic.scala b/tests/neg/main-annotation-nonstatic.scala index fe49646e23ae..68d3ba2b3569 100644 --- a/tests/neg/main-annotation-nonstatic.scala +++ b/tests/neg/main-annotation-nonstatic.scala @@ -1,2 +1,4 @@ +import scala.annotation.newMain + class A: - @main def foo(bar: Int) = () // error \ No newline at end of file + @newMain def foo(bar: Int) = () // error diff --git a/tests/neg/main-annotation-unknown-parser-1.scala b/tests/neg/main-annotation-unknown-parser-1.scala index 686d530d17de..75ff2ceac444 100644 --- a/tests/neg/main-annotation-unknown-parser-1.scala +++ b/tests/neg/main-annotation-unknown-parser-1.scala @@ -1,10 +1,12 @@ +import scala.annotation.newMain + class MyNumber(val value: Int) { def +(other: MyNumber): MyNumber = MyNumber(value + other.value) } object myProgram: - @main def add(num: MyNumber, inc: MyNumber): Unit = // error + @newMain def add(num: MyNumber, inc: MyNumber): Unit = // error println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/neg/main-annotation-unknown-parser-2.scala b/tests/neg/main-annotation-unknown-parser-2.scala index e1df3e16ab9a..a5681c39419b 100644 --- a/tests/neg/main-annotation-unknown-parser-2.scala +++ b/tests/neg/main-annotation-unknown-parser-2.scala @@ -1,8 +1,9 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString object myProgram: - @main def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = // error + @newMain def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = // error val numV = Test.value(num) val incV = Test.value(inc) println(s"$numV + $incV = ${numV + incV}") diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 568bda04196b..e83c605b1b75 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -32,6 +32,13 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.MainAnnotation", "scala.annotation.MainAnnotation$", + //// New feature: prototype of new version of @main + // This will never be stabilized. When it is ready it should replace the old @main annotation (requires scala.annotation.MainAnnotation). + // Needs user feedback. + "scala.annotation.newMain", + "scala.annotation.newMain$", + "scala.annotation.newMain$.alias", + //// New APIs: Mirror // Can be stabilized in 3.3.0 or later. "scala.deriving.Mirror$.fromProductTyped", // This API is a bit convoluted. We may need some more feedback before we can stabilize it. diff --git a/tests/run/main-annotation-birthday.scala b/tests/run/main-annotation-birthday.scala index d38f145e7b1b..92aacb6ffe0b 100644 --- a/tests/run/main-annotation-birthday.scala +++ b/tests/run/main-annotation-birthday.scala @@ -1,3 +1,5 @@ +import scala.annotation.newMain + /** * Wishes a happy birthday to lucky people! * @@ -5,7 +7,7 @@ * @param name the name of the luckiest person! * @param others all the other lucky people */ -@main def happyBirthday(age: Int, name: String, others: String*) = +@newMain def happyBirthday(age: Int, name: String, others: String*) = val suffix = age % 100 match case 11 | 12 | 13 => "th" diff --git a/tests/run/main-annotation-dash-dash.check b/tests/run/main-annotation-dash-dash.check new file mode 100644 index 000000000000..3c33f4e31c9a --- /dev/null +++ b/tests/run/main-annotation-dash-dash.check @@ -0,0 +1,17 @@ +str = x +rest = y,z + +str = x +rest = y,z + +str = -a +rest = x,y,z + +str = x +rest = y,z + +str = y +rest = z + +Error: missing argument for --str +Usage: foo [--str] [ [ [...]]] diff --git a/tests/run/main-annotation-dash-dash.scala b/tests/run/main-annotation-dash-dash.scala new file mode 100644 index 000000000000..e33204ce51d8 --- /dev/null +++ b/tests/run/main-annotation-dash-dash.scala @@ -0,0 +1,25 @@ +import scala.annotation.newMain + +object myProgram: + + @newMain def foo(str: String, rest: String*): Unit = + println(s"str = $str") + println(s"rest = ${rest.mkString(",")}") + println() + +end myProgram + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("foo") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("x", "y", "z")) + callMain(Array("--", "x", "y", "z")) + callMain(Array("--", "-a", "x", "y", "z")) + callMain(Array("x", "--", "y", "z")) + callMain(Array("--str", "y", "--", "z")) + callMain(Array("--str", "--", "y", "z")) // missing argument for `--str` +end Test diff --git a/tests/run/main-annotation-default-value-1.scala b/tests/run/main-annotation-default-value-1.scala index 4bb712c86831..f0495a56bb7a 100644 --- a/tests/run/main-annotation-default-value-1.scala +++ b/tests/run/main-annotation-default-value-1.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int = 0, inc: Int = 1): Unit = + @newMain def add(num: Int = 0, inc: Int = 1): Unit = println(s"$num + $inc = ${num + inc}") end myProgram @@ -17,4 +19,4 @@ object Test: callMain(Array("2", "3")) callMain(Array("2")) callMain(Array()) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-default-value-2.scala b/tests/run/main-annotation-default-value-2.scala index 33282bdc27fa..dc5736117c57 100644 --- a/tests/run/main-annotation-default-value-2.scala +++ b/tests/run/main-annotation-default-value-2.scala @@ -1,7 +1,9 @@ +import scala.annotation.newMain + // Sample main method object myProgram: - @main def alwaysPassParam(forbiddenParam: Int = throw new IllegalStateException("This should not be evaluated!")): Unit = + @newMain def alwaysPassParam(forbiddenParam: Int = throw new IllegalStateException("This should not be evaluated!")): Unit = println(forbiddenParam) end myProgram @@ -28,4 +30,4 @@ object Test: catch { case e: Exception if hasCauseIllegalStateException(e) => println("OK") } -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-flags.check b/tests/run/main-annotation-flags.check new file mode 100644 index 000000000000..849744a4f821 --- /dev/null +++ b/tests/run/main-annotation-flags.check @@ -0,0 +1,16 @@ +shortFlags: a = false, b = false +shortFlags: a = true, b = false +shortFlags: a = true, b = true +Error: unused argument: true +Error: unused argument: false +Usage: shortFlags [-a] [-b] +Error: unused argument: true +Usage: shortFlags [-a] [-b] +Error: unused argument: true +Usage: shortFlags [-a] [-b] +longFlags: flag1 = false, flag2 = false +longFlags: flag1 = true, flag2 = false +longFlags: flag1 = true, flag2 = true +mixedFlags: a = false, flag = false +mixedFlags: a = true, flag = false +mixedFlags: a = true, flag = true diff --git a/tests/run/main-annotation-flags.scala b/tests/run/main-annotation-flags.scala new file mode 100644 index 000000000000..cd95e88cab48 --- /dev/null +++ b/tests/run/main-annotation-flags.scala @@ -0,0 +1,41 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + @newMain def shortFlags(a: Boolean, b: Boolean): Unit = + println(s"shortFlags: a = $a, b = $b") + + @newMain def longFlags(flag1: Boolean, flag2: Boolean): Unit = + println(s"longFlags: flag1 = $flag1, flag2 = $flag2") + + @newMain def mixedFlags(a: Boolean, flag: Boolean): Unit = + println(s"mixedFlags: a = $a, flag = $flag") + +end myProgram + +object Test: + def callMain(name: String, args: String*): Unit = + val clazz = Class.forName(name) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args.toArray) + + def main(args: Array[String]): Unit = + callMain("shortFlags") + callMain("shortFlags", "-a") + callMain("shortFlags", "-a", "-b") + callMain("shortFlags", "true", "false") + callMain("shortFlags", "-a", "true") + callMain("shortFlags", "-b", "true") + + + callMain("longFlags") + callMain("longFlags", "--flag1") + callMain("longFlags", "--flag1", "--flag2") + + callMain("mixedFlags") + callMain("mixedFlags", "-a") + callMain("mixedFlags", "-a", "--flag") + + +end Test diff --git a/tests/run/main-annotation-help-override.check b/tests/run/main-annotation-help-override.check index 921f6a185fcf..45c6122e28c7 100644 --- a/tests/run/main-annotation-help-override.check +++ b/tests/run/main-annotation-help-override.check @@ -4,28 +4,34 @@ Usage: helpOverride1 [--notHelp] A method that should let --help and -h display help. Arguments: --notHelp - Int -Error: invalid argument for help: --help +Error: missing argument for --help +Error: missing argument for help Usage: helpOverride2 [--help] Usage: helpOverride3 [-h] A method that should let --help display help, but not -h. Arguments: -h - Int -Error: invalid argument for help: --help +Error: missing argument for --help +Error: missing argument for help Error: missing argument for h Usage: helpOverride4 [--help] [-h] -Error: invalid argument for notHelp: --help +Error: missing argument for --help +Error: missing argument for notHelp Usage: helpOverride5 [--notHelp | --help] Usage: helpOverride6 [--notHelp | -h] A method that should let --help display help, but not -h. Arguments: --notHelp (-h) - Int -Error: invalid argument for notHelp: --help +Error: missing argument for --help +Error: missing argument for notHelp Error: missing argument for notH Usage: helpOverride7 [--notHelp | --help] [--notH | -h] -Error: invalid argument for notHelp: --help -Usage: helpOverride8 [--notHelp | --help | -h] +Error: missing argument for --help +Error: missing argument for notHelp +Usage: helpOverride8 [--notHelp | -h | --help] +helpOverride9: true ##### -h Usage: helpOverride1 [--notHelp] @@ -37,9 +43,11 @@ Usage: helpOverride2 [--help] A method that should let -h display help, but not --help. Arguments: --help - Int -Error: invalid argument for h: -h +Error: missing argument for -h +Error: missing argument for h Usage: helpOverride3 [-h] -Error: invalid argument for help: -h +Error: missing argument for -h +Error: missing argument for help Error: missing argument for h Usage: helpOverride4 [--help] [-h] Usage: helpOverride5 [--notHelp | --help] @@ -47,10 +55,14 @@ Usage: helpOverride5 [--notHelp | --help] A method that should let -h display help, but not --help. Arguments: --notHelp (--help) - Int -Error: invalid argument for notHelp: -h +Error: missing argument for -h +Error: missing argument for notHelp Usage: helpOverride6 [--notHelp | -h] -Error: invalid argument for notHelp: -h +Error: missing argument for -h +Error: missing argument for notHelp Error: missing argument for notH Usage: helpOverride7 [--notHelp | --help] [--notH | -h] -Error: invalid argument for notHelp: -h -Usage: helpOverride8 [--notHelp | --help | -h] +Error: missing argument for -h +Error: missing argument for notHelp +Usage: helpOverride8 [--notHelp | -h | --help] +helpOverride9: true diff --git a/tests/run/main-annotation-help-override.scala b/tests/run/main-annotation-help-override.scala index 18e3f038178f..40b793c7b839 100644 --- a/tests/run/main-annotation-help-override.scala +++ b/tests/run/main-annotation-help-override.scala @@ -1,31 +1,37 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias import scala.util.Try object myProgram: /** A method that should let --help and -h display help. */ - @main def helpOverride1(notHelp: Int) = ??? + @newMain def helpOverride1(notHelp: Int) = ??? /** A method that should let -h display help, but not --help. */ - @main def helpOverride2(help: Int) = ??? + @newMain def helpOverride2(help: Int) = ??? /** A method that should let --help display help, but not -h. */ - @main def helpOverride3(h: Int) = ??? + @newMain def helpOverride3(h: Int) = ??? /** A method that should not let --help and -h display help. */ - @main def helpOverride4(help: Int, h: Int) = ??? + @newMain def helpOverride4(help: Int, h: Int) = ??? /** A method that should let -h display help, but not --help. */ - @main def helpOverride5(@main.Alias("help") notHelp: Int) = ??? + @newMain def helpOverride5(@alias("help") notHelp: Int) = ??? /** A method that should let --help display help, but not -h. */ - @main def helpOverride6(@main.Alias("h") notHelp: Int) = ??? + @newMain def helpOverride6(@alias("h") notHelp: Int) = ??? /** A method that should not let --help and -h display help. */ - @main def helpOverride7(@main.Alias("help") notHelp: Int, @main.Alias("h") notH: Int) = ??? + @newMain def helpOverride7(@alias("help") notHelp: Int, @alias("h") notH: Int) = ??? /** A method that should not let --help and -h display help. */ - @main def helpOverride8(@main.Alias("help", "h") notHelp: Int) = ??? + @newMain def helpOverride8(@alias("help") @alias("h") notHelp: Int) = ??? + + /** A method that should not let --help and -h display help. */ + // Probably the correct way to override help flags. + @newMain def helpOverride9(@alias("h") help: Boolean) = println(s"helpOverride9: $help") end myProgram @@ -44,4 +50,4 @@ object Test: callAllMains(Array("--help")) println("##### -h") callAllMains(Array("-h")) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-help.scala b/tests/run/main-annotation-help.scala index cef057789f25..551bbf67d30b 100644 --- a/tests/run/main-annotation-help.scala +++ b/tests/run/main-annotation-help.scala @@ -1,3 +1,5 @@ +import scala.annotation.newMain + import scala.util.CommandLineParser.FromString import scala.util.Try @@ -17,15 +19,15 @@ object myProgram: /** * Adds two numbers. */ - @main def doc1(num: Int, inc: Int): Unit = () + @newMain def doc1(num: Int, inc: Int): Unit = () /** Adds two numbers. */ - @main def doc2(num: Int, inc: Int): Unit = () + @newMain def doc2(num: Int, inc: Int): Unit = () /** * Adds two numbers. */ - @main def doc3(num: Int, inc: Int): Unit = () + @newMain def doc3(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -33,14 +35,14 @@ object myProgram: * @param num the first number * @param inc the second number */ - @main def doc4(num: Int, inc: Int = 1): Unit = () + @newMain def doc4(num: Int, inc: Int = 1): Unit = () /** * Adds two numbers. * * @param num the first number */ - @main def doc5(num: Int, inc: Int): Unit = () + @newMain def doc5(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -48,7 +50,7 @@ object myProgram: * @param num * @param inc */ - @main def doc6(num: Int, inc: Int): Unit = () + @newMain def doc6(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -57,7 +59,7 @@ object myProgram: * @param inc the second number * @return the sum of the two numbers (not really) */ - @main def doc7(num: Int, inc: Int): Unit = () + @newMain def doc7(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -66,7 +68,7 @@ object myProgram: * @param inc the second number * @return the sum of the two numbers (not really) */ - @main def doc8(num: Int, inc: Int): Unit = () + @newMain def doc8(num: Int, inc: Int): Unit = () /** * Adds two numbers. Same as [[doc1]]. @@ -76,7 +78,7 @@ object myProgram: * @return the sum of the two numbers (not really) * @see [[doc1]] */ - @main def doc9(num: Int, inc: Int): Unit = () + @newMain def doc9(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -95,7 +97,7 @@ object myProgram: * have to write this one * on three lines */ - @main def doc10(num: Int, inc: Int): Unit = () + @newMain def doc10(num: Int, inc: Int): Unit = () /** * Adds two numbers. @@ -108,22 +110,22 @@ object myProgram: * * And another one! */ - @main def doc11(num: Int, inc: Int): Unit = () + @newMain def doc11(num: Int, inc: Int): Unit = () /** * Adds two numbers. It seems that I have a very long line of documentation and therefore might need to be cut at some point to fit a small terminal screen. */ - @main def doc12(num: Int, inc: Int): Unit = () + @newMain def doc12(num: Int, inc: Int): Unit = () /** * Addstwonumbers.ItseemsthatIhaveaverylonglineofdocumentationandthereforemightneedtobecutatsomepointtofitasmallterminalscreen. */ - @main def doc13(num: Int, inc: Int): Unit = () + @newMain def doc13(num: Int, inc: Int): Unit = () /** * Loudly judges the number of argument you gave to this poor function. */ - @main def doc14( + @newMain def doc14( arg1: String, arg2: Int, arg3: String, arg4: Int, arg5: String, arg6: Int, arg7: String, arg8: Int, arg9: String = "I", arg10: Int = 42, arg11: String = "used", arg12: Int = 0, @@ -135,17 +137,17 @@ object myProgram: * @param myNum my first number to add * @param myInc my second number to add */ - @main def doc15(myNum: MyNumber, myInc: MyNumber): Unit = () + @newMain def doc15(myNum: MyNumber, myInc: MyNumber): Unit = () /** * Compares two instances of [[MyGeneric]]. * @param first my first element * @param second my second element */ - @main def doc16(first: MyGeneric[Int], second: MyGeneric[Int]): Unit = () + @newMain def doc16(first: MyGeneric[Int], second: MyGeneric[Int]): Unit = () // This should not be printed in explain! - @main def doc17(a: Int, b: Int, c: String): Unit = () + @newMain def doc17(a: Int, b: Int, c: String): Unit = () end myProgram diff --git a/tests/run/main-annotation-homemade-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala index 4719622e3ab5..daf27b944d99 100644 --- a/tests/run/main-annotation-homemade-annot-1.scala +++ b/tests/run/main-annotation-homemade-annot-1.scala @@ -1,5 +1,5 @@ import scala.concurrent._ -import scala.annotation.MainAnnotation +import scala.annotation.* import scala.collection.mutable import ExecutionContext.Implicits.global import duration._ diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala index 602184be7177..3fc42abcce79 100644 --- a/tests/run/main-annotation-homemade-annot-3.scala +++ b/tests/run/main-annotation-homemade-annot-3.scala @@ -21,4 +21,3 @@ class mainNoArgs extends MainAnnotation[FromString, Any]: def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? def run(program: () => Any): Unit = program() - diff --git a/tests/run/main-annotation-homemade-parser-1.scala b/tests/run/main-annotation-homemade-parser-1.scala index c4d55e879c6d..b4d095dcceb2 100644 --- a/tests/run/main-annotation-homemade-parser-1.scala +++ b/tests/run/main-annotation-homemade-parser-1.scala @@ -1,3 +1,4 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString class MyNumber(val value: Int) { @@ -9,7 +10,7 @@ given FromString[MyNumber] with object myProgram: - @main def add(num: MyNumber, inc: MyNumber): Unit = + @newMain def add(num: MyNumber, inc: MyNumber): Unit = println(s"${num.value} + ${inc.value} = ${num.value + inc.value}") end myProgram diff --git a/tests/run/main-annotation-homemade-parser-2.scala b/tests/run/main-annotation-homemade-parser-2.scala index c79b598bcf86..d7aeaaac8762 100644 --- a/tests/run/main-annotation-homemade-parser-2.scala +++ b/tests/run/main-annotation-homemade-parser-2.scala @@ -1,3 +1,4 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString given FromString[Test.MyNumber] with @@ -5,7 +6,7 @@ given FromString[Test.MyNumber] with object myProgram: - @main def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = + @newMain def add(num: Test.MyNumber, inc: Test.MyNumber): Unit = val numV = Test.value(num) val incV = Test.value(inc) println(s"$numV + $incV = ${numV + incV}") diff --git a/tests/run/main-annotation-homemade-parser-3.scala b/tests/run/main-annotation-homemade-parser-3.scala index d74588c227f5..37ef32e6a0ee 100644 --- a/tests/run/main-annotation-homemade-parser-3.scala +++ b/tests/run/main-annotation-homemade-parser-3.scala @@ -1,3 +1,4 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString given FromString[Int] with @@ -8,7 +9,7 @@ object myProgram: given FromString[Int] with override def fromString(s: String) = -1 * s.toInt // Should be ignored, because not top-level - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/run/main-annotation-homemade-parser-4.scala b/tests/run/main-annotation-homemade-parser-4.scala index 0bc826d0258e..d7efc84ab424 100644 --- a/tests/run/main-annotation-homemade-parser-4.scala +++ b/tests/run/main-annotation-homemade-parser-4.scala @@ -1,3 +1,4 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString given [T : FromString]: FromString[Option[T]] with @@ -22,9 +23,9 @@ given [T : FromString]: FromString[Either[T, String]] with object myProgram: - @main def getOption(o: Option[Int] = Some(42)) = println(o) + @newMain def getOption(o: Option[Int] = Some(42)) = println(o) - @main def getEither(e: Either[Int, String] = Right("No argument given")) = println(e) + @newMain def getEither(e: Either[Int, String] = Right("No argument given")) = println(e) end myProgram diff --git a/tests/run/main-annotation-homemade-parser-5.scala b/tests/run/main-annotation-homemade-parser-5.scala index 4621716b537e..77823ac8cba0 100644 --- a/tests/run/main-annotation-homemade-parser-5.scala +++ b/tests/run/main-annotation-homemade-parser-5.scala @@ -1,3 +1,4 @@ +import scala.annotation.newMain import scala.util.CommandLineParser.FromString given intParser: FromString[Int => Int] with @@ -8,7 +9,7 @@ given stringParser: FromString[String => String] with object myProgram: - @main def show(getI: Int => Int, getS: String => String) = + @newMain def show(getI: Int => Int, getS: String => String) = println(getI(3)) println(getS(" world!")) diff --git a/tests/run/main-annotation-multiple.scala b/tests/run/main-annotation-multiple.scala index aebb2cb059dc..4177a97e98f3 100644 --- a/tests/run/main-annotation-multiple.scala +++ b/tests/run/main-annotation-multiple.scala @@ -1,12 +1,14 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") /** Subtracts two numbers */ - @main def sub(num: Int, inc: Int): Unit = + @newMain def sub(num: Int, inc: Int): Unit = println(s"$num - $inc = ${num - inc}") end myProgram diff --git a/tests/run/main-annotation-named-params.scala b/tests/run/main-annotation-named-params.scala index f4978bafe480..9fa6f6677890 100644 --- a/tests/run/main-annotation-named-params.scala +++ b/tests/run/main-annotation-named-params.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram @@ -22,4 +24,4 @@ object Test: callMain(Array("--num", "2", "--num", "1", "--inc", "3")) callMain(Array("--inc", "1", "--num", "2", "--num", "1", "--inc", "3")) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-no-parameters-no-parens.scala b/tests/run/main-annotation-no-parameters-no-parens.scala index 9ba6f150f21b..ca0e32877bc1 100644 --- a/tests/run/main-annotation-no-parameters-no-parens.scala +++ b/tests/run/main-annotation-no-parameters-no-parens.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Does nothing, except confirming that it runs */ - @main def run: Unit = + @newMain def run: Unit = println("I run properly!") end myProgram diff --git a/tests/run/main-annotation-no-parameters.scala b/tests/run/main-annotation-no-parameters.scala index 978ec6e6373e..eb30ac6148ca 100644 --- a/tests/run/main-annotation-no-parameters.scala +++ b/tests/run/main-annotation-no-parameters.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Does nothing, except confirming that it runs */ - @main def run(): Unit = + @newMain def run(): Unit = println("I run properly!") end myProgram diff --git a/tests/run/main-annotation-overload.scala b/tests/run/main-annotation-overload.scala index 5688edc1bfc2..85a06dc23a0c 100644 --- a/tests/run/main-annotation-overload.scala +++ b/tests/run/main-annotation-overload.scala @@ -1,3 +1,5 @@ +import scala.annotation.newMain + // Sample main method object myProgram: @@ -6,7 +8,7 @@ object myProgram: ??? /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") /** Adds one number (malformed, doesn't work) */ diff --git a/tests/run/main-annotation-param-annot-1.scala b/tests/run/main-annotation-param-annot-1.scala index 27125d052567..26b4b7f62a13 100644 --- a/tests/run/main-annotation-param-annot-1.scala +++ b/tests/run/main-annotation-param-annot-1.scala @@ -1,31 +1,34 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + object myProgram: - @main def altName1( - @main.Alias("myNum") num: Int, + @newMain def altName1( + @alias("myNum") num: Int, inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") - @main def altName2( - @main.Alias("myNum") num: Int, - @main.Alias("myInc") inc: Int + @newMain def altName2( + @alias("myNum") num: Int, + @alias("myInc") inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") - @main def shortName1( - @main.Alias("n") num: Int, + @newMain def shortName1( + @alias("n") num: Int, inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") - @main def shortName2( - @main.Alias("n") num: Int, - @main.Alias("i") inc: Int + @newMain def shortName2( + @alias("n") num: Int, + @alias("i") inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") - @main def mix1( - @main.Alias("myNum") @main.Alias("n") num: Int, - @main.Alias("i") @main.Alias("myInc") inc: Int + @newMain def mix1( + @alias("myNum") @alias("n") num: Int, + @alias("i") @alias("myInc") inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") @@ -40,15 +43,15 @@ object myProgram: def myInc = {new Exception("myInc")}.getMessage def myShortInc = () => "i" - @main def mix2( - @main.Alias(myNum) @main.Alias(myShortNum) num: Int, - @main.Alias(myShortInc()) @main.Alias(myInc) inc: Int + @newMain def mix2( + @alias(myNum) @alias(myShortNum) num: Int, + @alias(myShortInc()) @alias(myInc) inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") - @main def multiple( - @main.Alias("myNum", "n") num: Int, - @main.Alias("i", "myInc") inc: Int + @newMain def multiple( + @alias("myNum") @alias("n") num: Int, + @alias("i") @alias("myInc") inc: Int ): Unit = println(s"$num + $inc = ${num + inc}") end myProgram @@ -102,4 +105,4 @@ object Test: callMain("multiple", Array("-n", "2", "--myInc", "3")) callMain("multiple", Array("--myNum", "2", "-i", "3")) callMain("multiple", Array("-n", "2", "-i", "3")) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-param-annot-2.scala b/tests/run/main-annotation-param-annot-2.scala index 1c3699f2ab59..ce3b00fe8ed1 100644 --- a/tests/run/main-annotation-param-annot-2.scala +++ b/tests/run/main-annotation-param-annot-2.scala @@ -1,27 +1,30 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + object myProgram: - @main def multipleSameShortNames1( - @main.Alias("n") num: Int, - @main.Alias("n") inc: Int + @newMain def multipleSameShortNames1( + @alias("n") num: Int, + @alias("n") inc: Int ): Unit = () - @main def multipleSameShortNames2( - @main.Alias("n") @main.Alias("n") num: Int, + @newMain def multipleSameShortNames2( + @alias("n") @alias("n") num: Int, inc: Int ): Unit = () - @main def multipleSameNames1( - @main.Alias("arg") num: Int, - @main.Alias("arg") inc: Int + @newMain def multipleSameNames1( + @alias("arg") num: Int, + @alias("arg") inc: Int ): Unit = () - @main def multipleSameNames2( - @main.Alias("arg") @main.Alias("arg") num: Int, + @newMain def multipleSameNames2( + @alias("arg") @alias("arg") num: Int, inc: Int ): Unit = () - @main def multipleSameNames3( + @newMain def multipleSameNames3( num: Int, - @main.Alias("num") inc: Int + @alias("num") inc: Int ): Unit = () end myProgram @@ -54,4 +57,4 @@ object Test: callMain("multipleSameNames2", Array("--num", "2", "--inc", "3")) callMain("multipleSameNames2", Array("--arg", "2", "--inc", "3")) callMain("multipleSameNames3", Array("--num", "2", "--inc", "3")) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-param-annot-invalid-params.scala b/tests/run/main-annotation-param-annot-invalid-params.scala index 4cfa15705320..3eeb7b55a2ae 100644 --- a/tests/run/main-annotation-param-annot-invalid-params.scala +++ b/tests/run/main-annotation-param-annot-invalid-params.scala @@ -1,17 +1,20 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + import java.lang.reflect.InvocationTargetException object myProgram: - @main def empty( - @main.Alias("") i: Int, + @newMain def empty( + @alias("") i: Int, ): Unit = () - @main def space( - @main.Alias(" ") i: Int, + @newMain def space( + @alias(" ") i: Int, ): Unit = () - @main def nonLetter( - @main.Alias("1") i: Int, + @newMain def nonLetter( + @alias("1") i: Int, ): Unit = () end myProgram @@ -39,4 +42,4 @@ object Test: callMain("empty", Array("3")) callMain("space", Array("3")) callMain("nonLetter", Array("3")) -end Test \ No newline at end of file +end Test diff --git a/tests/run/main-annotation-return-type-1.scala b/tests/run/main-annotation-return-type-1.scala index 8ddc1a07539f..5dfdd76047bb 100644 --- a/tests/run/main-annotation-return-type-1.scala +++ b/tests/run/main-annotation-return-type-1.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers and returns them */ - @main def add(num: Int, inc: Int) = + @newMain def add(num: Int, inc: Int) = println(num + inc) end myProgram diff --git a/tests/run/main-annotation-return-type-2.scala b/tests/run/main-annotation-return-type-2.scala index dbf1503f38a1..7bb75da1a227 100644 --- a/tests/run/main-annotation-return-type-2.scala +++ b/tests/run/main-annotation-return-type-2.scala @@ -1,3 +1,5 @@ +import scala.annotation.newMain + class MyResult(val result: Int): override def toString: String = result.toString @@ -5,7 +7,7 @@ class MyResult(val result: Int): object myProgram: /** Adds two numbers and returns them */ - @main def add(num: Int, inc: Int) = + @newMain def add(num: Int, inc: Int) = println(MyResult(num + inc)) end myProgram diff --git a/tests/run/main-annotation-short-name.check b/tests/run/main-annotation-short-name.check index 930ac62b97ee..e02da36c1cb1 100644 --- a/tests/run/main-annotation-short-name.check +++ b/tests/run/main-annotation-short-name.check @@ -1,7 +1,5 @@ 2 + 3 = 5 2 + 3 = 5 -Error: missing argument for n -Error: missing argument for i -Error: unknown argument name: --n -Error: unknown argument name: --i +Error: ill-formed argument name: --n +Error: ill-formed argument name: --i Usage: add [-n] [-i] diff --git a/tests/run/main-annotation-short-name.scala b/tests/run/main-annotation-short-name.scala index 8efa51c55354..2123acb4f882 100644 --- a/tests/run/main-annotation-short-name.scala +++ b/tests/run/main-annotation-short-name.scala @@ -1,7 +1,9 @@ +import scala.annotation.newMain + object myProgram: /** Adds two numbers */ - @main def add(n: Int, i: Int): Unit = + @newMain def add(n: Int, i: Int): Unit = println(s"$n + $i = ${n + i}") end myProgram diff --git a/tests/run/main-annotation-simple.scala b/tests/run/main-annotation-simple.scala index 33eb4cbb0308..78f8927d924f 100644 --- a/tests/run/main-annotation-simple.scala +++ b/tests/run/main-annotation-simple.scala @@ -1,7 +1,9 @@ +import scala.annotation.newMain + object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/run/main-annotation-top-level.scala b/tests/run/main-annotation-top-level.scala index 932b74b690be..c3c1dd79042f 100644 --- a/tests/run/main-annotation-top-level.scala +++ b/tests/run/main-annotation-top-level.scala @@ -1,9 +1,11 @@ +import scala.annotation.newMain + /** Adds two numbers */ -@main def add(num: Int, inc: Int): Unit = +@newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") /** Adds any amount of numbers */ -@main def addAll(num: Int = 0, incs: Int*): Unit = +@newMain def addAll(num: Int = 0, incs: Int*): Unit = print(num) if (incs.length > 0) { print(" + ") diff --git a/tests/run/main-annotation-types.check b/tests/run/main-annotation-types.check index 0b7f741518c7..6ab941caa08c 100644 --- a/tests/run/main-annotation-types.check +++ b/tests/run/main-annotation-types.check @@ -2,17 +2,17 @@ Here's what I got: int - 2 double - 3.0 string - 4 -boolean - true +byte - 1 Here's what I got: int - -1 double - 3.4567890987654456E18 string - false -boolean - false +byte - 127 Here's what I got: int - 2147483647 double - 3.1415926535 string - Hello world! -boolean - true +byte - 0 diff --git a/tests/run/main-annotation-types.scala b/tests/run/main-annotation-types.scala index 8d4fa14761ab..ab0654c78a80 100644 --- a/tests/run/main-annotation-types.scala +++ b/tests/run/main-annotation-types.scala @@ -1,18 +1,20 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Displays some parameters */ - @main def show( + @newMain def show( int: Int, double: Double, string: String, - boolean: Boolean + byte: Byte ): Unit = println("Here's what I got:") println(s"int - $int") println(s"double - $double") println(s"string - $string") - println(s"boolean - $boolean") + println(s"byte - $byte") println() end myProgram @@ -24,7 +26,7 @@ object Test: method.invoke(null, args) def main(args: Array[String]): Unit = - callMain(Array("2", "3", "4", "true")) - callMain(Array("-1", "3456789098765445678", "false", "FALSE")) - callMain(Array("2147483647", "3.1415926535", "Hello world!", "True")) + callMain(Array("2", "3", "4", "1")) + callMain(Array("-1", "3456789098765445678", "false", "127")) + callMain(Array("2147483647", "3.1415926535", "Hello world!", "0")) end Test diff --git a/tests/run/main-annotation-vararg-1.scala b/tests/run/main-annotation-vararg-1.scala index bc8f84a3c6a9..5c488940fa14 100644 --- a/tests/run/main-annotation-vararg-1.scala +++ b/tests/run/main-annotation-vararg-1.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds any amount of numbers */ - @main def add(nums: Int*): Unit = + @newMain def add(nums: Int*): Unit = if (nums.isEmpty) println("No number input") else diff --git a/tests/run/main-annotation-vararg-2.scala b/tests/run/main-annotation-vararg-2.scala index 4f0507268133..3a8399e70768 100644 --- a/tests/run/main-annotation-vararg-2.scala +++ b/tests/run/main-annotation-vararg-2.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Checks that the correct amount of parameters were passed */ - @main def count(count: Int, elems: String*): Unit = + @newMain def count(count: Int, elems: String*): Unit = if (elems.length == count) println("Correct") else diff --git a/tests/run/main-annotation-wrong-param-1.check b/tests/run/main-annotation-wrong-param-1.check index a51e31141d4f..24f498f2fba0 100644 --- a/tests/run/main-annotation-wrong-param-1.check +++ b/tests/run/main-annotation-wrong-param-1.check @@ -1,13 +1,8 @@ -Error: invalid argument for inc: true Error: unused argument: SPAAAAACE Usage: add [--num] [--inc] -Error: invalid argument for num: add Error: unused argument: 3 Usage: add [--num] [--inc] -Error: invalid argument for num: true -Error: invalid argument for inc: false Error: unused argument: 10 Usage: add [--num] [--inc] -Error: invalid argument for num: binary Error: unused argument: 01 Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-1.scala b/tests/run/main-annotation-wrong-param-1.scala index 88e7e4c67937..a7f7c72bb6ef 100644 --- a/tests/run/main-annotation-wrong-param-1.scala +++ b/tests/run/main-annotation-wrong-param-1.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/run/main-annotation-wrong-param-names.check b/tests/run/main-annotation-wrong-param-names.check index d9bbcc049196..2469195cc975 100644 --- a/tests/run/main-annotation-wrong-param-names.check +++ b/tests/run/main-annotation-wrong-param-names.check @@ -1,15 +1,12 @@ -Error: missing argument for num -Error: missing argument for inc -Error: unknown argument name: --n -Error: unknown argument name: --i +Error: ill-formed argument name: --n +Error: ill-formed argument name: --i Usage: add [--num] [--inc] -Error: invalid argument for num: num Error: unused argument: inc Error: unused argument: 10 Usage: add [--num] [--inc] -Error: missing argument for inc Error: unknown argument name: --something -Usage: add [--num] [--inc] Error: missing argument for inc +Usage: add [--num] [--inc] Error: unknown argument name: --else +Error: missing argument for inc Usage: add [--num] [--inc] diff --git a/tests/run/main-annotation-wrong-param-names.scala b/tests/run/main-annotation-wrong-param-names.scala index 548bfb3521b5..6cfdc426f993 100644 --- a/tests/run/main-annotation-wrong-param-names.scala +++ b/tests/run/main-annotation-wrong-param-names.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/run/main-annotation-wrong-param-number.scala b/tests/run/main-annotation-wrong-param-number.scala index d2025faffd21..4feee21496ed 100644 --- a/tests/run/main-annotation-wrong-param-number.scala +++ b/tests/run/main-annotation-wrong-param-number.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram diff --git a/tests/run/main-annotation-wrong-param-type.check b/tests/run/main-annotation-wrong-param-type.check index db8f007cc823..26fff8aa661b 100644 --- a/tests/run/main-annotation-wrong-param-type.check +++ b/tests/run/main-annotation-wrong-param-type.check @@ -1,14 +1,8 @@ -Error: invalid argument for inc: true -Usage: add [--num] [--inc] -Error: invalid argument for num: 2.1 -Usage: add [--num] [--inc] -Error: invalid argument for inc: 3.1415921535 -Usage: add [--num] [--inc] -Error: invalid argument for num: 192.168.1.1 -Usage: add [--num] [--inc] -Error: invalid argument for num: false -Error: invalid argument for inc: true -Usage: add [--num] [--inc] -Error: invalid argument for num: Hello -Error: invalid argument for inc: world! -Usage: add [--num] [--inc] +Error: could not parse argument for `inc` of type Int: true +Error: could not parse argument for `num` of type Int: 2.1 +Error: could not parse argument for `inc` of type Int: 3.1415921535 +Error: could not parse argument for `num` of type Int: 192.168.1.1 +Error: could not parse argument for `num` of type Int: false +Error: could not parse argument for `inc` of type Int: true +Error: could not parse argument for `num` of type Int: Hello +Error: could not parse argument for `inc` of type Int: world! diff --git a/tests/run/main-annotation-wrong-param-type.scala b/tests/run/main-annotation-wrong-param-type.scala index e4e4b3893334..bce72a55df94 100644 --- a/tests/run/main-annotation-wrong-param-type.scala +++ b/tests/run/main-annotation-wrong-param-type.scala @@ -1,8 +1,10 @@ +import scala.annotation.newMain + // Sample main method object myProgram: /** Adds two numbers */ - @main def add(num: Int, inc: Int): Unit = + @newMain def add(num: Int, inc: Int): Unit = println(s"$num + $inc = ${num + inc}") end myProgram