From 73ca44be579e5100706d174f18025fc4487e9cb9 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 16 May 2016 16:24:50 -0700 Subject: SI-4625 Recognize App in script Cheap name test: if the script object extends "App", take it for a main-bearing parent. Note that if `-Xscript` is not `Main`, the default, then the source is taken as a snippet and there is no attempt to locate an existing `main` method. --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/compiler/scala/tools/nsc/ast/parser/Parsers.scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index c04d305f9e..7af5c505de 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -380,11 +380,15 @@ self => case DefDef(_, nme.main, Nil, List(_), _, _) => true case _ => false } + def isApp(t: Tree) = t match { + case Template(ps, _, _) => ps.exists { case Ident(x) if x.decoded == "App" => true ; case _ => false } + case _ => false + } /* For now we require there only be one top level object. */ var seenModule = false val newStmts = stmts collect { case t @ Import(_, _) => t - case md @ ModuleDef(mods, name, template) if !seenModule && (md exists isMainMethod) => + 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 -- cgit v1.2.3 From 5bcefbe1889a1b8e1f9bac04090428eaaa7b2fd3 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 16 May 2016 22:57:51 -0700 Subject: SI-4625 App is a thing Scripting knows it by name. --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 5 ++++- src/reflect/scala/reflect/internal/StdNames.scala | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/compiler/scala/tools/nsc/ast/parser/Parsers.scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 7af5c505de..358ccb5dc3 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] = { + 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 @@ -381,7 +384,7 @@ self => case _ => false } def isApp(t: Tree) = t match { - case Template(ps, _, _) => ps.exists { case Ident(x) if x.decoded == "App" => true ; case _ => false } + case Template(ps, _, _) => ps.exists(cond(_) { case Ident(tpnme.App) => true }) case _ => false } /* For now we require there only be one top level object. */ 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" -- cgit v1.2.3 From ee365cccaf740d5ec353718556b010137f4cdd4d Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 16 May 2016 23:28:16 -0700 Subject: SI-4625 Permit arbitrary top-level in script In an unwrapped script, where a `main` entry point is discovered in a top-level object, retain all top-level classes. Everything winds up in the default package. --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 4 +++- test/files/run/t4625b.check | 1 + test/files/run/t4625b.scala | 7 +++++++ test/files/run/t4625b.script | 8 ++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t4625b.check create mode 100644 test/files/run/t4625b.scala create mode 100644 test/files/run/t4625b.script (limited to 'src/compiler/scala/tools/nsc/ast/parser/Parsers.scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 358ccb5dc3..1ece580b96 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -384,7 +384,7 @@ self => case _ => false } def isApp(t: Tree) = t match { - case Template(ps, _, _) => ps.exists(cond(_) { case Ident(tpnme.App) => true }) + case Template(parents, _, _) => parents.exists(cond(_) { case Ident(tpnme.App) => true }) case _ => false } /* For now we require there only be one top level object. */ @@ -402,6 +402,8 @@ self => */ if (name == mainModuleName) md else treeCopy.ModuleDef(md, mods, mainModuleName, template) + case md @ ModuleDef(_, _, _) => md + case cd @ ClassDef(_, _, _, _) => cd case _ => /* If we see anything but the above, fail. */ return None 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) +} -- cgit v1.2.3 From e753135f02a8177e809937e56fed5c054091691f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 17 May 2016 00:00:52 -0700 Subject: SI-4625 Warn when discarding script object It's pretty confusing when your script object becomes a local and then nothing happens. Such as when you're writing a test and it takes forever to figure out what's going on. --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 15 +++++++++++---- test/files/run/t4625c.check | 3 +++ test/files/run/t4625c.scala | 7 +++++++ test/files/run/t4625c.script | 6 ++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 test/files/run/t4625c.check create mode 100644 test/files/run/t4625c.scala create mode 100644 test/files/run/t4625c.script (limited to 'src/compiler/scala/tools/nsc/ast/parser/Parsers.scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 1ece580b96..c2f2141fd3 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -389,8 +389,8 @@ self => } /* For now we require there only be one top level object. */ var seenModule = false + var disallowed = EmptyTree: Tree val newStmts = stmts collect { - case t @ Import(_, _) => t 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 @@ -404,11 +404,18 @@ self => else treeCopy.ModuleDef(md, mods, mainModuleName, template) case md @ ModuleDef(_, _, _) => md case cd @ ClassDef(_, _, _, _) => cd - case _ => + case t @ Import(_, _) => t + case t => /* If we see anything but the above, fail. */ - return None + disallowed = t + EmptyTree + } + if (disallowed.isEmpty) Some(makeEmptyPackage(0, newStmts)) + else { + if (seenModule) + warning(disallowed.pos.point, "Script has a main object but statement is disallowed") + None } - Some(makeEmptyPackage(0, newStmts)) } if (mainModuleName == newTermName(ScriptRunner.defaultScriptMain)) 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..fa14f43950 --- /dev/null +++ b/test/files/run/t4625c.script @@ -0,0 +1,6 @@ + +val x = "value x" + +object Main extends App { + println(s"Test ran with $x.") +} -- cgit v1.2.3 From 7f514bba9ff1993ccbfdcf4a37a8045849f1647a Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 17 May 2016 14:12:57 -0700 Subject: SI-4625 Warn on first non-toplevel only Fixed the warning when main module is accompanied by snippets. Minor clean-up so even I can follow what is returned. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 88 +++++++++++----------- test/files/run/t4625c.script | 1 + 2 files changed, 47 insertions(+), 42 deletions(-) (limited to 'src/compiler/scala/tools/nsc/ast/parser/Parsers.scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index c2f2141fd3..308669256d 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -371,7 +371,7 @@ self => * 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 @@ -387,10 +387,10 @@ self => case Template(parents, _, _) => parents.exists(cond(_) { case Ident(tpnme.App) => true }) case _ => false } - /* For now we require there only be one top level object. */ + /* We allow only one main module. */ var seenModule = false var disallowed = EmptyTree: Tree - val newStmts = stmts collect { + 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 @@ -407,54 +407,58 @@ self => case t @ Import(_, _) => t case t => /* If we see anything but the above, fail. */ - disallowed = t + if (disallowed.isEmpty) disallowed = t EmptyTree } - if (disallowed.isEmpty) Some(makeEmptyPackage(0, newStmts)) + if (disallowed.isEmpty) makeEmptyPackage(0, newStmts) else { if (seenModule) warning(disallowed.pos.point, "Script has a main object but statement is disallowed") - None + EmptyTree } } - 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 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 { ... } - 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 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 { ... } + makeEmptyPackage(0, moduleDef :: Nil) + } + + mainModule orElse repackaged } /* --------------- PLACEHOLDERS ------------------------------------------- */ diff --git a/test/files/run/t4625c.script b/test/files/run/t4625c.script index fa14f43950..16159208e0 100644 --- a/test/files/run/t4625c.script +++ b/test/files/run/t4625c.script @@ -1,5 +1,6 @@ val x = "value x" +val y = "value y" object Main extends App { println(s"Test ran with $x.") -- cgit v1.2.3