diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 050ea39e4394..0b778a5861c4 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -162,9 +162,14 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter val (cl1, cl2) = if (classSymbol.effectiveName.toString < dupClassSym.effectiveName.toString) (classSymbol, dupClassSym) else (dupClassSym, classSymbol) + val same = classSymbol.effectiveName.toString == dupClassSym.effectiveName.toString ctx.atPhase(ctx.typerPhase) { - the[Context].warning(s"${cl1.show} differs only in case from ${cl2.showLocated}. " + - "Such classes will overwrite one another on case-insensitive filesystems.", cl1.sourcePos) + if (same) + the[Context].warning( // FIXME: This should really be an error, but then FromTasty tests fail + s"${cl1.show} and ${cl2.showLocated} produce classes that overwrite one another", cl1.sourcePos) + else + the[Context].warning(s"${cl1.show} differs only in case from ${cl2.showLocated}. " + + "Such classes will overwrite one another on case-insensitive filesystems.", cl1.sourcePos) } } } diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala new file mode 100644 index 000000000000..e9d44a564412 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -0,0 +1,107 @@ +package dotty.tools.dotc +package ast + +import core._ +import Symbols._, Types._, Contexts._, Decorators._, util.Spans._, Flags._, Constants._ +import StdNames.nme +import ast.Trees._ + +/** Generate proxy classes for @main functions. + * A function like + * + * @main def f(x: S, ys: T*) = ... + * + * would be translated to something like + * + * import CommandLineParser._ + * class f { + * @static def main(args: Array[String]): Unit = + * try + * f( + * parseArgument[S](args, 0), + * parseRemainingArguments[T](args, 1): _* + * ) + * catch case err: ParseError => showError(err) + * } + */ +object MainProxies { + + def mainProxies(stats: List[tpd.Tree]) given Context: List[untpd.Tree] = { + import tpd._ + def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { + case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => + stat.symbol :: Nil + case stat @ TypeDef(name, impl: Template) if stat.symbol.is(Module) => + mainMethods(impl.body) + case _ => + Nil + } + mainMethods(stats).flatMap(mainProxy) + } + + import untpd._ + def mainProxy(mainFun: Symbol) given (ctx: Context): List[TypeDef] = { + val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span + def pos = mainFun.sourcePos + val argsRef = Ident(nme.args) + + def addArgs(call: untpd.Tree, mt: MethodType, idx: Int): untpd.Tree = { + if (mt.isImplicitMethod) { + ctx.error(s"@main method cannot have implicit parameters", pos) + call + } + else { + val args = mt.paramInfos.zipWithIndex map { + (formal, n) => + val (parserSym, formalElem) = + if (formal.isRepeatedParam) (defn.CLP_parseRemainingArguments, formal.argTypes.head) + else (defn.CLP_parseArgument, formal) + val arg = Apply( + TypeApply(ref(parserSym.termRef), TypeTree(formalElem) :: Nil), + argsRef :: Literal(Constant(idx + n)) :: Nil) + if (formal.isRepeatedParam) repeated(arg) else arg + } + val call1 = Apply(call, args) + mt.resType match { + case restpe: MethodType => + if (mt.paramInfos.lastOption.getOrElse(NoType).isRepeatedParam) + ctx.error(s"varargs parameter of @main method must come last", pos) + addArgs(call1, restpe, idx + args.length) + case _ => + call1 + } + } + } + + var result: List[TypeDef] = Nil + if (!mainFun.owner.isStaticOwner) + ctx.error(s"@main method is not statically accessible", pos) + else { + var call = ref(mainFun.termRef) + mainFun.info match { + case _: ExprType => + case mt: MethodType => + call = addArgs(call, mt, 0) + case _: PolyType => + ctx.error(s"@main method cannot have type parameters", pos) + case _ => + ctx.error(s"@main can only annotate a method", pos) + } + val errVar = Ident(nme.error) + val handler = CaseDef( + Typed(errVar, TypeTree(defn.CLP_ParseError.typeRef)), + EmptyTree, + Apply(ref(defn.CLP_showError.termRef), errVar :: Nil)) + val body = Try(call, handler :: Nil, EmptyTree) + val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) + .withFlags(Param) + val mainMeth = DefDef(nme.main, Nil, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) + .withFlags(JavaStatic) + val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) + val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) + .withFlags(Final) + if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan) :: Nil + } + result + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala index 2005a2add3b2..32f73dbd699a 100644 --- a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -22,10 +22,13 @@ class JavaPlatform extends Platform { // The given symbol is a method with the right name and signature to be a runnable java program. def isMainMethod(sym: SymDenotation)(implicit ctx: Context): Boolean = - (sym.name == nme.main) && (sym.info match { - case MethodTpe(_, defn.ArrayOf(el) :: Nil, restpe) => el =:= defn.StringType && (restpe isRef defn.UnitClass) - case _ => false - }) + sym.name == nme.main && + (sym.owner.is(Module) || sym.owner.isClass && !sym.owner.is(Trait) && sym.is(JavaStatic)) && { + sym.info match { + case MethodTpe(_, defn.ArrayOf(el) :: Nil, restpe) => el =:= defn.StringType && (restpe isRef defn.UnitClass) + case _ => false + } + } /** Update classpath with a substituted subentry */ def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = currentClassPath.get match { diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index cc90e3bae56c..a8925b3ca257 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -10,12 +10,14 @@ import util.Spans.Span object Annotations { + def annotClass(tree: Tree) given Context = + if (tree.symbol.isConstructor) tree.symbol.owner + else tree.tpe.typeSymbol + abstract class Annotation { def tree(implicit ctx: Context): Tree - def symbol(implicit ctx: Context): Symbol = - if (tree.symbol.isConstructor) tree.symbol.owner - else tree.tpe.typeSymbol + def symbol(implicit ctx: Context): Symbol = annotClass(tree) def matches(cls: Symbol)(implicit ctx: Context): Boolean = symbol.derivesFrom(cls) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 60df6c4e5fe1..6ef8a232318b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -695,10 +695,16 @@ class Definitions { @threadUnsafe lazy val ValueOfClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.ValueOf")) @threadUnsafe lazy val StatsModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("dotty.tools.dotc.util.Stats")) - @threadUnsafe lazy val Stats_doRecord: SymbolPerRun = perRunSym(StatsModule.requiredMethodRef("doRecord")) + @threadUnsafe lazy val Stats_doRecord: SymbolPerRun = perRunSym(StatsModule.requiredMethodRef("doRecord")) @threadUnsafe lazy val XMLTopScopeModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.xml.TopScope")) + @threadUnsafe lazy val CommandLineParserModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.util.CommandLineParser")) + @threadUnsafe lazy val CLP_ParseError: ClassSymbolPerRun = perRunClass(CommandLineParserModule.requiredClass("ParseError").typeRef) + @threadUnsafe lazy val CLP_parseArgument: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseArgument")) + @threadUnsafe lazy val CLP_parseRemainingArguments: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseRemainingArguments")) + @threadUnsafe lazy val CLP_showError: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("showError")) + @threadUnsafe lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple") def TupleClass(implicit ctx: Context): ClassSymbol = TupleTypeRef.symbol.asClass @threadUnsafe lazy val Tuple_cons: SymbolPerRun = perRunSym(TupleClass.requiredMethodRef("*:")) @@ -712,8 +718,8 @@ class Definitions { def TupleXXL_fromIterator(implicit ctx: Context): Symbol = TupleXXLModule.requiredMethod("fromIterator") - lazy val DynamicTupleModule: Symbol = ctx.requiredModule("scala.runtime.DynamicTuple") - lazy val DynamicTupleModuleClass: Symbol = DynamicTupleModule.moduleClass + @threadUnsafe lazy val DynamicTupleModule: Symbol = ctx.requiredModule("scala.runtime.DynamicTuple") + @threadUnsafe lazy val DynamicTupleModuleClass: Symbol = DynamicTupleModule.moduleClass lazy val DynamicTuple_consIterator: Symbol = DynamicTupleModule.requiredMethod("consIterator") lazy val DynamicTuple_concatIterator: Symbol = DynamicTupleModule.requiredMethod("concatIterator") lazy val DynamicTuple_dynamicApply: Symbol = DynamicTupleModule.requiredMethod("dynamicApply") @@ -724,10 +730,10 @@ class Definitions { lazy val DynamicTuple_dynamicToArray: Symbol = DynamicTupleModule.requiredMethod("dynamicToArray") lazy val DynamicTuple_productToArray: Symbol = DynamicTupleModule.requiredMethod("productToArray") - lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction") + @threadUnsafe lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction") def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass - lazy val InternalTupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.internal.TupledFunction") + @threadUnsafe lazy val InternalTupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.internal.TupledFunction") def InternalTupleFunctionClass(implicit ctx: Context): ClassSymbol = InternalTupledFunctionTypeRef.symbol.asClass def InternalTupleFunctionModule(implicit ctx: Context): Symbol = ctx.requiredModule("scala.internal.TupledFunction") @@ -751,6 +757,7 @@ class Definitions { @threadUnsafe lazy val ForceInlineAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.forceInline")) @threadUnsafe lazy val InlineParamAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.annotation.internal.InlineParam")) @threadUnsafe lazy val InvariantBetweenAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.annotation.internal.InvariantBetween")) + @threadUnsafe lazy val MainAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.main")) @threadUnsafe lazy val MigrationAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.annotation.migration")) @threadUnsafe lazy val NativeAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.native")) @threadUnsafe lazy val RepeatedAnnot: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.annotation.internal.Repeated")) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 006567d89371..f626cd01809a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -250,6 +250,14 @@ abstract class Reporter extends interfaces.ReporterResult { */ def errorsReported: Boolean = hasErrors + /** Run `op` and return `true` if errors were reported by this reporter. + */ + def reportsErrorsFor(op: Context => Unit) given (ctx: Context): Boolean = { + val initial = errorCount + op(ctx) + errorCount > initial + } + private[this] var reportedFeaturesUseSites = Set[Symbol]() def isReportedFeatureUseSite(featureTrait: Symbol): Boolean = diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 5145f138da7f..e9bf617a913f 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -230,7 +230,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder allNonLocalClassesInSrc += cl - if (sym.isStatic && defType == DefinitionType.Module && ctx.platform.hasMainMethod(sym)) { + if (sym.isStatic && ctx.platform.hasMainMethod(sym)) { _mainClasses += name } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 93b1754f5e7e..0b0e06c6a652 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1136,6 +1136,20 @@ trait Checking { } } + /** check that annotation `annot` is applicable to symbol `sym` */ + def checkAnnotApplicable(annot: Tree, sym: Symbol) given (ctx: Context): Boolean = + !ctx.reporter.reportsErrorsFor { implicit ctx => + val annotCls = Annotations.annotClass(annot) + val pos = annot.sourcePos + if (annotCls == defn.MainAnnot) { + if (!sym.isRealMethod) + ctx.error(em"@main annotation cannot be applied to $sym", pos) + if (!sym.owner.is(Module) || !sym.owner.isStatic) + ctx.error(em"$sym cannot be a @main method since it cannot be accessed statically", pos) + } + // TODO: Add more checks here + } + /** Check that symbol's external name does not clash with symbols defined in the same scope */ def checkNoAlphaConflict(stats: List[Tree])(implicit ctx: Context): Unit = { var seen = Set[Name]() @@ -1157,6 +1171,7 @@ trait ReChecking extends Checking { override def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(implicit ctx: Context): Unit = () override def checkRefsLegal(tree: tpd.Tree, badOwner: Symbol, allowed: (Name, Symbol) => Boolean, where: String)(implicit ctx: Context): Unit = () override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(implicit ctx: Context): Unit = () + override def checkAnnotApplicable(annot: Tree, sym: Symbol) given (ctx: Context): Boolean = true } trait NoChecking extends ReChecking { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 237531d01fcb..0c0f4e95dcb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3,7 +3,7 @@ package dotc package typer import core._ -import ast.{tpd, _} +import ast._ import Trees._ import Constants._ import StdNames._ @@ -1460,7 +1460,8 @@ class Typer extends Namer sym.annotations.foreach(_.ensureCompleted) lazy val annotCtx = annotContext(mdef, sym) // necessary in order to mark the typed ahead annotations as definitely typed: - untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation(_)(annotCtx)) + for annot <- untpd.modsDeco(mdef).mods.annotations do + checkAnnotApplicable(typedAnnotation(annot)(annotCtx), sym) } def typedAnnotation(annot: untpd.Tree)(implicit ctx: Context): Tree = { @@ -1793,7 +1794,9 @@ class Typer extends Namer case pid1: RefTree if pkg.exists => if (!pkg.is(Package)) ctx.error(PackageNameAlreadyDefined(pkg), tree.sourcePos) val packageCtx = ctx.packageContext(tree, pkg) - val stats1 = typedStats(tree.stats, pkg.moduleClass)(packageCtx) + var stats1 = typedStats(tree.stats, pkg.moduleClass)(packageCtx) + if (!ctx.isAfterTyper) + stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))(packageCtx)._2 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) case _ => // Package will not exist if a duplicate type has already been entered, see `tests/neg/1708.scala` diff --git a/docs/docs/reference/changed-features/main-functions.md b/docs/docs/reference/changed-features/main-functions.md new file mode 100644 index 000000000000..91b5a95f5467 --- /dev/null +++ b/docs/docs/reference/changed-features/main-functions.md @@ -0,0 +1,82 @@ +--- +layout: doc-page +title: "Main Methods" +--- + +Scala 3 offers a new way to define programs that can be invoked from the command line: +A `@main` annotation on a method turns this method into an executable program. +Example: +```scala + @main def happyBirthday(age: Int, name: String, others: String*) = { + val suffix = + (age % 100) match { + case 11 | 12 | 13 => "th" + case _ => + (age % 10) match { + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + } + } + val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do bldr.append(" and ").append(other) + bldr.toString + } +``` +This would generate a main program `happyBirthday` that could be called like this +``` +> scala happyBirthday 23 Lisa Peter +Happy 23rd Birthday, Lisa and Peter! +``` +A `@main` annotated method can be written either at the top-level or in a statically accessible object. The name of the program is in each case the name of the method, without any object prefixes. The `@main` method can have an arbitrary number of parameters. +For each parameter type there must be an instance of the `scala.util.FromString` typeclass +that is used to convert an argument string to the required parameter type. +The parameter list of a main method can end in a repeated parameter that then +takes all remaining arguments given on the command line. + +The program implemented from a `@main` method checks that there are enough arguments on +the command line to fill in all parameters, and that argument strings are convertible to +the required types. If a check fails, the program is terminated with an error message. +Examples: +``` +> scala happyBirthday 22 +Illegal command line after first argument: more arguments expected +> scala happyBirthday sixty Fred +Illegal command line: java.lang.NumberFormatException: For input string: "sixty" +``` +The Scala compiler generates a program from a `@main` method `f` as follows: + + - It creates a class named `f` in the package where the `@main` method was found + - The class has a static method `main` with the usual signature. It takes an `Array[String]` + as argument and returns `Unit`. + - The generated `main` method calls method `f` with arguments converted using + methods in the `scala.util.CommandLineParser` object. + +For instance, the `happyBirthDay` method above would generate additional code equivalent to the following class: +```scala + final class happyBirthday { + import scala.util.{CommndLineParser => CLP} + def main(args: Array[String]): Unit = + try + happyBirthday( + CLP.parseArgument[Int](args, 0), + CLP.parseArgument[String](args, 1), + CLP.parseRemainingArguments[String](args, 2)) + catch { + case error: CLP.ParseError => CLP.showError(error) + } + } +``` +**Note**: The `` modifier above expresses that the `main` method is generated +as a static method of class `happyBirthDay`. It is not available for user programs in Scala. Regular "static" members are generated in Scala using objects instead. + +`@main` methods are the recommended scheme to generate programs that can be invoked from the command line in Scala 3. They replace the previous scheme to write program as objects with a special `App` parent class. In Scala 2, `happyBirthday` could be written also like this: +```scala + object happyBirthday extends App { + // needs by-hand parsing of arguments vector + ... + } +``` +The previous functionality of `App`, which relied on the "magic" `DelayedInit` trait, is no longer available. `App` still exists in limited form for now, but it does not support command line arguments and will be deprecated in the future. If programs need to cross-build +between Scala 2 and Scala 3, it is recommended to use an explicit `main` method with an `Array[String]` argument instead. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 403657a7d7b7..9da7fa35f921 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -131,6 +131,8 @@ sidebar: url: docs/reference/changed-features/compiler-plugins.html - title: Lazy Vals initialization url: docs/reference/changed-features/lazy-vals-init.html + - title: Main Functions + url: docs/reference/changed-features/main-functions.html - title: Dropped Features subsection: - title: DelayedInit diff --git a/library/src/scala/main.scala b/library/src/scala/main.scala new file mode 100644 index 000000000000..057fdad4c2fb --- /dev/null +++ b/library/src/scala/main.scala @@ -0,0 +1,13 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala + +/** An annotation that designates a main function + */ +class main extends scala.annotation.Annotation {} diff --git a/library/src/scala/util/CommandLineParser.scala b/library/src/scala/util/CommandLineParser.scala new file mode 100644 index 000000000000..fb4abdc4ecf7 --- /dev/null +++ b/library/src/scala/util/CommandLineParser.scala @@ -0,0 +1,44 @@ +package scala.util + +/** A utility object to support command line parsing for @main methods */ +object CommandLineParser { + + /** An exception raised for an illegal command line + * @param idx The index of the argument that's faulty (starting from 0) + * @param msg The error message + */ + class ParseError(val idx: Int, val msg: String) extends Exception + + /** Parse command line argument `s`, which has index `n`, as a value of type `T` + * @throws ParseError if argument cannot be converted to type `T`. + */ + def parseString[T](str: String, n: Int) given (fs: FromString[T]): T = { + try fs.fromString(str) + catch { + case ex: IllegalArgumentException => throw ParseError(n, ex.toString) + } + } + + /** Parse `n`'th argument in `args` (counting from 0) as a value of type `T` + * @throws ParseError if argument does not exist or cannot be converted to type `T`. + */ + def parseArgument[T](args: Array[String], n: Int) given (fs: FromString[T]): T = + if n < args.length then parseString(args(n), n) + else throw ParseError(n, "more arguments expected") + + /** Parse all arguments from `n`'th one (counting from 0) as a list of values of type `T` + * @throws ParseError if some of the arguments cannot be converted to type `T`. + */ + def parseRemainingArguments[T](args: Array[String], n: Int) given (fs: FromString[T]): List[T] = + if n < args.length then parseString(args(n), n) :: parseRemainingArguments(args, n + 1) + else Nil + + /** Print error message explaining given ParserError */ + def showError(err: ParseError): Unit = { + val where = + if err.idx == 0 then "" + else if err.idx == 1 then " after first argument" + else s" after ${err.idx} arguments" + println(s"Illegal command line$where: ${err.msg}") + } +} diff --git a/library/src/scala/util/FromString.scala b/library/src/scala/util/FromString.scala new file mode 100644 index 000000000000..8da6484efa60 --- /dev/null +++ b/library/src/scala/util/FromString.scala @@ -0,0 +1,47 @@ +package scala.util + +trait FromString[T] { + /** Can throw java.lang.IllegalArgumentException */ + def fromString(s: String): T + + def fromStringOption(s: String): Option[T] = + try Some(fromString(s)) + catch { + case ex: IllegalArgumentException => None + } +} + +object FromString { + + given as FromString[String] { + def fromString(s: String) = s + } + + given as FromString[Boolean] { + def fromString(s: String) = s.toBoolean + } + + given as FromString[Byte] { + def fromString(s: String) = s.toByte + } + + given as FromString[Short] { + def fromString(s: String) = s.toShort + } + + given as FromString[Int] { + def fromString(s: String) = s.toInt + } + + given as FromString[Long] { + def fromString(s: String) = s.toLong + } + + given as FromString[Float] { + def fromString(s: String) = s.toFloat + } + + given as FromString[Double] { + def fromString(s: String) = s.toDouble + } +} diff --git a/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala b/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala new file mode 100644 index 000000000000..407323591c87 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala @@ -0,0 +1,4 @@ + +object foo { + @main def foo(x: Int) = () // error: class foo and object foo produce classes that overwrite one another +} diff --git a/tests/neg/main-functions-doubledef.scala b/tests/neg/main-functions-doubledef.scala new file mode 100644 index 000000000000..b70106526ea4 --- /dev/null +++ b/tests/neg/main-functions-doubledef.scala @@ -0,0 +1,10 @@ + +class bar +object bar { + @main def bar(x: Int) = () // error: class bar has already been compiled once during this run +} + +object baz { + @main def bam(x: Int): Unit = () + @main def bam(x: String): Unit = () // error: class bam has already been compiled once during this run +} diff --git a/tests/neg/main-functions.scala b/tests/neg/main-functions.scala new file mode 100644 index 000000000000..e142c67fd482 --- /dev/null +++ b/tests/neg/main-functions.scala @@ -0,0 +1,4 @@ +class TC +object Test1 { + @main def f(x: TC) = () // error: no implicit argument of type util.FromString[TC] was found +} diff --git a/tests/neg/main-functions2.scala b/tests/neg/main-functions2.scala new file mode 100644 index 000000000000..98b185adb0c6 --- /dev/null +++ b/tests/neg/main-functions2.scala @@ -0,0 +1,13 @@ +object Test2 { + @main val x = 2 // error: @main annotation cannot be applied to value x +} + +class Foo { + @main def f = () // error: method f cannot be a @main method since it cannot be accessed statically +} + +@main def g(x: Int*)(y: Int*) = () // error: varargs parameter of @main method must come last + +@main def h[T: util.FromString](x: T) = () // error: @main method cannot have type parameters + +@main def i(x: Int) given Foo = () // error: @main method cannot have implicit parameters diff --git a/tests/pos/reference/main-functions.scala b/tests/pos/reference/main-functions.scala new file mode 100644 index 000000000000..f4f84841c227 --- /dev/null +++ b/tests/pos/reference/main-functions.scala @@ -0,0 +1,16 @@ +@main def happyBirthday(age: Int, name: String, others: String*) = { + val suffix = + (age % 100) match { + case 11 | 12 | 13 => "th" + case _ => + (age % 10) match { + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + } + } + val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do bldr.append(" and ").append(other) + bldr.toString +} \ No newline at end of file diff --git a/tests/run/main-functions.check b/tests/run/main-functions.check new file mode 100644 index 000000000000..af5626b4a114 --- /dev/null +++ b/tests/run/main-functions.check @@ -0,0 +1 @@ +Hello, world! diff --git a/tests/run/main-functions.scala b/tests/run/main-functions.scala new file mode 100644 index 000000000000..4c220fc667b3 --- /dev/null +++ b/tests/run/main-functions.scala @@ -0,0 +1,7 @@ +@main def Test = + println("Hello, world!") + +object A { + @main def foo(x: Int, y: String, zs: Float*) = + println(s"I found: $x, $y, $zs") +}