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/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..fa8e9593849c --- /dev/null +++ b/tests/neg/main-annotation-currying.scala @@ -0,0 +1,8 @@ +import scala.annotation.newMain + +object myProgram: + + @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 new file mode 100644 index 000000000000..6f951056f1b2 --- /dev/null +++ b/tests/neg/main-annotation-generic.scala @@ -0,0 +1,8 @@ +import scala.annotation.newMain + +object myProgram: + + @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 new file mode 100644 index 000000000000..2a7d8202acf5 --- /dev/null +++ b/tests/neg/main-annotation-implicit-given.scala @@ -0,0 +1,13 @@ +import scala.annotation.newMain + +object myProgram: + implicit val x: Int = 2 + given Int = 3 + + @newMain def showImplicit(implicit num: Int): Unit = // error + println(num) + + @newMain def showUsing(using num: Int): Unit = // error + println(num) + +end myProgram diff --git a/tests/neg/main-annotation-multiple-annot.scala b/tests/neg/main-annotation-multiple-annot.scala new file mode 100644 index 000000000000..faec8162e9c4 --- /dev/null +++ b/tests/neg/main-annotation-multiple-annot.scala @@ -0,0 +1,8 @@ +import scala.annotation.newMain + +object myProgram: + + @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 new file mode 100644 index 000000000000..2e46098a9ac5 --- /dev/null +++ b/tests/neg/main-annotation-nonmethod.scala @@ -0,0 +1,11 @@ +import scala.annotation.newMain + +object myProgram: + + @newMain val n = 2 // error + + @newMain class A // 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 new file mode 100644 index 000000000000..68d3ba2b3569 --- /dev/null +++ b/tests/neg/main-annotation-nonstatic.scala @@ -0,0 +1,4 @@ +import scala.annotation.newMain + +class A: + @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 new file mode 100644 index 000000000000..75ff2ceac444 --- /dev/null +++ b/tests/neg/main-annotation-unknown-parser-1.scala @@ -0,0 +1,12 @@ +import scala.annotation.newMain + +class MyNumber(val value: Int) { + def +(other: MyNumber): MyNumber = MyNumber(value + other.value) +} + +object myProgram: + + @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 new file mode 100644 index 000000000000..a5681c39419b --- /dev/null +++ b/tests/neg/main-annotation-unknown-parser-2.scala @@ -0,0 +1,27 @@ +import scala.annotation.newMain +import scala.util.CommandLineParser.FromString + +object myProgram: + + @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}") + +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-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.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..92aacb6ffe0b --- /dev/null +++ b/tests/run/main-annotation-birthday.scala @@ -0,0 +1,32 @@ +import scala.annotation.newMain + +/** + * 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 + */ +@newMain 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-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.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..f0495a56bb7a --- /dev/null +++ b/tests/run/main-annotation-default-value-1.scala @@ -0,0 +1,22 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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 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..dc5736117c57 --- /dev/null +++ b/tests/run/main-annotation-default-value-2.scala @@ -0,0 +1,33 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + @newMain 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 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 new file mode 100644 index 000000000000..45c6122e28c7 --- /dev/null +++ b/tests/run/main-annotation-help-override.check @@ -0,0 +1,68 @@ +##### --help +Usage: helpOverride1 [--notHelp] + +A method that should let --help and -h display help. +Arguments: + --notHelp - Int +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: missing argument for --help +Error: missing argument for help +Error: missing argument for h +Usage: helpOverride4 [--help] [-h] +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: missing argument for --help +Error: missing argument for notHelp +Error: missing argument for notH +Usage: helpOverride7 [--notHelp | --help] [--notH | -h] +Error: missing argument for --help +Error: missing argument for notHelp +Usage: helpOverride8 [--notHelp | -h | --help] +helpOverride9: true +##### -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: missing argument for -h +Error: missing argument for h +Usage: helpOverride3 [-h] +Error: missing argument for -h +Error: missing argument for help +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: missing argument for -h +Error: missing argument for notHelp +Usage: helpOverride6 [--notHelp | -h] +Error: missing argument for -h +Error: missing argument for notHelp +Error: missing argument for notH +Usage: helpOverride7 [--notHelp | --help] [--notH | -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 new file mode 100644 index 000000000000..40b793c7b839 --- /dev/null +++ b/tests/run/main-annotation-help-override.scala @@ -0,0 +1,53 @@ +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. */ + @newMain def helpOverride1(notHelp: Int) = ??? + + /** A method that should let -h display help, but not --help. */ + @newMain def helpOverride2(help: Int) = ??? + + /** A method that should let --help display help, but not -h. */ + @newMain def helpOverride3(h: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @newMain def helpOverride4(help: Int, h: Int) = ??? + + + /** A method that should let -h display help, but not --help. */ + @newMain def helpOverride5(@alias("help") notHelp: Int) = ??? + + /** A method that should let --help display help, but not -h. */ + @newMain def helpOverride6(@alias("h") notHelp: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @newMain def helpOverride7(@alias("help") notHelp: Int, @alias("h") notH: Int) = ??? + + /** A method that should not let --help and -h display help. */ + @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 + +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 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..551bbf67d30b --- /dev/null +++ b/tests/run/main-annotation-help.scala @@ -0,0 +1,175 @@ +import scala.annotation.newMain + +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. + */ + @newMain def doc1(num: Int, inc: Int): Unit = () + + /** Adds two numbers. */ + @newMain def doc2(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + */ + @newMain def doc3(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + * @param inc the second number + */ + @newMain def doc4(num: Int, inc: Int = 1): Unit = () + + /** + * Adds two numbers. + * + * @param num the first number + */ + @newMain def doc5(num: Int, inc: Int): Unit = () + + /** + * Adds two numbers. + * + * @param num + * @param inc + */ + @newMain 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) + */ + @newMain 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) + */ + @newMain 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]] + */ + @newMain 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 + */ + @newMain 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! + */ + @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. + */ + @newMain def doc12(num: Int, inc: Int): Unit = () + + /** + * Addstwonumbers.ItseemsthatIhaveaverylonglineofdocumentationandthereforemightneedtobecutatsomepointtofitasmallterminalscreen. + */ + @newMain def doc13(num: Int, inc: Int): Unit = () + + /** + * Loudly judges the number of argument you gave to this poor function. + */ + @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, + 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 + */ + @newMain def doc15(myNum: MyNumber, myInc: MyNumber): Unit = () + + /** + * Compares two instances of [[MyGeneric]]. + * @param first my first element + * @param second my second element + */ + @newMain def doc16(first: MyGeneric[Int], second: MyGeneric[Int]): Unit = () + + // This should not be printed in explain! + @newMain 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-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..b4d095dcceb2 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-1.scala @@ -0,0 +1,27 @@ +import scala.annotation.newMain +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: + + @newMain 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..d7aeaaac8762 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-2.scala @@ -0,0 +1,30 @@ +import scala.annotation.newMain +import scala.util.CommandLineParser.FromString + +given FromString[Test.MyNumber] with + override def fromString(s: String) = Test.create(summon[FromString[Int]].fromString(s)) + +object myProgram: + + @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}") + +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..37ef32e6a0ee --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-3.scala @@ -0,0 +1,26 @@ +import scala.annotation.newMain +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 + + @newMain 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..d7efc84ab424 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-4.scala @@ -0,0 +1,47 @@ +import scala.annotation.newMain +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: + + @newMain def getOption(o: Option[Int] = Some(42)) = println(o) + + @newMain 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..77823ac8cba0 --- /dev/null +++ b/tests/run/main-annotation-homemade-parser-5.scala @@ -0,0 +1,26 @@ +import scala.annotation.newMain +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: + + @newMain 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..4177a97e98f3 --- /dev/null +++ b/tests/run/main-annotation-multiple.scala @@ -0,0 +1,25 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + + /** Subtracts two numbers */ + @newMain 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..9fa6f6677890 --- /dev/null +++ b/tests/run/main-annotation-named-params.scala @@ -0,0 +1,27 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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 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..ca0e32877bc1 --- /dev/null +++ b/tests/run/main-annotation-no-parameters-no-parens.scala @@ -0,0 +1,20 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Does nothing, except confirming that it runs */ + @newMain 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..eb30ac6148ca --- /dev/null +++ b/tests/run/main-annotation-no-parameters.scala @@ -0,0 +1,20 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Does nothing, except confirming that it runs */ + @newMain 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..85a06dc23a0c --- /dev/null +++ b/tests/run/main-annotation-overload.scala @@ -0,0 +1,32 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds three numbers (malformed, doesn't work) */ + def add(num1: Int, num2: Int, num3: Int): Unit = + ??? + + /** Adds two numbers */ + @newMain 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..26b4b7f62a13 --- /dev/null +++ b/tests/run/main-annotation-param-annot-1.scala @@ -0,0 +1,108 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + +object myProgram: + @newMain def altName1( + @alias("myNum") num: Int, + inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @newMain def altName2( + @alias("myNum") num: Int, + @alias("myInc") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @newMain def shortName1( + @alias("n") num: Int, + inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @newMain def shortName2( + @alias("n") num: Int, + @alias("i") inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @newMain def mix1( + @alias("myNum") @alias("n") num: Int, + @alias("i") @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" + + @newMain def mix2( + @alias(myNum) @alias(myShortNum) num: Int, + @alias(myShortInc()) @alias(myInc) inc: Int + ): Unit = + println(s"$num + $inc = ${num + inc}") + + @newMain def multiple( + @alias("myNum") @alias("n") num: Int, + @alias("i") @alias("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 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..ce3b00fe8ed1 --- /dev/null +++ b/tests/run/main-annotation-param-annot-2.scala @@ -0,0 +1,60 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + +object myProgram: + @newMain def multipleSameShortNames1( + @alias("n") num: Int, + @alias("n") inc: Int + ): Unit = () + + @newMain def multipleSameShortNames2( + @alias("n") @alias("n") num: Int, + inc: Int + ): Unit = () + + @newMain def multipleSameNames1( + @alias("arg") num: Int, + @alias("arg") inc: Int + ): Unit = () + + @newMain def multipleSameNames2( + @alias("arg") @alias("arg") num: Int, + inc: Int + ): Unit = () + + @newMain def multipleSameNames3( + num: Int, + @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 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..3eeb7b55a2ae --- /dev/null +++ b/tests/run/main-annotation-param-annot-invalid-params.scala @@ -0,0 +1,45 @@ +import scala.annotation.newMain +import scala.annotation.newMain.alias + +import java.lang.reflect.InvocationTargetException + +object myProgram: + + @newMain def empty( + @alias("") i: Int, + ): Unit = () + + @newMain def space( + @alias(" ") i: Int, + ): Unit = () + + @newMain def nonLetter( + @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 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..5dfdd76047bb --- /dev/null +++ b/tests/run/main-annotation-return-type-1.scala @@ -0,0 +1,23 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers and returns them */ + @newMain 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..7bb75da1a227 --- /dev/null +++ b/tests/run/main-annotation-return-type-2.scala @@ -0,0 +1,26 @@ +import scala.annotation.newMain + +class MyResult(val result: Int): + override def toString: String = result.toString + +// Sample main method +object myProgram: + + /** Adds two numbers and returns them */ + @newMain 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..e02da36c1cb1 --- /dev/null +++ b/tests/run/main-annotation-short-name.check @@ -0,0 +1,5 @@ +2 + 3 = 5 +2 + 3 = 5 +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 new file mode 100644 index 000000000000..2123acb4f882 --- /dev/null +++ b/tests/run/main-annotation-short-name.scala @@ -0,0 +1,22 @@ +import scala.annotation.newMain + +object myProgram: + + /** Adds two numbers */ + @newMain 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..78f8927d924f --- /dev/null +++ b/tests/run/main-annotation-simple.scala @@ -0,0 +1,19 @@ +import scala.annotation.newMain + +object myProgram: + + /** Adds two numbers */ + @newMain 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..c3c1dd79042f --- /dev/null +++ b/tests/run/main-annotation-top-level.scala @@ -0,0 +1,34 @@ +import scala.annotation.newMain + +/** Adds two numbers */ +@newMain def add(num: Int, inc: Int): Unit = + println(s"$num + $inc = ${num + inc}") + +/** Adds any amount of numbers */ +@newMain 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..6ab941caa08c --- /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 +byte - 1 + +Here's what I got: +int - -1 +double - 3.4567890987654456E18 +string - false +byte - 127 + +Here's what I got: +int - 2147483647 +double - 3.1415926535 +string - Hello world! +byte - 0 + diff --git a/tests/run/main-annotation-types.scala b/tests/run/main-annotation-types.scala new file mode 100644 index 000000000000..ab0654c78a80 --- /dev/null +++ b/tests/run/main-annotation-types.scala @@ -0,0 +1,32 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Displays some parameters */ + @newMain def show( + int: Int, + double: Double, + string: String, + byte: Byte + ): Unit = + println("Here's what I got:") + println(s"int - $int") + println(s"double - $double") + println(s"string - $string") + println(s"byte - $byte") + 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", "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.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..5c488940fa14 --- /dev/null +++ b/tests/run/main-annotation-vararg-1.scala @@ -0,0 +1,27 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds any amount of numbers */ + @newMain 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..3a8399e70768 --- /dev/null +++ b/tests/run/main-annotation-vararg-2.scala @@ -0,0 +1,30 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Checks that the correct amount of parameters were passed */ + @newMain 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..24f498f2fba0 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-1.check @@ -0,0 +1,8 @@ +Error: unused argument: SPAAAAACE +Usage: add [--num] [--inc] +Error: unused argument: 3 +Usage: add [--num] [--inc] +Error: unused argument: 10 +Usage: add [--num] [--inc] +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..a7f7c72bb6ef --- /dev/null +++ b/tests/run/main-annotation-wrong-param-1.scala @@ -0,0 +1,23 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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..2469195cc975 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-names.check @@ -0,0 +1,12 @@ +Error: ill-formed argument name: --n +Error: ill-formed argument name: --i +Usage: add [--num] [--inc] +Error: unused argument: inc +Error: unused argument: 10 +Usage: add [--num] [--inc] +Error: unknown argument name: --something +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 new file mode 100644 index 000000000000..6cfdc426f993 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-names.scala @@ -0,0 +1,23 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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..4feee21496ed --- /dev/null +++ b/tests/run/main-annotation-wrong-param-number.scala @@ -0,0 +1,23 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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..26fff8aa661b --- /dev/null +++ b/tests/run/main-annotation-wrong-param-type.check @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000000..bce72a55df94 --- /dev/null +++ b/tests/run/main-annotation-wrong-param-type.scala @@ -0,0 +1,25 @@ +import scala.annotation.newMain + +// Sample main method +object myProgram: + + /** Adds two numbers */ + @newMain 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