diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 5e969c0c38c9..7962a425e2dc 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] = { @@ -365,7 +359,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 5db706197245..ca2736165a7d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -920,7 +920,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 1cce3fdea280..7130e782d90b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1350,10 +1350,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 4e03f19d42f2..595d048c803b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2627,7 +2627,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