From a5df0e72b417cc420f734804fd075d476290a15d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 20 Jul 2019 17:19:20 +0200 Subject: [PATCH 01/13] Implement @main functions --- .../dotty/tools/dotc/ast/MainProxies.scala | 100 ++++++++++++++++++ .../dotty/tools/dotc/core/Definitions.scala | 17 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +- library/src/scala/main.scala | 13 +++ .../src/scala/util/CommandLineParser.scala | 55 ++++++++++ library/src/scala/util/FromString.scala | 47 ++++++++ .../main-functions-nameclash.scala | 4 + tests/neg/main-functions-doubledef.scala | 10 ++ tests/neg/main-functions.scala | 15 +++ tests/run/main-functions.check | 1 + tests/run/main-functions.scala | 7 ++ 11 files changed, 268 insertions(+), 7 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/ast/MainProxies.scala create mode 100644 library/src/scala/main.scala create mode 100644 library/src/scala/util/CommandLineParser.scala create mode 100644 library/src/scala/util/FromString.scala create mode 100644 tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala create mode 100644 tests/neg/main-functions-doubledef.scala create mode 100644 tests/neg/main-functions.scala create mode 100644 tests/run/main-functions.check create mode 100644 tests/run/main-functions.scala 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..7ad304585995 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -0,0 +1,100 @@ +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 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 + + val argsRef = Ident(nme.args) + + def addArgs(call: untpd.Tree, formals: List[Type], restpe: Type, idx: Int): untpd.Tree = { + val args = formals.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) + restpe match { + case restpe: MethodType if !restpe.isImplicitMethod => + if (formals.lastOption.getOrElse(NoType).isRepeatedParam) + ctx.error(s"varargs parameter of @main method must come last", mainFun.sourcePos) + addArgs(call1, restpe.paramInfos, restpe.resType, idx + args.length) + case _ => + call1 + } + } + + var result: List[TypeDef] = Nil + if (!mainFun.owner.isStaticOwner) + ctx.error(s"@main method is not statically accessible", mainFun.sourcePos) + else { + var call = ref(mainFun.termRef) + mainFun.info match { + case _: ExprType => + case mt: MethodType => + if (!mt.isImplicitMethod) call = addArgs(call, mt.paramInfos, mt.resultType, 0) + case _: PolyType => + ctx.error(s"@main method cannot have type parameters", mainFun.sourcePos) + case _ => + ctx.error(s"@main can only annotate a method", mainFun.sourcePos) + } + 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) + if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan) :: Nil + } + result + } +} \ No newline at end of file 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/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 237531d01fcb..d98e1c1bcab1 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._ @@ -1793,7 +1793,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/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..9bfb7180b8ef --- /dev/null +++ b/library/src/scala/util/CommandLineParser.scala @@ -0,0 +1,55 @@ +package scala.util + +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` */ + 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` */ + 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` */ + 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}") + } +} + +/* A function like + + @main 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) + } +*/ \ No newline at end of file diff --git a/library/src/scala/util/FromString.scala b/library/src/scala/util/FromString.scala new file mode 100644 index 000000000000..531974f4700e --- /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 { + + delegate for FromString[String] { + def fromString(s: String) = s + } + + delegate for FromString[Boolean] { + def fromString(s: String) = s.toBoolean + } + + delegate for FromString[Byte] { + def fromString(s: String) = s.toByte + } + + delegate for FromString[Short] { + def fromString(s: String) = s.toShort + } + + delegate for FromString[Int] { + def fromString(s: String) = s.toInt + } + + delegate for FromString[Long] { + def fromString(s: String) = s.toLong + } + + delegate for FromString[Float] { + def fromString(s: String) = s.toFloat + } + + delegate for 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..1c0173aa9867 --- /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 differs only in case from object foo +} 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..4cd2ac509cad --- /dev/null +++ b/tests/neg/main-functions.scala @@ -0,0 +1,15 @@ +object Test1 { + @main def f(x: Foo) = () // error: no implicit argument of type util.FromString[Foo] was found +} + +object Test2 { + @main val x = 2 // does nothing, should this be made an error? +} + +class Foo { + @main def f = () // does nothing, should this be made an error? +} + +@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 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") +} From fce621ca6ac36dab6ab3bca441e94556253cdeb6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 20 Jul 2019 19:08:47 +0200 Subject: [PATCH 02/13] Add docs --- .../changed-features/main-functions.md | 82 +++++++++++++++++++ .../src/scala/util/CommandLineParser.scala | 31 +++---- tests/pos/reference/main-functions.scala | 16 ++++ 3 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 docs/docs/reference/changed-features/main-functions.md create mode 100644 tests/pos/reference/main-functions.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..d7c02e03df77 --- /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 + 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 recommende to use an explicit `main` method with an `Array[String] argument instead. diff --git a/library/src/scala/util/CommandLineParser.scala b/library/src/scala/util/CommandLineParser.scala index 9bfb7180b8ef..fb4abdc4ecf7 100644 --- a/library/src/scala/util/CommandLineParser.scala +++ b/library/src/scala/util/CommandLineParser.scala @@ -1,5 +1,6 @@ package scala.util +/** A utility object to support command line parsing for @main methods */ object CommandLineParser { /** An exception raised for an illegal command line @@ -8,7 +9,9 @@ object CommandLineParser { */ 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` */ + /** 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 { @@ -16,12 +19,16 @@ object CommandLineParser { } } - /** Parse `n`'th argument in `args` (counting from 0) as a value of type `T` */ + /** 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` */ + /** 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 @@ -35,21 +42,3 @@ object CommandLineParser { println(s"Illegal command line$where: ${err.msg}") } } - -/* A function like - - @main 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) - } -*/ \ No newline at end of file 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 From 97c1cd25ff66d38f399400cec95d100d12884102 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jul 2019 11:08:33 +0200 Subject: [PATCH 03/13] Make main proxies final and docs fixes --- compiler/src/dotty/tools/dotc/ast/MainProxies.scala | 1 + docs/docs/reference/changed-features/main-functions.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 7ad304585995..184e6e1f033f 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -93,6 +93,7 @@ object MainProxies { .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 diff --git a/docs/docs/reference/changed-features/main-functions.md b/docs/docs/reference/changed-features/main-functions.md index d7c02e03df77..91b5a95f5467 100644 --- a/docs/docs/reference/changed-features/main-functions.md +++ b/docs/docs/reference/changed-features/main-functions.md @@ -4,7 +4,7 @@ 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. +A `@main` annotation on a method turns this method into an executable program. Example: ```scala @main def happyBirthday(age: Int, name: String, others: String*) = { @@ -55,7 +55,7 @@ The Scala compiler generates a program from a `@main` method `f` as follows: For instance, the `happyBirthDay` method above would generate additional code equivalent to the following class: ```scala - class happyBirthday { + final class happyBirthday { import scala.util.{CommndLineParser => CLP} def main(args: Array[String]): Unit = try @@ -79,4 +79,4 @@ as a static method of class `happyBirthDay`. It is not available for user progra } ``` 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 recommende to use an explicit `main` method with an `Array[String] argument instead. +between Scala 2 and Scala 3, it is recommended to use an explicit `main` method with an `Array[String]` argument instead. From e1b10c85b5fee780c5c17cd4073e58e8fb88d89d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Jul 2019 11:15:36 +0200 Subject: [PATCH 04/13] Link main-functions.html in sidebar --- docs/sidebar.yml | 2 ++ 1 file changed, 2 insertions(+) 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 From ef16db7aa9e448080b079aeefecb3f83ef2b21a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Jul 2019 10:09:52 +0200 Subject: [PATCH 05/13] Fix typo --- compiler/src/dotty/tools/dotc/ast/MainProxies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 184e6e1f033f..693853113a8a 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -9,7 +9,7 @@ import ast.Trees._ /** Generate proxy classes for @main functions. * A function like * - * @main f(x: S, ys: T*) = ... + * @main def f(x: S, ys: T*) = ... * * would be translated to something like * From a2b6023f6f0c9c85769923b80429ab24b0a050a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Jul 2019 17:06:29 +0200 Subject: [PATCH 06/13] Improve clash error message --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 9 +++++++-- .../fatal-warnings/main-functions-nameclash.scala | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) 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/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala b/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala index 1c0173aa9867..407323591c87 100644 --- a/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala +++ b/tests/neg-custom-args/fatal-warnings/main-functions-nameclash.scala @@ -1,4 +1,4 @@ object foo { - @main def foo(x: Int) = () // error: class foo differs only in case from object foo + @main def foo(x: Int) = () // error: class foo and object foo produce classes that overwrite one another } From c53f465524597bad481f4930eaeaae8ae953e1a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Jul 2019 17:44:16 +0200 Subject: [PATCH 07/13] Allow static class methods as main methods --- .../src/dotty/tools/dotc/config/JavaPlatform.scala | 11 +++++++---- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) 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/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 } From eae23820836ef0e42fbd519838c19866e2e449d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jul 2019 11:58:49 +0200 Subject: [PATCH 08/13] Check @main annotations for applicability Check applicability of annotations only once they are completed --- .../src/dotty/tools/dotc/reporting/Reporter.scala | 8 ++++++++ .../src/dotty/tools/dotc/typer/Checking.scala | 14 ++++++++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/neg/main-functions.scala | 15 ++------------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 006567d89371..a7164d19cc0d 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: given Context => Unit) given Context : Boolean = { + val initial = errorCount + op + errorCount > initial + } + private[this] var reportedFeaturesUseSites = Set[Symbol]() def isReportedFeatureUseSite(featureTrait: Symbol): Boolean = diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 93b1754f5e7e..c605b14bee57 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1136,6 +1136,19 @@ trait Checking { } } + /** check that annotation `annot` is applicable to symbol `sym` */ + def checkAnnotApplicable(annot: Tree, sym: Symbol) given (ctx: Context): Boolean = + !ctx.reporter.reportsErrorsFor { + val pos = annot.sourcePos + if (annot.symbol == 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 +1170,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 d98e1c1bcab1..0c0f4e95dcb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 = { diff --git a/tests/neg/main-functions.scala b/tests/neg/main-functions.scala index 4cd2ac509cad..e142c67fd482 100644 --- a/tests/neg/main-functions.scala +++ b/tests/neg/main-functions.scala @@ -1,15 +1,4 @@ +class TC object Test1 { - @main def f(x: Foo) = () // error: no implicit argument of type util.FromString[Foo] was found + @main def f(x: TC) = () // error: no implicit argument of type util.FromString[TC] was found } - -object Test2 { - @main val x = 2 // does nothing, should this be made an error? -} - -class Foo { - @main def f = () // does nothing, should this be made an error? -} - -@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 From c53772c1742ba9285cb63568f5c9d2970663c4ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jul 2019 12:57:20 +0200 Subject: [PATCH 09/13] Avoid IFT in Reporter#reportsErrorsFor If we make this method take an implicit function rather than a normal one we get a build error with trace: ``` [error] ## Exception when compiling 9 sources to /Users/odersky/workspace/dotty/sbt-bridge/src/target/classes [error] Type scala.ImplicitFunction1 not present [error] sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117) [error] sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125) [error] sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) [error] sun.reflect.generics.repository.ConstructorRepository.getParameterTypes(ConstructorRepository.java:94) [error] java.lang.reflect.Executable.getGenericParameterTypes(Executable.java:283) [error] java.lang.reflect.Method.getGenericParameterTypes(Method.java:283) [error] sbt.internal.inc.ClassToAPI$.parameterTypes(ClassToAPI.scala:566) [error] sbt.internal.inc.ClassToAPI$.methodToDef(ClassToAPI.scala:318) [error] sbt.internal.inc.ClassToAPI$.$anonfun$structure$1(ClassToAPI.scala:182) [error] sbt.internal.inc.ClassToAPI$.$anonfun$mergeMap$1(ClassToAPI.scala:400) [error] scala.collection.TraversableLike.$anonfun$flatMap$1(TraversableLike.scala:240) [error] scala.collection.IndexedSeqOptimized.foreach(IndexedSeqOptimized.scala:32) [error] scala.collection.IndexedSeqOptimized.foreach$(IndexedSeqOptimized.scala:29) [error] scala.collection.mutable.WrappedArray.foreach(WrappedArray.scala:37) [error] scala.collection.TraversableLike.flatMap(TraversableLike.scala:240) [error] scala.collection.TraversableLike.flatMap$(TraversableLike.scala:237) [error] scala.collection.AbstractTraversable.flatMap(Traversable.scala:104) [error] sbt.internal.inc.ClassToAPI$.merge(ClassToAPI.scala:411) [error] sbt.internal.inc.ClassToAPI$.mergeMap(ClassToAPI.scala:400) [error] sbt.internal.inc.ClassToAPI$.structure(ClassToAPI.scala:182) [error] sbt.internal.inc.ClassToAPI$.x$2$lzycompute$1(ClassToAPI.scala:133) [error] sbt.internal.inc.ClassToAPI$.x$2$1(ClassToAPI.scala:133) [error] sbt.internal.inc.ClassToAPI$.instance$lzycompute$1(ClassToAPI.scala:133) [error] sbt.internal.inc.ClassToAPI$.instance$1(ClassToAPI.scala:133) [error] sbt.internal.inc.ClassToAPI$.$anonfun$toDefinitions0$1(ClassToAPI.scala:140) [error] xsbti.api.SafeLazyProxy$$anon$1.get(SafeLazyProxy.scala:26) [error] xsbti.api.SafeLazy$Impl.get(SafeLazy.java:58) ``` --- compiler/src/dotty/tools/dotc/reporting/Reporter.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index a7164d19cc0d..f626cd01809a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -252,9 +252,9 @@ abstract class Reporter extends interfaces.ReporterResult { /** Run `op` and return `true` if errors were reported by this reporter. */ - def reportsErrorsFor(op: given Context => Unit) given Context : Boolean = { + def reportsErrorsFor(op: Context => Unit) given (ctx: Context): Boolean = { val initial = errorCount - op + op(ctx) errorCount > initial } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c605b14bee57..573e645f9216 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1138,7 +1138,7 @@ trait Checking { /** check that annotation `annot` is applicable to symbol `sym` */ def checkAnnotApplicable(annot: Tree, sym: Symbol) given (ctx: Context): Boolean = - !ctx.reporter.reportsErrorsFor { + !ctx.reporter.reportsErrorsFor { implicit ctx => val pos = annot.sourcePos if (annot.symbol == defn.MainAnnot) { if (!sym.isRealMethod) From ffea9f416c53ffc9d2af27eccc1f11bba3a685e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jul 2019 13:56:15 +0200 Subject: [PATCH 10/13] Add test --- tests/neg/main-functions2.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/neg/main-functions2.scala diff --git a/tests/neg/main-functions2.scala b/tests/neg/main-functions2.scala new file mode 100644 index 000000000000..d641e85eb0f8 --- /dev/null +++ b/tests/neg/main-functions2.scala @@ -0,0 +1,11 @@ +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 From 4df2db1720012dd324ace691336be25013436dc2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jul 2019 14:03:32 +0200 Subject: [PATCH 11/13] Use given as for FromString instances --- library/src/scala/util/FromString.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/src/scala/util/FromString.scala b/library/src/scala/util/FromString.scala index 531974f4700e..8da6484efa60 100644 --- a/library/src/scala/util/FromString.scala +++ b/library/src/scala/util/FromString.scala @@ -13,35 +13,35 @@ trait FromString[T] { object FromString { - delegate for FromString[String] { + given as FromString[String] { def fromString(s: String) = s } - delegate for FromString[Boolean] { + given as FromString[Boolean] { def fromString(s: String) = s.toBoolean } - delegate for FromString[Byte] { + given as FromString[Byte] { def fromString(s: String) = s.toByte } - delegate for FromString[Short] { + given as FromString[Short] { def fromString(s: String) = s.toShort } - delegate for FromString[Int] { + given as FromString[Int] { def fromString(s: String) = s.toInt } - delegate for FromString[Long] { + given as FromString[Long] { def fromString(s: String) = s.toLong } - delegate for FromString[Float] { + given as FromString[Float] { def fromString(s: String) = s.toFloat } - delegate for FromString[Double] { + given as FromString[Double] { def fromString(s: String) = s.toDouble } } From 622e8c3ac07957a2083578369db52e30537974ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Jul 2019 18:00:45 +0200 Subject: [PATCH 12/13] Fix annotation applicability checking --- compiler/src/dotty/tools/dotc/core/Annotations.scala | 8 +++++--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) 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/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 573e645f9216..0b0e06c6a652 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1139,8 +1139,9 @@ 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 (annot.symbol == defn.MainAnnot) { + 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) From eb374a15d3918a661e6d06098b26347dcad3319c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 29 Jul 2019 18:53:47 +0200 Subject: [PATCH 13/13] Disallow @main methods with implicit parameters --- .../dotty/tools/dotc/ast/MainProxies.scala | 52 +++++++++++-------- tests/neg/main-functions2.scala | 2 + 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 693853113a8a..e9d44a564412 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -42,44 +42,50 @@ object MainProxies { 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, formals: List[Type], restpe: Type, idx: Int): untpd.Tree = { - val args = formals.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 + 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 } - val call1 = Apply(call, args) - restpe match { - case restpe: MethodType if !restpe.isImplicitMethod => - if (formals.lastOption.getOrElse(NoType).isRepeatedParam) - ctx.error(s"varargs parameter of @main method must come last", mainFun.sourcePos) - addArgs(call1, restpe.paramInfos, restpe.resType, idx + args.length) - case _ => - call1 + 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", mainFun.sourcePos) + ctx.error(s"@main method is not statically accessible", pos) else { var call = ref(mainFun.termRef) mainFun.info match { case _: ExprType => case mt: MethodType => - if (!mt.isImplicitMethod) call = addArgs(call, mt.paramInfos, mt.resultType, 0) + call = addArgs(call, mt, 0) case _: PolyType => - ctx.error(s"@main method cannot have type parameters", mainFun.sourcePos) + ctx.error(s"@main method cannot have type parameters", pos) case _ => - ctx.error(s"@main can only annotate a method", mainFun.sourcePos) + ctx.error(s"@main can only annotate a method", pos) } val errVar = Ident(nme.error) val handler = CaseDef( diff --git a/tests/neg/main-functions2.scala b/tests/neg/main-functions2.scala index d641e85eb0f8..98b185adb0c6 100644 --- a/tests/neg/main-functions2.scala +++ b/tests/neg/main-functions2.scala @@ -9,3 +9,5 @@ class Foo { @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