Skip to content

Commit

Permalink
Merge pull request #5169 from som-snytt/issue/4625
Browse files Browse the repository at this point in the history
SI-4625 Recognize App in script
  • Loading branch information
lrytz committed May 23, 2016
2 parents f640fa0 + 7f514bb commit 9a703b4
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 44 deletions.
108 changes: 64 additions & 44 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 ------------------------------------------- */
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions test/files/run/t4625.check
@@ -0,0 +1 @@
Test ran.
7 changes: 7 additions & 0 deletions 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"
}
5 changes: 5 additions & 0 deletions test/files/run/t4625.script
@@ -0,0 +1,5 @@

object Main extends Runnable with App {
def run() = println("Test ran.")
run()
}
1 change: 1 addition & 0 deletions test/files/run/t4625b.check
@@ -0,0 +1 @@
Misc top-level detritus
7 changes: 7 additions & 0 deletions 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"
}
8 changes: 8 additions & 0 deletions 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)
}
3 changes: 3 additions & 0 deletions 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"
^
7 changes: 7 additions & 0 deletions 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"
}
7 changes: 7 additions & 0 deletions 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.")
}

0 comments on commit 9a703b4

Please sign in to comment.