summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@typesafe.com>2016-05-23 16:56:41 +0200
committerLukas Rytz <lukas.rytz@typesafe.com>2016-05-23 16:56:41 +0200
commit9a703b4a654ce990397d254b0031e366096afcf5 (patch)
treefa4b9c55eea958ed557fb82d380b7c1a61d8556b
parentf640fa0a6d01270b4c16238073c154bf0e71b559 (diff)
parent7f514bba9ff1993ccbfdcf4a37a8045849f1647a (diff)
downloadscala-9a703b4a654ce990397d254b0031e366096afcf5.tar.gz
scala-9a703b4a654ce990397d254b0031e366096afcf5.tar.bz2
scala-9a703b4a654ce990397d254b0031e366096afcf5.zip
Merge pull request #5169 from som-snytt/issue/4625
SI-4625 Recognize App in script
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala108
-rw-r--r--src/reflect/scala/reflect/internal/StdNames.scala1
-rw-r--r--test/files/run/t4625.check1
-rw-r--r--test/files/run/t4625.scala7
-rw-r--r--test/files/run/t4625.script5
-rw-r--r--test/files/run/t4625b.check1
-rw-r--r--test/files/run/t4625b.scala7
-rw-r--r--test/files/run/t4625b.script8
-rw-r--r--test/files/run/t4625c.check3
-rw-r--r--test/files/run/t4625c.scala7
-rw-r--r--test/files/run/t4625c.script7
11 files changed, 111 insertions, 44 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index c04d305f9e..308669256d 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -365,12 +365,15 @@ self =>
val stmts = parseStats()
def mainModuleName = newTermName(settings.script.value)
+
/* If there is only a single object template in the file and it has a
* suitable main method, we will use it rather than building another object
* around it. Since objects are loaded lazily the whole script would have
* been a no-op, so we're not taking much liberty.
*/
- def searchForMain(): Option[Tree] = {
+ def searchForMain(): Tree = {
+ import PartialFunction.cond
+
/* Have to be fairly liberal about what constitutes a main method since
* nothing has been typed yet - for instance we can't assume the parameter
* type will look exactly like "Array[String]" as it could have been renamed
@@ -380,11 +383,15 @@ self =>
case DefDef(_, nme.main, Nil, List(_), _, _) => true
case _ => false
}
- /* For now we require there only be one top level object. */
+ def isApp(t: Tree) = t match {
+ case Template(parents, _, _) => parents.exists(cond(_) { case Ident(tpnme.App) => true })
+ case _ => false
+ }
+ /* We allow only one main module. */
var seenModule = false
- val newStmts = stmts collect {
- case t @ Import(_, _) => t
- case md @ ModuleDef(mods, name, template) if !seenModule && (md exists isMainMethod) =>
+ var disallowed = EmptyTree: Tree
+ val newStmts = stmts.map {
+ case md @ ModuleDef(mods, name, template) if !seenModule && (isApp(template) || md.exists(isMainMethod)) =>
seenModule = true
/* This slightly hacky situation arises because we have no way to communicate
* back to the scriptrunner what the name of the program is. Even if we were
@@ -395,50 +402,63 @@ self =>
*/
if (name == mainModuleName) md
else treeCopy.ModuleDef(md, mods, mainModuleName, template)
- case _ =>
+ case md @ ModuleDef(_, _, _) => md
+ case cd @ ClassDef(_, _, _, _) => cd
+ case t @ Import(_, _) => t
+ case t =>
/* If we see anything but the above, fail. */
- return None
+ if (disallowed.isEmpty) disallowed = t
+ EmptyTree
+ }
+ if (disallowed.isEmpty) makeEmptyPackage(0, newStmts)
+ else {
+ if (seenModule)
+ warning(disallowed.pos.point, "Script has a main object but statement is disallowed")
+ EmptyTree
}
- Some(makeEmptyPackage(0, newStmts))
}
- if (mainModuleName == newTermName(ScriptRunner.defaultScriptMain))
- searchForMain() foreach { return _ }
+ def mainModule: Tree =
+ if (mainModuleName == newTermName(ScriptRunner.defaultScriptMain)) searchForMain() else EmptyTree
- /* Here we are building an AST representing the following source fiction,
- * where `moduleName` is from -Xscript (defaults to "Main") and <stmts> are
- * the result of parsing the script file.
- *
- * {{{
- * object moduleName {
- * def main(args: Array[String]): Unit =
- * new AnyRef {
- * stmts
- * }
- * }
- * }}}
- */
- def emptyInit = DefDef(
- NoMods,
- nme.CONSTRUCTOR,
- Nil,
- ListOfNil,
- TypeTree(),
- Block(List(Apply(gen.mkSuperInitCall, Nil)), literalUnit)
- )
-
- // def main
- def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String)))
- def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.args, mainParamType, EmptyTree))
- def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), gen.mkAnonymousNew(stmts))
-
- // object Main
- def moduleName = newTermName(ScriptRunner scriptMain settings)
- def moduleBody = Template(atInPos(scalaAnyRefConstr) :: Nil, noSelfType, List(emptyInit, mainDef))
- def moduleDef = ModuleDef(NoMods, moduleName, moduleBody)
-
- // package <empty> { ... }
- makeEmptyPackage(0, moduleDef :: Nil)
+ def repackaged: Tree = {
+ /* Here we are building an AST representing the following source fiction,
+ * where `moduleName` is from -Xscript (defaults to "Main") and <stmts> are
+ * the result of parsing the script file.
+ *
+ * {{{
+ * object moduleName {
+ * def main(args: Array[String]): Unit =
+ * new AnyRef {
+ * stmts
+ * }
+ * }
+ * }}}
+ */
+ def emptyInit = DefDef(
+ NoMods,
+ nme.CONSTRUCTOR,
+ Nil,
+ ListOfNil,
+ TypeTree(),
+ Block(List(Apply(gen.mkSuperInitCall, Nil)), literalUnit)
+ )
+
+ // def main
+ def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String)))
+ def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.args, mainParamType, EmptyTree))
+ def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), gen.mkAnonymousNew(stmts))
+
+ // object Main
+ def moduleName = newTermName(ScriptRunner scriptMain settings)
+ def moduleBody = Template(atInPos(scalaAnyRefConstr) :: Nil, noSelfType, List(emptyInit, mainDef))
+ def moduleDef = ModuleDef(NoMods, moduleName, moduleBody)
+
+ // package <empty> { ... }
+ makeEmptyPackage(0, moduleDef :: Nil)
+ }
+
+ mainModule orElse repackaged
}
/* --------------- PLACEHOLDERS ------------------------------------------- */
diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala
index 52558d9395..a0688e129c 100644
--- a/src/reflect/scala/reflect/internal/StdNames.scala
+++ b/src/reflect/scala/reflect/internal/StdNames.scala
@@ -240,6 +240,7 @@ trait StdNames {
final val Any: NameType = "Any"
final val AnyVal: NameType = "AnyVal"
+ final val App: NameType = "App"
final val FlagSet: NameType = "FlagSet"
final val Mirror: NameType = "Mirror"
final val Modifiers: NameType = "Modifiers"
diff --git a/test/files/run/t4625.check b/test/files/run/t4625.check
new file mode 100644
index 0000000000..e4a4d15b87
--- /dev/null
+++ b/test/files/run/t4625.check
@@ -0,0 +1 @@
+Test ran.
diff --git a/test/files/run/t4625.scala b/test/files/run/t4625.scala
new file mode 100644
index 0000000000..44f6225220
--- /dev/null
+++ b/test/files/run/t4625.scala
@@ -0,0 +1,7 @@
+
+import scala.tools.partest.ScriptTest
+
+object Test extends ScriptTest {
+ // must be called Main to get probing treatment in parser
+ override def testmain = "Main"
+}
diff --git a/test/files/run/t4625.script b/test/files/run/t4625.script
new file mode 100644
index 0000000000..600ceacbb6
--- /dev/null
+++ b/test/files/run/t4625.script
@@ -0,0 +1,5 @@
+
+object Main extends Runnable with App {
+ def run() = println("Test ran.")
+ run()
+}
diff --git a/test/files/run/t4625b.check b/test/files/run/t4625b.check
new file mode 100644
index 0000000000..e79539a5c4
--- /dev/null
+++ b/test/files/run/t4625b.check
@@ -0,0 +1 @@
+Misc top-level detritus
diff --git a/test/files/run/t4625b.scala b/test/files/run/t4625b.scala
new file mode 100644
index 0000000000..44f6225220
--- /dev/null
+++ b/test/files/run/t4625b.scala
@@ -0,0 +1,7 @@
+
+import scala.tools.partest.ScriptTest
+
+object Test extends ScriptTest {
+ // must be called Main to get probing treatment in parser
+ override def testmain = "Main"
+}
diff --git a/test/files/run/t4625b.script b/test/files/run/t4625b.script
new file mode 100644
index 0000000000..f21a553dd1
--- /dev/null
+++ b/test/files/run/t4625b.script
@@ -0,0 +1,8 @@
+
+trait X { def x = "Misc top-level detritus" }
+
+object Bumpkus
+
+object Main extends X with App {
+ println(x)
+}
diff --git a/test/files/run/t4625c.check b/test/files/run/t4625c.check
new file mode 100644
index 0000000000..6acb1710b9
--- /dev/null
+++ b/test/files/run/t4625c.check
@@ -0,0 +1,3 @@
+newSource1.scala:2: warning: Script has a main object but statement is disallowed
+val x = "value x"
+ ^
diff --git a/test/files/run/t4625c.scala b/test/files/run/t4625c.scala
new file mode 100644
index 0000000000..44f6225220
--- /dev/null
+++ b/test/files/run/t4625c.scala
@@ -0,0 +1,7 @@
+
+import scala.tools.partest.ScriptTest
+
+object Test extends ScriptTest {
+ // must be called Main to get probing treatment in parser
+ override def testmain = "Main"
+}
diff --git a/test/files/run/t4625c.script b/test/files/run/t4625c.script
new file mode 100644
index 0000000000..16159208e0
--- /dev/null
+++ b/test/files/run/t4625c.script
@@ -0,0 +1,7 @@
+
+val x = "value x"
+val y = "value y"
+
+object Main extends App {
+ println(s"Test ran with $x.")
+}