From ad871eea3398fa282ea7d6b6f44ce085ee05404b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Mar 2024 11:42:05 +0100 Subject: [PATCH] Remove experimental `MainAnnotation`/`newMain` (replaced with `MacroAnnotation`) `MainAnnotation` and its implementation `newMain` predate `MacroAnnotation`. The `MacroAnnotation` is subsumed feature and allows much more flexibility. `MainAnnotation` and `newMain` could be reimplemented as a macro annotation in an external library. See SIP-63: https://github.com/scala/improvement-proposals/pull/80 --- .../dotty/tools/dotc/ast/MainProxies.scala | 326 +-------------- .../dotty/tools/dotc/core/Definitions.scala | 6 - .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 1 - .../reference/experimental/main-annotation.md | 4 +- .../src/scala/annotation/MainAnnotation.scala | 129 ------ library/src/scala/annotation/newMain.scala | 389 ------------------ .../completion/CompletionKeywordSuite.scala | 8 +- tests/neg/main-annotation-currying.scala | 8 - tests/neg/main-annotation-generic.scala | 8 - .../neg/main-annotation-implicit-given.scala | 13 - .../neg/main-annotation-mainannotation.scala | 3 - .../neg/main-annotation-multiple-annot.scala | 8 - tests/neg/main-annotation-nonmethod.scala | 11 - tests/neg/main-annotation-nonstatic.scala | 4 - .../main-annotation-unknown-parser-1.scala | 12 - .../main-annotation-unknown-parser-2.scala | 27 -- .../stdlibExperimentalDefinitions.scala | 22 - tests/run/main-annotation-birthday.scala | 35 -- tests/run/main-annotation-dash-dash.scala | 28 -- .../run/main-annotation-default-value-1.scala | 25 -- .../run/main-annotation-default-value-2.scala | 36 -- tests/run/main-annotation-example.scala | 65 --- tests/run/main-annotation-flags.scala | 44 -- tests/run/main-annotation-help-override.scala | 56 --- tests/run/main-annotation-help.scala | 178 -------- .../main-annotation-homemade-annot-1.scala | 49 --- .../main-annotation-homemade-annot-2.scala | 52 --- .../main-annotation-homemade-annot-3.scala | 26 -- .../main-annotation-homemade-annot-4.scala | 27 -- .../main-annotation-homemade-annot-5.scala | 28 -- .../main-annotation-homemade-annot-6.scala | 65 --- .../main-annotation-homemade-parser-1.scala | 30 -- .../main-annotation-homemade-parser-2.scala | 33 -- .../main-annotation-homemade-parser-3.scala | 29 -- .../main-annotation-homemade-parser-4.scala | 50 --- .../main-annotation-homemade-parser-5.scala | 29 -- tests/run/main-annotation-multiple.scala | 28 -- tests/run/main-annotation-named-params.scala | 30 -- tests/run/main-annotation-newMain.scala | 323 --------------- ...n-annotation-no-parameters-no-parens.scala | 23 -- tests/run/main-annotation-no-parameters.scala | 23 -- tests/run/main-annotation-overload.scala | 35 -- tests/run/main-annotation-param-annot-1.scala | 111 ----- tests/run/main-annotation-param-annot-2.scala | 63 --- ...nnotation-param-annot-invalid-params.scala | 48 --- tests/run/main-annotation-return-type-1.scala | 26 -- tests/run/main-annotation-return-type-2.scala | 29 -- tests/run/main-annotation-short-name.scala | 25 -- tests/run/main-annotation-simple.scala | 22 - tests/run/main-annotation-top-level.scala | 37 -- tests/run/main-annotation-types.scala | 35 -- tests/run/main-annotation-vararg-1.scala | 30 -- tests/run/main-annotation-vararg-2.scala | 33 -- tests/run/main-annotation-wrong-param-1.scala | 26 -- .../main-annotation-wrong-param-names.scala | 26 -- .../main-annotation-wrong-param-number.scala | 26 -- .../main-annotation-wrong-param-type.scala | 28 -- tests/run/main-calculator-example.scala | 67 --- 59 files changed, 7 insertions(+), 2953 deletions(-) delete mode 100644 library/src/scala/annotation/MainAnnotation.scala delete mode 100644 library/src/scala/annotation/newMain.scala delete mode 100644 tests/neg/main-annotation-currying.scala delete mode 100644 tests/neg/main-annotation-generic.scala delete mode 100644 tests/neg/main-annotation-implicit-given.scala delete mode 100644 tests/neg/main-annotation-mainannotation.scala delete mode 100644 tests/neg/main-annotation-multiple-annot.scala delete mode 100644 tests/neg/main-annotation-nonmethod.scala delete mode 100644 tests/neg/main-annotation-nonstatic.scala delete mode 100644 tests/neg/main-annotation-unknown-parser-1.scala delete mode 100644 tests/neg/main-annotation-unknown-parser-2.scala delete mode 100644 tests/run/main-annotation-birthday.scala delete mode 100644 tests/run/main-annotation-dash-dash.scala delete mode 100644 tests/run/main-annotation-default-value-1.scala delete mode 100644 tests/run/main-annotation-default-value-2.scala delete mode 100644 tests/run/main-annotation-example.scala delete mode 100644 tests/run/main-annotation-flags.scala delete mode 100644 tests/run/main-annotation-help-override.scala delete mode 100644 tests/run/main-annotation-help.scala delete mode 100644 tests/run/main-annotation-homemade-annot-1.scala delete mode 100644 tests/run/main-annotation-homemade-annot-2.scala delete mode 100644 tests/run/main-annotation-homemade-annot-3.scala delete mode 100644 tests/run/main-annotation-homemade-annot-4.scala delete mode 100644 tests/run/main-annotation-homemade-annot-5.scala delete mode 100644 tests/run/main-annotation-homemade-annot-6.scala delete mode 100644 tests/run/main-annotation-homemade-parser-1.scala delete mode 100644 tests/run/main-annotation-homemade-parser-2.scala delete mode 100644 tests/run/main-annotation-homemade-parser-3.scala delete mode 100644 tests/run/main-annotation-homemade-parser-4.scala delete mode 100644 tests/run/main-annotation-homemade-parser-5.scala delete mode 100644 tests/run/main-annotation-multiple.scala delete mode 100644 tests/run/main-annotation-named-params.scala delete mode 100644 tests/run/main-annotation-newMain.scala delete mode 100644 tests/run/main-annotation-no-parameters-no-parens.scala delete mode 100644 tests/run/main-annotation-no-parameters.scala delete mode 100644 tests/run/main-annotation-overload.scala delete mode 100644 tests/run/main-annotation-param-annot-1.scala delete mode 100644 tests/run/main-annotation-param-annot-2.scala delete mode 100644 tests/run/main-annotation-param-annot-invalid-params.scala delete mode 100644 tests/run/main-annotation-return-type-1.scala delete mode 100644 tests/run/main-annotation-return-type-2.scala delete mode 100644 tests/run/main-annotation-short-name.scala delete mode 100644 tests/run/main-annotation-simple.scala delete mode 100644 tests/run/main-annotation-top-level.scala delete mode 100644 tests/run/main-annotation-types.scala delete mode 100644 tests/run/main-annotation-vararg-1.scala delete mode 100644 tests/run/main-annotation-vararg-2.scala delete mode 100644 tests/run/main-annotation-wrong-param-1.scala delete mode 100644 tests/run/main-annotation-wrong-param-names.scala delete mode 100644 tests/run/main-annotation-wrong-param-number.scala delete mode 100644 tests/run/main-annotation-wrong-param-type.scala delete mode 100644 tests/run/main-calculator-example.scala diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 8ee75cbf364b..7bf83d548c97 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -12,11 +12,6 @@ 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 +30,7 @@ object MainProxies { * catch case err: ParseError => showError(err) * } */ - private def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + def proxies(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) => @@ -127,323 +122,4 @@ object MainProxies { result } - private type DefaultValueSymbols = Map[Int, Symbol] - private type ParameterAnnotationss = Seq[Seq[Annotation]] - - /** - * Generate proxy classes for main functions. - * A function like - * - * /** - * * Lorem ipsum dolor sit amet - * * consectetur adipiscing elit. - * * - * * @param x my param x - * * @param ys all my params y - * */ - * @myMain(80) def f( - * @myMain.Alias("myX") x: S, - * y: S, - * ys: T* - * ) = ... - * - * would be translated to something like - * - * final class f { - * static def main(args: Array[String]): Unit = { - * val annotation = new myMain(80) - * val info = new Info( - * name = "f", - * documentation = "Lorem ipsum dolor sit amet consectetur adipiscing elit.", - * parameters = Seq( - * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))), - * new scala.annotation.MainAnnotation.Parameter("y", "S", true, false, "", Seq()), - * new scala.annotation.MainAnnotation.Parameter("ys", "T", false, true, "all my params y", Seq()) - * ) - * ), - * val command = annotation.command(info, args) - * if command.isDefined then - * val cmd = command.get - * val args0: () => S = annotation.argGetter[S](info.parameters(0), cmd(0), None) - * val args1: () => S = annotation.argGetter[S](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) - * val args2: () => Seq[T] = annotation.varargGetter[T](info.parameters(2), cmd.drop(2)) - * annotation.run(() => f(args0(), args1(), args2()*)) - * } - * } - */ - private def mainAnnotationProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { - import tpd.* - - /** - * Computes the symbols of the default values of the function. Since they cannot be inferred anymore at this - * point of the compilation, they must be explicitly passed by [[mainProxy]]. - */ - def defaultValueSymbols(scope: Tree, funSymbol: Symbol): DefaultValueSymbols = - scope match { - case TypeDef(_, template: Template) => - template.body.flatMap((_: Tree) match { - case dd: DefDef if dd.name.is(DefaultGetterName) && dd.name.firstPart == funSymbol.name => - val DefaultGetterName.NumberedInfo(index) = dd.name.info: @unchecked - List(index -> dd.symbol) - case _ => Nil - }).toMap - case _ => Map.empty - } - - /** Computes the list of main methods present in the code. */ - 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 { - case Nil => - Nil - case _ :: Nil => - val paramAnnotations = stat.paramss.flatMap(_.map( - valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotationParameterAnnotation)) - )) - (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil - case mainAnnot :: others => - report.error(em"method cannot have multiple main annotations", mainAnnot.tree) - Nil - } - case stat @ TypeDef(_, impl: Template) if stat.symbol.is(Module) => - mainMethods(stat, impl.body) - case _ => - Nil - } - - // Assuming that the top-level object was already generated, all main methods will have a scope - mainMethods(EmptyTree, stats).flatMap(mainAnnotationProxy) - } - - private def mainAnnotationProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): Option[TypeDef] = { - val mainAnnot = mainFun.getAnnotation(defn.MainAnnotationClass).get - def pos = mainFun.sourcePos - - val documentation = new Documentation(docComment) - - /** () => value */ - def unitToValue(value: Tree): Tree = - val defDef = DefDef(nme.ANON_FUN, List(Nil), TypeTree(), value) - Block(defDef, Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) - - /** Generate a list of trees containing the ParamInfo instantiations. - * - * A ParamInfo has the following shape - * ``` - * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) - * ``` - */ - def parameterInfos(mt: MethodType): List[Tree] = - extension (tree: Tree) def withProperty(sym: Symbol, args: List[Tree]) = - Apply(Select(tree, sym.name), args) - - for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield - val param = paramName.toString - val paramType0 = if formal.isRepeatedParam then formal.argTypes.head.dealias else formal.dealias - val paramType = paramType0.dealias - val paramTypeOwner = paramType.typeSymbol.owner - val paramTypeStr = - if paramTypeOwner == defn.EmptyPackageClass then paramType.show - else paramTypeOwner.showFullName + "." + paramType.show - val hasDefault = defaultValueSymbols.contains(idx) - val isRepeated = formal.isRepeatedParam - val paramDoc = documentation.argDocs.getOrElse(param, "") - val paramAnnots = - val annotationTrees = paramAnnotations(idx).map(instantiateAnnotation).toList - Apply(ref(defn.SeqModule.termRef), annotationTrees) - - val constructorArgs = List(param, paramTypeStr, hasDefault, isRepeated, paramDoc) - .map(value => Literal(Constant(value))) - - New(TypeTree(defn.MainAnnotationParameter.typeRef), List(constructorArgs :+ paramAnnots)) - - end parameterInfos - - /** - * Creates a list of references and definitions of arguments. - * The goal is to create the - * `val args0: () => S = annotation.argGetter[S](0, cmd(0), None)` - * part of the code. - */ - def argValDefs(mt: MethodType): List[ValDef] = - for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield - val argName = nme.args ++ idx.toString - val isRepeated = formal.isRepeatedParam - val formalType = if isRepeated then formal.argTypes.head else formal - val getterName = if isRepeated then nme.varargGetter else nme.argGetter - val defaultValueGetterOpt = defaultValueSymbols.get(idx) match - case None => ref(defn.NoneModule.termRef) - case Some(dvSym) => - val value = unitToValue(ref(dvSym.termRef)) - Apply(ref(defn.SomeClass.companionModule.termRef), value) - val argGetter0 = TypeApply(Select(Ident(nme.annotation), getterName), TypeTree(formalType) :: Nil) - val index = Literal(Constant(idx)) - val paramInfo = Apply(Select(Ident(nme.info), nme.parameters), index) - val argGetter = - if isRepeated then Apply(argGetter0, List(paramInfo, Apply(Select(Ident(nme.cmd), nme.drop), List(index)))) - else Apply(argGetter0, List(paramInfo, Apply(Ident(nme.cmd), List(index)), defaultValueGetterOpt)) - ValDef(argName, TypeTree(), argGetter) - end argValDefs - - - /** Create a list of argument references that will be passed as argument to the main method. - * `args0`, ...`argn*` - */ - def argRefs(mt: MethodType): List[Tree] = - for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield - val argRef = Apply(Ident(nme.args ++ idx.toString), Nil) - if formal.isRepeatedParam then repeated(argRef) else argRef - end argRefs - - - /** Turns an annotation (e.g. `@main(40)`) into an instance of the class (e.g. `new scala.main(40)`). */ - def instantiateAnnotation(annot: Annotation): Tree = - val argss = { - def recurse(t: tpd.Tree, acc: List[List[Tree]]): List[List[Tree]] = t match { - case Apply(t, args: List[tpd.Tree]) => recurse(t, extractArgs(args) :: acc) - case _ => acc - } - - def extractArgs(args: List[tpd.Tree]): List[Tree] = - args.flatMap { - case Typed(SeqLiteral(varargs, _), _) => varargs.map(arg => TypedSplice(arg)) - case arg: Select if arg.name.is(DefaultGetterName) => Nil // Ignore default values, they will be added later by the compiler - case arg => List(TypedSplice(arg)) - } - - recurse(annot.tree, Nil) - } - - New(TypeTree(annot.symbol.typeRef), argss) - end instantiateAnnotation - - def generateMainClass(mainCall: Tree, args: List[Tree], parameterInfos: List[Tree]): TypeDef = - val cmdInfo = - val nameTree = Literal(Constant(mainFun.showName)) - val docTree = Literal(Constant(documentation.mainDoc)) - val paramInfos = Apply(ref(defn.SeqModule.termRef), parameterInfos) - New(TypeTree(defn.MainAnnotationInfo.typeRef), List(List(nameTree, docTree, paramInfos))) - - val annotVal = ValDef( - nme.annotation, - TypeTree(), - instantiateAnnotation(mainAnnot) - ) - val infoVal = ValDef( - nme.info, - TypeTree(), - cmdInfo - ) - val command = ValDef( - nme.command, - TypeTree(), - Apply( - Select(Ident(nme.annotation), nme.command), - List(Ident(nme.info), Ident(nme.args)) - ) - ) - val argsVal = ValDef( - nme.cmd, - TypeTree(), - Select(Ident(nme.command), nme.get) - ) - val run = Apply(Select(Ident(nme.annotation), nme.run), mainCall) - val body0 = If( - Select(Ident(nme.command), nme.isDefined), - Block(argsVal :: args, run), - EmptyTree - ) - val body = Block(List(annotVal, infoVal, command), body0) // TODO add `if (cmd.nonEmpty)` - - val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) - .withFlags(Param) - /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. - * The annotations will be retype-checked in another scope that may not have the same imports. - */ - def insertTypeSplices = new TreeMap { - override def transform(tree: Tree)(using Context): Tree = tree match - case tree: tpd.Ident @unchecked => TypedSplice(tree) - case tree => super.transform(tree) - } - val annots = mainFun.annotations - .filterNot(_.matches(defn.MainAnnotationClass)) - .map(annot => insertTypeSplices.transform(annot.tree)) - val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) - .withFlags(JavaStatic) - .withAnnotations(annots) - val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) - val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) - .withFlags(Final | Invisible) - mainCls.withSpan(mainAnnot.tree.span.toSynthetic) - end generateMainClass - - if (!mainFun.owner.isStaticOwner) - report.error(em"main method is not statically accessible", pos) - None - else mainFun.info match { - case _: ExprType => - Some(generateMainClass(unitToValue(ref(mainFun.termRef)), Nil, Nil)) - case mt: MethodType => - if (mt.isImplicitMethod) - report.error(em"main method cannot have implicit parameters", pos) - None - else mt.resType match - case restpe: MethodType => - report.error(em"main method cannot be curried", pos) - None - case _ => - Some(generateMainClass(unitToValue(Apply(ref(mainFun.termRef), argRefs(mt))), argValDefs(mt), parameterInfos(mt))) - case _: PolyType => - report.error(em"main method cannot have type parameters", pos) - None - case _ => - report.error(em"main can only annotate a method", pos) - None - } - } - - /** A class responsible for extracting the docstrings of a method. */ - private class Documentation(docComment: Option[Comment]): - import util.CommentParsing.* - - /** The main part of the documentation. */ - lazy val mainDoc: String = _mainDoc - /** The parameters identified by @param. Maps from parameter name to its documentation. */ - lazy val argDocs: Map[String, String] = _argDocs - - private var _mainDoc: String = "" - private var _argDocs: Map[String, String] = Map() - - docComment match { - case Some(comment) => if comment.isDocComment then parseDocComment(comment.raw) else _mainDoc = comment.raw - case None => - } - - private def cleanComment(raw: String): String = - var lines: Seq[String] = raw.trim.nn.split('\n').nn.toSeq - lines = lines.map(l => l.substring(skipLineLead(l, -1), l.length).nn.trim.nn) - var s = lines.foldLeft("") { - case ("", s2) => s2 - case (s1, "") if s1.last == '\n' => s1 // Multiple newlines are kept as single newlines - case (s1, "") => s1 + '\n' - case (s1, s2) if s1.last == '\n' => s1 + s2 - case (s1, s2) => s1 + ' ' + s2 - } - s.replaceAll(raw"\[\[", "").nn.replaceAll(raw"\]\]", "").nn.trim.nn - - private def parseDocComment(raw: String): Unit = - // Positions of the sections (@) in the docstring - val tidx: List[(Int, Int)] = tagIndex(raw) - - // Parse main comment - var mainComment: String = raw.substring(skipLineLead(raw, 0), startTag(raw, tidx)).nn - _mainDoc = cleanComment(mainComment) - - // Parse arguments comments - val argsCommentsSpans: Map[String, (Int, Int)] = paramDocs(raw, "@param", tidx) - val argsCommentsTextSpans = argsCommentsSpans.view.mapValues(extractSectionText(raw, _)) - val argsCommentsTexts = argsCommentsTextSpans.mapValues({ case (beg, end) => raw.substring(beg, end).nn }) - _argDocs = argsCommentsTexts.mapValues(cleanComment(_)).toMap - end Documentation } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 789e744fbfc9..f187498da1fb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -926,12 +926,6 @@ class Definitions { @tu lazy val XMLTopScopeModule: Symbol = requiredModule("scala.xml.TopScope") - @tu lazy val MainAnnotationClass: ClassSymbol = requiredClass("scala.annotation.MainAnnotation") - @tu lazy val MainAnnotationInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Info") - @tu lazy val MainAnnotationParameter: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Parameter") - @tu lazy val MainAnnotationParameterAnnotation: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterAnnotation") - @tu lazy val MainAnnotationCommand: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Command") - @tu lazy val CommandLineParserModule: Symbol = requiredModule("scala.util.CommandLineParser") @tu lazy val CLP_ParseError: ClassSymbol = CommandLineParserModule.requiredClass("ParseError").typeRef.symbol.asClass @tu lazy val CLP_parseArgument: Symbol = CommandLineParserModule.requiredMethod("parseArgument") diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 56f67574a72d..96e2e937927d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1508,7 +1508,7 @@ trait Checking { val annotCls = Annotations.annotClass(annot) val concreteAnnot = Annotations.ConcreteAnnotation(annot) val pos = annot.srcPos - if (annotCls == defn.MainAnnot || concreteAnnot.matches(defn.MainAnnotationClass)) { + if (annotCls == 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/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 24721f1cd758..5a5a53590d62 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1260,7 +1260,6 @@ class Namer { typer: Typer => && annot.symbol != defn.TailrecAnnot && annot.symbol != defn.MainAnnot && !annot.symbol.derivesFrom(defn.MacroAnnotationClass) - && !annot.symbol.derivesFrom(defn.MainAnnotationClass) }) if forwarder.isType then diff --git a/docs/_docs/reference/experimental/main-annotation.md b/docs/_docs/reference/experimental/main-annotation.md index 7cc105be06f9..98a8479132da 100644 --- a/docs/_docs/reference/experimental/main-annotation.md +++ b/docs/_docs/reference/experimental/main-annotation.md @@ -4,6 +4,8 @@ title: "MainAnnotation" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/main-annotation.html --- +> This feature was removed in https://github.com/scala/scala3/pull/19937. It was subsumed by macro annotations. See SIP-63 https://github.com/scala/improvement-proposals/pull/80. + `MainAnnotation` provides a generic way to define main annotations such as `@main`. When a users annotates a method with an annotation that extends `MainAnnotation` a class with a `main` method will be generated. The main method will contain the code needed to parse the command line arguments and run the application. @@ -93,6 +95,6 @@ import scala.util.CommandLineParser.FromString[T] val result = program() println("result: " + result) println("executed program") - + end myMain ``` diff --git a/library/src/scala/annotation/MainAnnotation.scala b/library/src/scala/annotation/MainAnnotation.scala deleted file mode 100644 index 29e650e50b74..000000000000 --- a/library/src/scala/annotation/MainAnnotation.scala +++ /dev/null @@ -1,129 +0,0 @@ -package scala.annotation - -/** MainAnnotation provides the functionality for a compiler-generated main class. - * It links a compiler-generated main method (call it compiler-main) to a user - * written main method (user-main). - * The protocol of calls from compiler-main is as follows: - * - * - create a `command` with the command line arguments, - * - for each parameter of user-main, a call to `command.argGetter`, - * or `command.varargGetter` if is a final varargs parameter, - * - a call to `command.run` with the closure of user-main applied to all arguments. - * - * Example: - * ```scala sc:nocompile - * /** Sum all the numbers - * * - * * @param first Fist number to sum - * * @param rest The rest of the numbers to sum - * */ - * @myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum - * ``` - * generates - * ```scala sc:nocompile - * object foo { - * def main(args: Array[String]): Unit = { - * val mainAnnot = new myMain() - * val info = new Info( - * name = "foo.main", - * documentation = "Sum all the numbers", - * parameters = Seq( - * new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), - * new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") - * ) - * ) - * val mainArgsOpt = mainAnnot.command(info, args) - * if mainArgsOpt.isDefined then - * val mainArgs = mainArgsOpt.get - * val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using parser Int - * val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using parser Int - * val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using parser Int - * mainAnnot.run(() => sum(args0(), args1(), args2()*)) - * } - * } - * ``` - * - * @param Parser The class used for argument string parsing and arguments into a `T` - * @param Result The required result type of the main method. - * If this type is Any or Unit, any type will be accepted. - */ -@experimental -trait MainAnnotation[Parser[_], Result] extends StaticAnnotation: - import MainAnnotation.{Info, Parameter} - - /** Process the command arguments before parsing them. - * - * Return `Some` of the sequence of arguments that will be parsed to be passed to the main method. - * This sequence needs to have the same length as the number of parameters of the main method (i.e. `info.parameters.size`). - * If there is a varags parameter, then the sequence must be at least of length `info.parameters.size - 1`. - * - * Returns `None` if the arguments are invalid and parsing and run should be stopped. - * - * @param info The information about the command (name, documentation and info about parameters) - * @param args The command line arguments - */ - def command(info: Info, args: Seq[String]): Option[Seq[String]] - - /** The getter for the `idx`th argument of type `T` - * - * @param idx The index of the argument - * @param defaultArgument Optional lambda to instantiate the default argument - */ - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using Parser[T]): () => T - - /** The getter for a final varargs argument of type `T*` */ - def varargGetter[T](param: Parameter, args: Seq[String])(using Parser[T]): () => Seq[T] - - /** Run `program` if all arguments are valid if all arguments are valid - * - * @param program A function containing the call to the main method and instantiation of its arguments - */ - def run(program: () => Result): Unit - -end MainAnnotation - -@experimental -object MainAnnotation: - - /** Information about the main method - * - * @param name The name of the main method - * @param documentation The documentation of the main method without the `@param` documentation (see Parameter.documentaion) - * @param parameters Information about the parameters of the main method - */ - @experimental // MiMa does not check scope inherited @experimental - final class Info( - val name: String, - val documentation: String, - val parameters: Seq[Parameter], - ): - - /** If the method ends with a varargs parameter */ - def hasVarargs: Boolean = parameters.nonEmpty && parameters.last.isVarargs - - end Info - - /** Information about a parameter of a main method - * - * @param name The name of the parameter - * @param typeName The name of the parameter's type - * @param hasDefault If the parameter has a default argument - * @param isVarargs If the parameter is a varargs parameter (can only be true for the last parameter) - * @param documentation The documentation of the parameter (from `@param` documentation in the main method) - * @param annotations The annotations of the parameter that extend `ParameterAnnotation` - */ - @experimental // MiMa does not check scope inherited @experimental - final class Parameter( - val name: String, - val typeName: String, - val hasDefault: Boolean, - val isVarargs: Boolean, - val documentation: String, - val annotations: Seq[ParameterAnnotation], - ) - - /** Marker trait for annotations that will be included in the Parameter annotations. */ - @experimental // MiMa does not check scope inherited @experimental - trait ParameterAnnotation extends StaticAnnotation - -end MainAnnotation diff --git a/library/src/scala/annotation/newMain.scala b/library/src/scala/annotation/newMain.scala deleted file mode 100644 index 552e4225a648..000000000000 --- a/library/src/scala/annotation/newMain.scala +++ /dev/null @@ -1,389 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ 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") - - @experimental // MiMa does not check scope inherited @experimental - 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 - - @experimental // MiMa does not check scope inherited @experimental - 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/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala index c828cd4e6e67..15c449904074 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionKeywordSuite.scala @@ -364,9 +364,7 @@ class CompletionKeywordSuite extends BaseCompletionSuite: | val x: Map[Int, new@@] |} """.stripMargin, - "", - // to avoid newMain annotation - filter = str => !str.contains("newMain") + "" ) // TODO: Should provide empty completions // The issue is that the tree looks the same as for `case @@` (it doesn't see `new`) @@ -382,9 +380,7 @@ class CompletionKeywordSuite extends BaseCompletionSuite: | } |} """.stripMargin, - "", - // to avoid newMain annotation - filter = str => !str.contains("newMain") + "" ) @Test def `super-typeapply` = diff --git a/tests/neg/main-annotation-currying.scala b/tests/neg/main-annotation-currying.scala deleted file mode 100644 index fa8e9593849c..000000000000 --- a/tests/neg/main-annotation-currying.scala +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 6f951056f1b2..000000000000 --- a/tests/neg/main-annotation-generic.scala +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 2a7d8202acf5..000000000000 --- a/tests/neg/main-annotation-implicit-given.scala +++ /dev/null @@ -1,13 +0,0 @@ -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-mainannotation.scala b/tests/neg/main-annotation-mainannotation.scala deleted file mode 100644 index 21e37d1779af..000000000000 --- a/tests/neg/main-annotation-mainannotation.scala +++ /dev/null @@ -1,3 +0,0 @@ -import scala.annotation.MainAnnotation - -@MainAnnotation def f(i: Int, n: Int) = () // error diff --git a/tests/neg/main-annotation-multiple-annot.scala b/tests/neg/main-annotation-multiple-annot.scala deleted file mode 100644 index faec8162e9c4..000000000000 --- a/tests/neg/main-annotation-multiple-annot.scala +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 2e46098a9ac5..000000000000 --- a/tests/neg/main-annotation-nonmethod.scala +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 68d3ba2b3569..000000000000 --- a/tests/neg/main-annotation-nonstatic.scala +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 75ff2ceac444..000000000000 --- a/tests/neg/main-annotation-unknown-parser-1.scala +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index a5681c39419b..000000000000 --- a/tests/neg/main-annotation-unknown-parser-2.scala +++ /dev/null @@ -1,27 +0,0 @@ -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-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index ca48dd2d8a5f..bc44cbfec18f 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -25,28 +25,6 @@ val experimentalDefinitionInLibrary = Set( "scala.util.TupledFunction", "scala.util.TupledFunction$", - //// New feature: main annotation generalization - // Can be stabilized when language feature is stabilized. - // Needs user feedback. - // Should argGetter/varargGetter be simplified? - // Should we have better support for main annotation macros? - "scala.annotation.MainAnnotation", - "scala.annotation.MainAnnotation$", - "scala.annotation.MainAnnotation$.Info", - "scala.annotation.MainAnnotation$.Parameter", - "scala.annotation.MainAnnotation$.ParameterAnnotation", - - - //// 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", - "scala.annotation.newMain.Help", - "scala.annotation.newMain.Help$", - "scala.annotation.newMain.Names", - //// New feature: capture checking "scala.annotation.capability", "scala.annotation.retains", diff --git a/tests/run/main-annotation-birthday.scala b/tests/run/main-annotation-birthday.scala deleted file mode 100644 index 32cf28784ced..000000000000 --- a/tests/run/main-annotation-birthday.scala +++ /dev/null @@ -1,35 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-dash-dash.scala deleted file mode 100644 index 3fe0f47983d5..000000000000 --- a/tests/run/main-annotation-dash-dash.scala +++ /dev/null @@ -1,28 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.newMain - -object myProgram: - - @newMain def foo(str: String, rest: String*): Unit = - println(s"str = $str") - println(s"rest = ${rest.mkString(",")}") - println() - -end myProgram - -object Test: - def callMain(args: Array[String]): Unit = - val clazz = Class.forName("foo") - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, args) - - def main(args: Array[String]): Unit = - callMain(Array("x", "y", "z")) - callMain(Array("--", "x", "y", "z")) - callMain(Array("--", "-a", "x", "y", "z")) - callMain(Array("x", "--", "y", "z")) - callMain(Array("--str", "y", "--", "z")) - callMain(Array("--str", "--", "y", "z")) // missing argument for `--str` -end Test diff --git a/tests/run/main-annotation-default-value-1.scala b/tests/run/main-annotation-default-value-1.scala deleted file mode 100644 index cf4ba79e1aff..000000000000 --- a/tests/run/main-annotation-default-value-1.scala +++ /dev/null @@ -1,25 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-default-value-2.scala deleted file mode 100644 index 8b60e6197405..000000000000 --- a/tests/run/main-annotation-default-value-2.scala +++ /dev/null @@ -1,36 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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-example.scala b/tests/run/main-annotation-example.scala deleted file mode 100644 index 926496e595e7..000000000000 --- a/tests/run/main-annotation-example.scala +++ /dev/null @@ -1,65 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* -import collection.mutable -import scala.util.CommandLineParser.FromString - -/** Sum all the numbers - * - * @param first Fist number to sum - * @param rest The rest of the numbers to sum - */ -@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum - - -object Test: - def callMain(args: Array[String]): Unit = - val clazz = Class.forName("sum") - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, args) - - def main(args: Array[String]): Unit = - callMain(Array("23", "2", "3")) -end Test - -@experimental -class myMain extends MainAnnotation[FromString, Int]: - import MainAnnotation.{ Info, Parameter } - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = - if args.contains("--help") then - println(info.documentation) - None // do not parse or run the program - else if info.parameters.exists(_.hasDefault) then - println("Default arguments are not supported") - None - else if info.hasVarargs then - val numPlainArgs = info.parameters.length - 1 - if numPlainArgs > args.length then - println("Not enough arguments") - None - else - Some(args) - else - if info.parameters.length > args.length then - println("Not enough arguments") - None - else if info.parameters.length < args.length then - println("Too many arguments") - None - else - Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = - () => parser.fromString(arg) - - def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = - () => args.map(arg => parser.fromString(arg)) - - def run(program: () => Int): Unit = - println("executing program") - val result = program() - println("result: " + result) - println("executed program") -end myMain diff --git a/tests/run/main-annotation-flags.scala b/tests/run/main-annotation-flags.scala deleted file mode 100644 index 8a579e6e2d00..000000000000 --- a/tests/run/main-annotation-flags.scala +++ /dev/null @@ -1,44 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-help-override.scala deleted file mode 100644 index bfff85c5a353..000000000000 --- a/tests/run/main-annotation-help-override.scala +++ /dev/null @@ -1,56 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-help.scala deleted file mode 100644 index d68bb0d7e874..000000000000 --- a/tests/run/main-annotation-help.scala +++ /dev/null @@ -1,178 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala deleted file mode 100644 index 3106dae4006f..000000000000 --- a/tests/run/main-annotation-homemade-annot-1.scala +++ /dev/null @@ -1,49 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.concurrent._ -import scala.annotation.* -import scala.collection.mutable -import ExecutionContext.Implicits.global -import duration._ -import util.CommandLineParser.FromString - -@mainAwait def get(wait: Int): Future[Int] = Future{ - Thread.sleep(1000 * wait) - 42 -} - -@mainAwait def getMany(wait: Int*): Future[Int] = Future{ - Thread.sleep(1000 * wait.sum) - wait.length -} - -object Test: - def callMain(cls: String, args: Array[String]): Unit = - val clazz = Class.forName(cls) - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, args) - - def main(args: Array[String]): Unit = - println(Await.result(get(1), Duration(2, SECONDS))) - callMain("get", Array("1")) - callMain("getMany", Array("1")) - callMain("getMany", Array("0", "1")) -end Test - -@experimental -class mainAwait(timeout: Int = 2) extends MainAnnotation[FromString, Future[Any]]: - import MainAnnotation.* - - // This is a toy example, it only works with positional args - def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = - () => p.fromString(arg) - - def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = - () => for arg <- args yield p.fromString(arg) - - def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS))) - -end mainAwait diff --git a/tests/run/main-annotation-homemade-annot-2.scala b/tests/run/main-annotation-homemade-annot-2.scala deleted file mode 100644 index 980241ff93d3..000000000000 --- a/tests/run/main-annotation-homemade-annot-2.scala +++ /dev/null @@ -1,52 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.collection.mutable -import scala.annotation.* -import util.CommandLineParser.FromString - -@myMain()("A") -def foo1(): Unit = println("I was run!") - -@myMain(0)("This should not be printed") -def foo2() = throw new Exception("This should not be run") - -@myMain(1)("Purple smart", "Blue fast", "White fashion", "Yellow quiet", "Orange honest", "Pink loud") -def foo3() = println("Here are some colors:") - -@myMain()() -def foo4() = println("This will be printed, but nothing more.") - -object Test: - val allClazzes: Seq[Class[?]] = - LazyList.from(1).map(i => scala.util.Try(Class.forName("foo" + i.toString))).takeWhile(_.isSuccess).map(_.get) - - def callMains(): Unit = - for (clazz <- allClazzes) - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]()) - - def main(args: Array[String]) = - callMains() -end Test - -// This is a toy example, it only works with positional args -@experimental -class myMain(runs: Int = 3)(after: String*) extends MainAnnotation[FromString, Any]: - import MainAnnotation.* - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = - () => p.fromString(arg) - - def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = - () => for arg <- args yield p.fromString(arg) - - def run(f: () => Any): Unit = - for (_ <- 1 to runs) - f() - if after.length > 0 then println(after.mkString(", ")) - end run - -end myMain diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala deleted file mode 100644 index 4a894777c562..000000000000 --- a/tests/run/main-annotation-homemade-annot-3.scala +++ /dev/null @@ -1,26 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* -import scala.util.CommandLineParser.FromString - -@mainNoArgs def foo() = println("Hello world!") - -object Test: - def main(args: Array[String]) = - val clazz = Class.forName("foo") - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]()) -end Test - -@experimental -class mainNoArgs extends MainAnnotation[FromString, Any]: - import MainAnnotation.* - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - - 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-annot-4.scala b/tests/run/main-annotation-homemade-annot-4.scala deleted file mode 100644 index b50e89523475..000000000000 --- a/tests/run/main-annotation-homemade-annot-4.scala +++ /dev/null @@ -1,27 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* -import scala.util.CommandLineParser.FromString - -@mainManyArgs(1, "B", 3) def foo() = println("Hello world!") - -object Test: - def main(args: Array[String]) = - val clazz = Class.forName("foo") - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]()) -end Test - -@experimental -class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation[FromString, Any]: - import MainAnnotation.* - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - - 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-annot-5.scala b/tests/run/main-annotation-homemade-annot-5.scala deleted file mode 100644 index a129a51da7eb..000000000000 --- a/tests/run/main-annotation-homemade-annot-5.scala +++ /dev/null @@ -1,28 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* -import scala.util.CommandLineParser.FromString - -@mainManyArgs(Some(1)) def foo() = println("Hello world!") -@mainManyArgs(None) def bar() = println("Hello world!") - -object Test: - def main(args: Array[String]) = - for (methodName <- List("foo", "bar")) - val clazz = Class.forName(methodName) - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]()) -end Test - -@experimental -class mainManyArgs(o: Option[Int]) extends MainAnnotation[FromString, Any]: - import MainAnnotation.* - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - - 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-annot-6.scala b/tests/run/main-annotation-homemade-annot-6.scala deleted file mode 100644 index 5a92e6382d3d..000000000000 --- a/tests/run/main-annotation-homemade-annot-6.scala +++ /dev/null @@ -1,65 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* - -/** Foo docs */ -@myMain def foo(i: Int, j: String = "2") = println(s"foo($i, $j)") -/** Bar docs - * - * @param i the first parameter - */ -@myMain def bar(@MyParamAnnot(3) i: List[Int], rest: Int*) = println(s"bar($i, ${rest.mkString(", ")})") - -object Test: - def main(args: Array[String]) = - for (methodName <- List("foo", "bar")) - val clazz = Class.forName(methodName) - val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]("1", "2")) -end Test - -@experimental -class myMain extends MainAnnotation[Make, Any]: - import MainAnnotation.* - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = - def paramInfoString(paramInfo: Parameter) = - import paramInfo.* - s" Parameter(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)" - println( - s"""command( - | ${args.mkString("Array(", ", ", ")")}, - | ${info.name}, - | "${info.documentation}", - | ${info.parameters.map(paramInfoString).mkString("Seq(\n", ",\n", "\n )*")} - |)""".stripMargin) - Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: Make[T]): () => T = - () => p.make - - def varargGetter[T](param: Parameter, args: Seq[String])(using p: Make[T]): () => Seq[T] = - println("varargGetter()") - () => Seq(p.make, p.make) - - def run(f: () => Any): Unit = - println("run()") - f() - println() - -@experimental -case class MyParamAnnot(n: Int) extends MainAnnotation.ParameterAnnotation - -trait Make[T]: - def make: T - -given Make[Int] with - def make: Int = 42 - - -given Make[String] with - def make: String = "abc" - -given [T: Make]: Make[List[T]] with - def make: List[T] = List(summon[Make[T]].make) diff --git a/tests/run/main-annotation-homemade-parser-1.scala b/tests/run/main-annotation-homemade-parser-1.scala deleted file mode 100644 index 94d43bf19cc5..000000000000 --- a/tests/run/main-annotation-homemade-parser-1.scala +++ /dev/null @@ -1,30 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-homemade-parser-2.scala deleted file mode 100644 index 4f40f9b42b27..000000000000 --- a/tests/run/main-annotation-homemade-parser-2.scala +++ /dev/null @@ -1,33 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-homemade-parser-3.scala deleted file mode 100644 index 066e40f1b3a0..000000000000 --- a/tests/run/main-annotation-homemade-parser-3.scala +++ /dev/null @@ -1,29 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-homemade-parser-4.scala deleted file mode 100644 index 668aa040380c..000000000000 --- a/tests/run/main-annotation-homemade-parser-4.scala +++ /dev/null @@ -1,50 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-homemade-parser-5.scala deleted file mode 100644 index 123631312ef7..000000000000 --- a/tests/run/main-annotation-homemade-parser-5.scala +++ /dev/null @@ -1,29 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-multiple.scala deleted file mode 100644 index dbc66d0df9ca..000000000000 --- a/tests/run/main-annotation-multiple.scala +++ /dev/null @@ -1,28 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-named-params.scala deleted file mode 100644 index 4cfa2c8049b4..000000000000 --- a/tests/run/main-annotation-named-params.scala +++ /dev/null @@ -1,30 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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-newMain.scala b/tests/run/main-annotation-newMain.scala deleted file mode 100644 index 5b00a46ce7e9..000000000000 --- a/tests/run/main-annotation-newMain.scala +++ /dev/null @@ -1,323 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -import scala.annotation.* -import collection.mutable -import scala.util.CommandLineParser.FromString - -@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 - - - -@experimental -final class newMain extends MainAnnotation[FromString, Any]: - import newMain._ - import MainAnnotation._ - - private inline val argMarker = "--" - private inline 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 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 - - private var info: Info = _ // TODO remove this var - - - /** 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 def getAliases(param: Parameter): Seq[String] = - param.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) - - private def getAlternativeNames(param: Parameter): Seq[String] = - getAliases(param).filter(nameIsValid(_)) - - private def getShortNames(param: Parameter): Seq[Char] = - getAliases(param).filter(shortNameIsValid(_)).map(_(0)) - - 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 && shortNameIsValidChar(name(0)) - - private inline def shortNameIsValidChar(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 getInvalidNames(param: Parameter): Seq[String | Char] = - getAliases(param).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) - - def command(info: Info, args: Seq[String]): Option[Seq[String]] = - this.info = info - - val namesToCanonicalName: Map[String, String] = info.parameters.flatMap( - infos => - val names = getAlternativeNames(infos) - val canonicalName = infos.name - if nameIsValid(canonicalName) then (canonicalName +: names).map(_ -> canonicalName) - else names.map(_ -> canonicalName) - ).toMap - val shortNamesToCanonicalName: Map[Char, String] = info.parameters.flatMap( - infos => - val names = getShortNames(infos) - val canonicalName = infos.name - if shortNameIsValid(canonicalName) then (canonicalName(0) +: names).map(_ -> canonicalName) - else names.map(_ -> canonicalName) - ).toMap - - val helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) - val shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) - - val (positionalArgs, byNameArgs, invalidByNameArgs) = { - 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 && shortNameIsValidChar(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) - } - - val argStrings: Seq[Seq[String]] = - for paramInfo <- info.parameters yield { - if (paramInfo.isVarargs) { - val byNameGetters = byNameArgs.getOrElse(paramInfo.name, Seq()) - val positionalGetters = positionalArgs.removeAll() - // First take arguments passed by name, then those passed by position - byNameGetters ++ positionalGetters - } else { - byNameArgs.get(paramInfo.name) match - case Some(Nil) => - throw AssertionError(s"${paramInfo.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 ${paramInfo.name}: ${argValues.mkString(", ")}") - Nil - else - List(argValues.last) - case None => - if positionalArgs.length > 0 then - List(positionalArgs.dequeue()) - else if paramInfo.hasDefault then - Nil - else - error(s"missing argument for ${paramInfo.name}") - Nil - } - } - - // Check aliases unicity - val nameAndCanonicalName = info.parameters.flatMap { - case paramInfo => (paramInfo.name +: getAlternativeNames(paramInfo) ++: getShortNames(paramInfo)).map(_ -> paramInfo.name) - } - 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 = info.parameters.flatMap(getInvalidNames) - 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() - None - else if errors.nonEmpty then - for msg <- errors do println(s"Error: $msg") - usage() - None - else - Some(argStrings.flatten) - end command - - private def usage(): Unit = - def argsUsage: Seq[String] = - for (infos <- info.parameters) - yield { - val canonicalName = getNameWithMarker(infos.name) - val shortNames = getShortNames(infos).map(getNameWithMarker) - val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) - val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") - val shortTypeName = infos.typeName.split('.').last - if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]" - else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]" - 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 usageBeginning = s"Usage: ${info.name} " - 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 (info.documentation.nonEmpty) - println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) - if (info.parameters.nonEmpty) { - val argNameShift = 2 - val argDocShift = argNameShift + 2 - - println("Arguments:") - for infos <- info.parameters do - 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.split('.').last}") - if infos.isVarargs then argDoc.append(" (vararg)") - else if infos.hasDefault then argDoc.append(" (optional)") - - if (infos.documentation.nonEmpty) { - val shiftedDoc = - infos.documentation.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 convert[T](argName: String, arg: String, p: FromString[T]): () => T = - p.fromStringOption(arg) match - case Some(t) => () => t - case None => error(s"invalid argument for $argName: $arg") - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = { - if arg.nonEmpty then convert(param.name, arg, p) - else defaultArgument match - case Some(defaultGetter) => defaultGetter - case None => error(s"missing argument for ${param.name}") - } - - def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = { - val getters = args.map(arg => convert(param.name, arg, p)) - () => getters.map(_()) - } - - def run(execProgram: () => Any): Unit = { - if errors.nonEmpty then - for msg <- errors do println(s"Error: $msg") - usage() - else - execProgram() - } - -end newMain - -object newMain: - @experimental - final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation -end newMain diff --git a/tests/run/main-annotation-no-parameters-no-parens.scala b/tests/run/main-annotation-no-parameters-no-parens.scala deleted file mode 100644 index b62fd55538de..000000000000 --- a/tests/run/main-annotation-no-parameters-no-parens.scala +++ /dev/null @@ -1,23 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-no-parameters.scala deleted file mode 100644 index fc92a5680e07..000000000000 --- a/tests/run/main-annotation-no-parameters.scala +++ /dev/null @@ -1,23 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-overload.scala deleted file mode 100644 index 60f9b68a58a2..000000000000 --- a/tests/run/main-annotation-overload.scala +++ /dev/null @@ -1,35 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-param-annot-1.scala deleted file mode 100644 index 5cf29b9f4efb..000000000000 --- a/tests/run/main-annotation-param-annot-1.scala +++ /dev/null @@ -1,111 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-param-annot-2.scala deleted file mode 100644 index 76033f24e614..000000000000 --- a/tests/run/main-annotation-param-annot-2.scala +++ /dev/null @@ -1,63 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-param-annot-invalid-params.scala deleted file mode 100644 index 46bc812863b1..000000000000 --- a/tests/run/main-annotation-param-annot-invalid-params.scala +++ /dev/null @@ -1,48 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-return-type-1.scala deleted file mode 100644 index 1366cceeba8a..000000000000 --- a/tests/run/main-annotation-return-type-1.scala +++ /dev/null @@ -1,26 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-return-type-2.scala deleted file mode 100644 index e2dc6b8ae4e6..000000000000 --- a/tests/run/main-annotation-return-type-2.scala +++ /dev/null @@ -1,29 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-short-name.scala deleted file mode 100644 index 4a179fb793e1..000000000000 --- a/tests/run/main-annotation-short-name.scala +++ /dev/null @@ -1,25 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-simple.scala deleted file mode 100644 index 7d2fd501849b..000000000000 --- a/tests/run/main-annotation-simple.scala +++ /dev/null @@ -1,22 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-top-level.scala deleted file mode 100644 index 3e2bb7bb2fb4..000000000000 --- a/tests/run/main-annotation-top-level.scala +++ /dev/null @@ -1,37 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-types.scala deleted file mode 100644 index 0ee6220a1196..000000000000 --- a/tests/run/main-annotation-types.scala +++ /dev/null @@ -1,35 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-vararg-1.scala deleted file mode 100644 index 0227054e0189..000000000000 --- a/tests/run/main-annotation-vararg-1.scala +++ /dev/null @@ -1,30 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-vararg-2.scala deleted file mode 100644 index 8521795388b2..000000000000 --- a/tests/run/main-annotation-vararg-2.scala +++ /dev/null @@ -1,33 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-wrong-param-1.scala deleted file mode 100644 index 6c9e9e991136..000000000000 --- a/tests/run/main-annotation-wrong-param-1.scala +++ /dev/null @@ -1,26 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-wrong-param-names.scala deleted file mode 100644 index 90622d543bf1..000000000000 --- a/tests/run/main-annotation-wrong-param-names.scala +++ /dev/null @@ -1,26 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-wrong-param-number.scala deleted file mode 100644 index b8ef8c0ea9e7..000000000000 --- a/tests/run/main-annotation-wrong-param-number.scala +++ /dev/null @@ -1,26 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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.scala b/tests/run/main-annotation-wrong-param-type.scala deleted file mode 100644 index 0fbae70a48a5..000000000000 --- a/tests/run/main-annotation-wrong-param-type.scala +++ /dev/null @@ -1,28 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -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 diff --git a/tests/run/main-calculator-example.scala b/tests/run/main-calculator-example.scala deleted file mode 100644 index fc2e1397009b..000000000000 --- a/tests/run/main-calculator-example.scala +++ /dev/null @@ -1,67 +0,0 @@ -//> using options -experimental -Yno-experimental -// scalajs: --skip - -sealed trait Expression: - def eval(): Int -case class Number(n: Int) extends Expression: - def eval(): Int = n -case class Plus(e1: Expression, e2: Expression) extends Expression: - def eval(): Int = e1.eval() + e2.eval() - -//// - -@main def sum(n1: Int, n2: Int) = - val x1 = Number(n1) - val x2 = Number(n2) - val expr = Plus(x1, x2) - println(s"Expression: $expr") - val result = expr.eval() - println(s"Calculated: $result") - -//// - -import scala.annotation.{ MainAnnotation, experimental } -import scala.annotation.MainAnnotation.{ Info, Parameter } -import scala.util.CommandLineParser.FromString - -class showAndEval extends MainAnnotation[FromString, Expression]: - def command(info: Info, args: Seq[String]): Option[Seq[String]] = - assert(info.parameters.forall(param => param.typeName == "Number"), "Only Number parameters allowed") - println(s"executing ${info.name} with inputs: ${args.mkString(" ")}") - Some(args) - - def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = - () => parser.fromString(arg) - - def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = - () => args.map(arg => parser.fromString(arg)) - - def run(program: () => Expression): Unit = - val expr = program() - println(s"Expression: $expr") - val result = expr.eval() - println(s"Calculated: $result") -end showAndEval - -given FromString[Number] = (x: String) => Number(x.toInt) - -//// - -@showAndEval def sum2(x1: Number, x2: Number): Expression = - sumAll(x1, x2) - -@showAndEval def sumAll(xs: Number*): Expression = - if xs.isEmpty then Number(0) - else xs.tail.fold[Expression](xs.head)(Plus) - -//// - -@main def Test: Unit = - 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) - callMain("sum", "1", "2") - callMain("sum2", "2", "3") - callMain("sumAll", "1", "2", "3") -end Test