InputTask macro

Similar to task macros, the parsed value is accessed by calling `parsed`
on a Parser[T], Initialize[Parser[T]], or Initialize[State => Parser[T]].
Values of tasks and settings may be accessed as usual via `value`.
This commit is contained in:
Mark Harrah 2012-11-02 14:20:17 -04:00
parent 79aeb7b00e
commit 49e7214fe3
13 changed files with 430 additions and 132 deletions

View File

@ -397,16 +397,26 @@ object Defaults extends BuildCommon
}
def succeededFile(dir: File) = dir / "succeeded_tests"
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =
InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result =>
(streams, loadedTestFrameworks, testFilter in key, testGrouping in key, testExecution in key, testLoader, resolvedScoped, result, fullClasspath in key, javaHome in key, state) flatMap {
case (s, frameworks, filter, groups, config, loader, scoped, (selected, frameworkOptions), cp, javaHome, st) =>
implicit val display = Project.showContextKey(st)
val modifiedOpts = Tests.Filter(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
allTestGroupsTask(s, frameworks, loader, groups, newConfig, cp, javaHome) map (Tests.showResults(s.log, _, noTestsMessage(scoped)))
}
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] = inputTests0.mapReferenced(Def.mapScope(_ in key.key))
private[this] lazy val inputTests0: Initialize[InputTask[Unit]] =
{
val parser = loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) )
Def.inputTaskDyn {
val (selected, frameworkOptions) = parser.parsed
val s = streams.value
val filter = testFilter.value
val config = testExecution.value
implicit val display = Project.showContextKey(state.value)
val modifiedOpts = Tests.Filter(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
val groupsTask = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value)
val processed =
for(out <- groupsTask) yield
Tests.showResults(s.log, out, noTestsMessage(resolvedScoped.value))
Def.value(processed)
}
}
def allTestGroupsTask(s: TaskStreams, frameworks: Map[TestFramework,Framework], loader: ClassLoader, groups: Seq[Tests.Group], config: Tests.Execution, cp: Classpath, javaHome: Option[File]): Task[Tests.Output] = {
val groupTasks = groups map {
@ -537,23 +547,25 @@ object Defaults extends BuildCommon
IO.delete(clean)
IO.move(mappings.map(_.swap))
}
def runMainTask(classpath: TaskKey[Classpath], scalaRun: TaskKey[ScalaRun]): Initialize[InputTask[Unit]] =
def runMainTask(classpath: Initialize[Task[Classpath]], scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] =
{
import DefaultParsers._
InputTask( loadForParser(discoveredMainClasses)( (s, names) => runMainParser(s, names getOrElse Nil) ) ) { result =>
(classpath, scalaRun, streams, result) map { case (cp, runner, s, (mainClass, args)) =>
toError(runner.run(mainClass, data(cp), args, s.log))
}
val parser = loadForParser(discoveredMainClasses)( (s, names) => runMainParser(s, names getOrElse Nil) )
Def.inputTask {
val (mainClass, args) = parser.parsed
toError(scalaRun.value.run(mainClass, data(classpath.value), args, streams.value.log))
}
}
def runTask(classpath: TaskKey[Classpath], mainClassTask: TaskKey[Option[String]], scalaRun: TaskKey[ScalaRun]): Initialize[InputTask[Unit]] =
inputTask { result =>
(classpath, mainClassTask, scalaRun, streams, result) map { (cp, main, runner, s, args) =>
val mainClass = main getOrElse error("No main class detected.")
toError(runner.run(mainClass, data(cp), args, s.log))
}
def runTask(classpath: Initialize[Task[Classpath]], mainClassTask: Initialize[Task[Option[String]]], scalaRun: Initialize[Task[ScalaRun]]): Initialize[InputTask[Unit]] =
{
import Def.parserToInput
val parser = Def.spaceDelimited()
Def.inputTask {
val mainClass = mainClassTask.value getOrElse error("No main class detected.")
toError(scalaRun.value.run(mainClass, data(classpath.value), parser.parsed, streams.value.log))
}
}
def runnerTask = runner <<= runnerInit
def runnerInit: Initialize[Task[ScalaRun]] =
@ -1371,7 +1383,9 @@ trait BuildExtra extends BuildCommon
}
trait BuildCommon
{
def inputTask[T](f: TaskKey[Seq[String]] => Initialize[Task[T]]): Initialize[InputTask[T]] = InputTask(_ => complete.Parsers.spaceDelimited("<arg>"))(f)
@deprecated("Use Def.inputTask with the `Def.spaceDelimited()` parser.", "0.13.0")
def inputTask[T](f: TaskKey[Seq[String]] => Initialize[Task[T]]): Initialize[InputTask[T]] =
InputTask.create(Def.value((s: State) => Def.spaceDelimited()))(f)
implicit def globFilter(expression: String): NameFilter = GlobFilter(expression)
implicit def richAttributed(s: Seq[Attributed[File]]): RichAttributed = new RichAttributed(s)

View File

@ -1,9 +1,10 @@
package sbt
import complete.Parser
import java.io.File
/** A concrete settings system that uses `sbt.Scope` for the scope type. */
object Def extends Init[Scope]
object Def extends Init[Scope] with TaskMacroExtra
{
type Classpath = Seq[Attributed[File]]
@ -35,22 +36,38 @@ object Def extends Init[Scope]
case None => s
}
/** A default Parser for splitting input into space-separated arguments.
* `argLabel` is an optional, fixed label shown for an argument during tab completion.*/
def spaceDelimited(argLabel: String = "<arg>"): Parser[Seq[String]] = complete.Parsers.spaceDelimited(argLabel)
/** Lifts the result of a setting initialization into a Task. */
def toITask[T](i: Initialize[T]): Initialize[Task[T]] = map(i)(std.TaskExtra.inlineTask)
import language.experimental.macros
import std.TaskMacro.{MacroValue, taskDynMacroImpl, taskMacroImpl}
import std.TaskMacro.{InitParserInput, inputTaskMacroImpl, inputTaskDynMacroImpl, MacroValue, taskDynMacroImpl, taskMacroImpl, StateParserInput}
import std.SettingMacro.{settingDynMacroImpl,settingMacroImpl}
def task[T](t: T): Def.Initialize[Task[T]] = macro taskMacroImpl[T]
def taskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[Task[T]] = macro taskDynMacroImpl[T]
def setting[T](t: T): Def.Initialize[T] = macro settingMacroImpl[T]
def settingDyn[T](t: Def.Initialize[T]): Def.Initialize[T] = macro settingDynMacroImpl[T]
def inputTask[T](t: T): Def.Initialize[InputTask[T]] = macro inputTaskMacroImpl[T]
def inputTaskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[InputTask[T]] = macro inputTaskDynMacroImpl[T]
// The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to
// be used in task and setting macros as inputs with an ultimate result of type T
implicit def macroValueI[T](in: Initialize[T]): MacroValue[T] = ???
implicit def macroValueIT[T](in: Initialize[Task[T]]): MacroValue[T] = ???
implicit def macroValueT[T](in: Task[T]): MacroValue[T] = ???
// The following conversions enable the types Parser[T], Initialize[Parser[T]], and Initialize[State => Parser[T]] to
// be used in the inputTask macro as an input with an ultimate result of type T
implicit def parserInitToInput[T](p: Initialize[Parser[T]]): InitParserInput[T] = ???
implicit def parserInitStateToInput[T](p: Initialize[State => Parser[T]]): StateParserInput[T] = ???
}
// these need to be mixed into the sbt package object because the target doesn't involve Initialize or anything in Def
trait TaskMacroExtra
{
implicit def macroValueT[T](in: Task[T]): std.TaskMacro.MacroValue[T] = ???
implicit def parserToInput[T](in: Parser[T]): std.TaskMacro.RawParserInput[T] = ???
}

View File

@ -28,25 +28,38 @@ private sealed trait InputDynamic[T] extends InputTask[T]
}
object InputTask
{
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def static[T](p: Parser[Task[T]]): InputTask[T] = free(_ => p)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def static[I,T](p: Parser[I])(c: I => Task[T]): InputTask[T] = static(p map c)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def free[T](p: State => Parser[Task[T]]): InputTask[T] = new InputStatic[T](p)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def free[I,T](p: State => Parser[I])(c: I => Task[T]): InputTask[T] = free(s => p(s) map c)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def separate[I,T](p: State => Parser[I])(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
separate(Def value p)(action)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def separate[I,T](p: Initialize[State => Parser[I]])(action: Initialize[I => Task[T]]): Initialize[InputTask[T]] =
p.zipWith(action)((parser, act) => free(parser)(act))
private[sbt] lazy val inputMap: Task[Map[AnyRef,Any]] = mktask { error("Internal sbt error: input map not substituted.") }
@deprecated("Use the non-overloaded `create` or the `Def.inputTask` macro.", "0.13.0")
def apply[I,T](p: Initialize[State => Parser[I]])(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] =
create(p)(action)
// This interface allows the Parser to be constructed using other Settings, but not Tasks (which is desired).
// The action can be constructed using Settings and Tasks and with the parse result injected into a Task.
// This is the ugly part, requiring hooks in Load.finalTransforms and Aggregation.applyDynamicTasks
// to handle the dummy task for the parse result.
// However, this results in a minimal interface to the full capabilities of an InputTask for users
def apply[I,T](p: Initialize[State => Parser[I]])(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] =
def create[I,T](p: Initialize[State => Parser[I]])(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] =
{
val key: TaskKey[I] = Def.parseResult.asInstanceOf[TaskKey[I]]
(p zip Def.resolvedScoped zipWith action(key)) { case ((parserF, scoped), act) =>
@ -59,6 +72,17 @@ object InputTask
}
}
}
/** A dummy parser that consumes no input and produces nothing useful (unit).*/
def emptyParser: Initialize[State => Parser[Unit]] = parserAsInput(complete.DefaultParsers.success(()))
/** Implementation detail that is public because it is used by a macro.*/
def parserAsInput[T](p: Parser[T]): Initialize[State => Parser[T]] = Def.valueStrict(Types.const(p))
/** Implementation detail that is public because it is used y a macro.*/
def initParserAsInput[T](i: Initialize[Parser[T]]): Initialize[State => Parser[T]] = i(Types.const)
@deprecated("Use `create` or the `Def.inputTask` macro.", "0.13.0")
def apply[I,T](p: State => Parser[I])(action: TaskKey[I] => Initialize[Task[T]]): Initialize[InputTask[T]] =
apply(Def.value(p))(action)
}

View File

@ -31,14 +31,15 @@ sealed trait ScopedTaskable[T] extends Scoped {
sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T]
{
val key: AttributeKey[T]
def toTask: Initialize[Task[T]] = this apply inlineTask
def scopedKey: ScopedKey[T] = ScopedKey(scope, key)
def in(scope: Scope): SettingKey[T] = Scoped.scopedSetting(Scope.replaceThis(this.scope)(scope), this.key)
final def toTask: Initialize[Task[T]] = this apply inlineTask
final def scopedKey: ScopedKey[T] = ScopedKey(scope, key)
final def in(scope: Scope): SettingKey[T] = Scoped.scopedSetting(Scope.replaceThis(this.scope)(scope), this.key)
def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[T] = macro std.TaskMacro.settingAppend1Impl[T,U]
def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[T] = macro std.TaskMacro.settingAppendNImpl[T,U]
def <+= [V](value: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] = make(value)(a.appendValue)
def <++= [V](values: Initialize[V])(implicit a: Append.Values[T, V]): Setting[T] = make(values)(a.appendValues)
final def := (v: T): Setting[T] = macro std.TaskMacro.settingAssignMacroImpl[T]
final def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[T] = macro std.TaskMacro.settingAppend1Impl[T,U]
final def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[T] = macro std.TaskMacro.settingAppendNImpl[T,U]
final def <+= [V](value: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] = make(value)(a.appendValue)
final def <++= [V](values: Initialize[V])(implicit a: Append.Values[T, V]): Setting[T] = make(values)(a.appendValues)
protected[this] def make[S](other: Initialize[S])(f: (T, S) => T): Setting[T] = this <<= (this, other)(f)
}
@ -72,6 +73,8 @@ sealed trait InputKey[T] extends Scoped with KeyedInitialize[InputTask[T]] with
val key: AttributeKey[InputTask[T]]
def scopedKey: ScopedKey[InputTask[T]] = ScopedKey(scope, key)
def in(scope: Scope): InputKey[T] = Scoped.scopedInput(Scope.replaceThis(this.scope)(scope), this.key)
def :=(v: T): Setting[InputTask[T]] = macro std.TaskMacro.inputTaskAssignMacroImpl[T]
}
/** Methods and types related to constructing settings, including keys, scopes, and initializations. */
@ -103,7 +106,6 @@ object Scoped
def scopedKey: ScopedKey[S]
private[sbt] final def :==(value: S): Setting[S] = setting(scopedKey, Def.valueStrict(value))
final def := (v: S): Setting[S] = macro std.TaskMacro.settingAssignMacroImpl[S]
final def ~= (f: S => S): Setting[S] = Def.update(scopedKey)(f)
final def <<= (app: Initialize[S]): Setting[S] = set(app)
final def set (app: Initialize[S]): Setting[S] = setting(scopedKey, app)

View File

@ -4,7 +4,8 @@ package std
import Def.{Initialize,Setting}
import Types.{idFun,Id}
import TaskExtra.allM
import appmacro.{Convert, InputWrapper, Instance, MixedBuilder, MonadInstance}
import appmacro.{ContextUtil, Convert, InputWrapper, Instance, MixedBuilder, MonadInstance}
import complete.Parser
import language.experimental.macros
import scala.reflect._
@ -66,6 +67,7 @@ object TaskMacro
final val AssignInitName = "<<="
final val Append1InitName = "<+="
final val AppendNInitName = "<++="
final val InputTaskCreateName = "create"
def taskMacroImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Initialize[Task[T]]] =
Instance.contImpl[T](c, FullInstance, FullConvert, MixedBuilder)(Left(t))
@ -87,6 +89,13 @@ object TaskMacro
val assign = transformMacroImpl(c)( init.tree )( AssignInitName )
c.Expr[Setting[Task[T]]]( assign )
}
/** Implementation of := macro for tasks. */
def inputTaskAssignMacroImpl[T: c.WeakTypeTag](c: Context)(v: c.Expr[T]): c.Expr[Setting[InputTask[T]]] =
{
val init = inputTaskMacroImpl[T](c)(v)
val assign = transformMacroImpl(c)( init.tree )( AssignInitName )
c.Expr[Setting[InputTask[T]]]( assign )
}
/** Implementation of += macro for tasks. */
def taskAppend1Impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: Context)(v: c.Expr[U])(a: c.Expr[Append.Value[T, U]]): c.Expr[Setting[Task[T]]] =
{
@ -122,7 +131,7 @@ object TaskMacro
c.macroApplication match {
case Apply(Apply(TypeApply(Select(preT, nmeT), targs), _), a) =>
Apply(Apply(TypeApply(Select(preT, newTermName(newName).encodedName), targs), init :: Nil), a)
case x => unexpectedTree(x)
case x => ContextUtil.unexpectedTree(x)
}
}
private[this] def transformMacroImpl(c: Context)(init: c.Tree)(newName: String): c.Tree =
@ -131,31 +140,130 @@ object TaskMacro
val target =
c.macroApplication match {
case Apply(Select(prefix, _), _) => prefix
case x => unexpectedTree(x)
case x => ContextUtil.unexpectedTree(x)
}
Apply.apply(Select(target, newTermName(newName).encodedName), init :: Nil)
}
private[this] def unexpectedTree[C <: Context](tree: C#Tree): Nothing = error("Unexpected macro application tree (" + tree.getClass + "): " + tree)
sealed abstract class MacroValue[T] {
def value: T = macro std.TaskMacro.valueMacroImpl[T]
}
def valueMacroImpl[T: c.WeakTypeTag](c: Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T,Any](c)( ts => InputWrapper.wrapKey[T](c)(ts) )
sealed abstract class RawParserInput[T] {
def parsed: T = macro std.TaskMacro.rawParserMacro[T]
}
sealed abstract class InitParserInput[T] {
def parsed: T = macro std.TaskMacro.initParserMacro[T]
}
sealed abstract class StateParserInput[T] {
def parsed: T = macro std.TaskMacro.stateParserMacro[T]
}
/** Implements `Parser[T].parsed` by wrapping the Parser with the ParserInput wrapper.*/
def rawParserMacro[T: c.WeakTypeTag](c: Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T, Parser[T]](c) { p => c.universe.reify {
ParserInput.parser_\u2603\u2603[T](InputTask.parserAsInput(p.splice))
}}
/** Implements the `Initialize[Parser[T]].parsed` macro by wrapping the input with the ParserInput wrapper.*/
def initParserMacro[T: c.WeakTypeTag](c: Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T, Initialize[Parser[T]]](c) { t => c.universe.reify {
ParserInput.parser_\u2603\u2603[T](InputTask.initParserAsInput(t.splice))
}}
/** Implements the `Initialize[State => Parser[T]].parsed` macro by wrapping the input with the ParserInput wrapper.*/
def stateParserMacro[T: c.WeakTypeTag](c: Context): c.Expr[T] =
ContextUtil.selectMacroImpl[T, Initialize[State => Parser[T]]](c) { t => c.universe.reify {
ParserInput.parser_\u2603\u2603[T](t.splice)
}}
/** Implementation detail. The method temporarily holds the input parser (as a Tree, at compile time) until the input task macro processes it. */
object ParserInput {
/** The name of the wrapper method should be obscure.
* Wrapper checking is based solely on this name, so it must not conflict with a user method name.
* The user should never see this method because it is compile-time only and only used internally by the task macros.*/
val WrapName = "parser_\u2603\u2603"
// This method should be annotated as compile-time only when that feature is implemented
def parser_\u2603\u2603[T](i: Initialize[State => Parser[T]]): T = error("This method is an implementation detail and should not be referenced.")
}
def inputTaskMacroImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Initialize[InputTask[T]]] =
inputTaskMacro0[T](c)(Left(t))
def inputTaskDynMacroImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[Initialize[Task[T]]]): c.Expr[Initialize[InputTask[T]]] =
inputTaskMacro0[T](c)(Right(t))
private[this] def inputTaskMacro0[T: c.WeakTypeTag](c: Context)(t: Either[c.Expr[T], c.Expr[Initialize[Task[T]]]]): c.Expr[Initialize[InputTask[T]]] =
{
import c.universe._
c.macroApplication match {
case Select(Apply(_, t :: Nil), _) => wrap[T](c)(t)
case x => unexpectedTree(x)
import c.universe.{Apply=>ApplyTree,_}
val tag = implicitly[c.WeakTypeTag[T]]
val util = ContextUtil[c.type](c)
val it = Ident(util.singleton(InputTask))
val isParserWrapper = util.isWrapper(ParserInput.WrapName)
val isTaskWrapper = util.isWrapper(InputWrapper.WrapName)
val isAnyWrapper = (tr: Tree) => isParserWrapper(tr) || isTaskWrapper(tr)
val ttree = t match { case Left(l) => l.tree; case Right(r) => r.tree }
val defs = util.collectDefs(ttree, isAnyWrapper)
val checkQual = util.checkReferences(defs, isAnyWrapper)
val unitTask = c.typeOf[TaskKey[Unit]]
val taskKeyC = unitTask.typeConstructor
var result: Option[(Tree, Type, ValDef)] = None
def subWrapper(tpe: Type, qual: Tree): Tree =
if(result.isDefined)
{
c.error(qual.pos, "An InputTask can only have a single input parser.")
EmptyTree
}
else
{
qual.foreach(checkQual)
val keyType = appliedType(taskKeyC, tpe :: Nil) // TaskKey[<tpe>]
val vd = util.freshValDef(keyType, qual.symbol) // val $x: TaskKey[<tpe>]
result = Some( (qual, tpe, vd) )
val tree = util.refVal(vd) // $x
tree.setPos(qual.pos) // position needs to be set so that wrapKey passes the position onto the wrapper
assert(tree.tpe != null, "Null type: " + tree)
val wrapped = InputWrapper.wrapKey(c)( c.Expr[Any](tree) )( c.WeakTypeTag(tpe) )
wrapped.tree.setType(tpe)
}
// Tree for InputTask.create[<tpeA>, <tpeB>](arg1)(arg2)
def inputTaskApply(tpeA: Type, tpeB: Type, arg1: Tree, arg2: Tree) =
{
val typedApp = TypeApply(Select(it, InputTaskCreateName), TypeTree(tpeA) :: TypeTree(tpeB) :: Nil)
val app = ApplyTree( ApplyTree(typedApp, arg1 :: Nil), arg2 :: Nil)
c.Expr[Initialize[InputTask[T]]](app)
}
def expandTask(dyn: Boolean, tx: Tree): c.Expr[Initialize[Task[T]]] =
if(dyn)
taskDynMacroImpl[T](c)( c.Expr[Initialize[Task[T]]](tx) )
else
taskMacroImpl[T](c)( c.Expr[T](tx) )
val tx = util.transformWrappers(ttree, isParserWrapper, (tpe,tree) => subWrapper(tpe,tree))
val body = c.resetLocalAttrs( expandTask(t.isRight, tx).tree )
result match {
case Some((p, tpe, param)) =>
val f = Function(param :: Nil, body)
inputTaskApply(tpe, tag.tpe, p, f)
case None =>
// SI-6591 prevents the more direct version using reify:
// reify { InputTask[Unit,T].create(TaskMacro.emptyParser)(Types.const(body.splice)) }
val initType = c.weakTypeOf[Initialize[Task[T]]]
val tt = Ident(util.singleton(Types))
val f = ApplyTree(TypeApply(Select(tt, "const"), TypeTree(unitTask) :: TypeTree(initType) :: Nil), body :: Nil)
val p = reify { InputTask.emptyParser }
inputTaskApply(c.typeOf[Unit], tag.tpe, p.tree, f)
}
}
private[this] def wrap[T: c.WeakTypeTag](c: Context)(t: c.Tree): c.Expr[T] =
{
val ts = c.Expr[Any](t)
c.universe.reify { InputWrapper.wrap[T](ts.splice) }
}
}
/** Convert instance for plain `Task`s not within the settings system.
* This is not used for the main task/setting macros, but could be used when manipulating plain Tasks.*/
object TaskConvert extends Convert

View File

@ -1,7 +1,10 @@
package sbt
package std
object UseTask
import complete.{DefaultParsers, Parsers}
/*object UseTask
{
import Def._
@ -14,18 +17,28 @@ object UseTask
val a = taskDyn {
if(y.value) z else x
}
}
}*/
object Assign
{
import java.io.File
import Def.{Initialize,macroValueT}
import UseTask.{x,y,z,a,set,plain}
import Def.{Initialize,macroValueT,parserToInput}
// import UseTask.{x,y,z,a,set,plain}
val ak = TaskKey[Int]("a")
val bk = TaskKey[Seq[Int]]("b")
val ck = SettingKey[File]("c")
val sk = TaskKey[Set[_]]("s")
val ik = InputKey[Int]("i")
val isk = InputKey[String]("is")
val mk = SettingKey[Int]("m")
val tk = TaskKey[Int]("t")
val name = SettingKey[String]("name")
val dummyt = TaskKey[complete.Parser[String]]("dummyt")
val dummys = SettingKey[complete.Parser[String]]("dummys")
val dummy3 = SettingKey[complete.Parser[(String,Int)]]("dummy3")
val tsk: complete.Parser[TaskKey[String]] = ???
/* def azy = sk.value
def azy2 = appmacro.Debug.checkWild(Def.task{ sk.value.size })
@ -37,7 +50,42 @@ object Assign
bk ++= Seq(z.value)
)*/
def bool: Initialize[Boolean] = Def.setting { true }
import DefaultParsers._
val p = Def.setting { name.value ~> Space ~> ID }
val is = Seq(
mk := 3,
name := "asdf",
tk := (math.random*1000).toInt,
isk := tsk.parsed.value, // ParserInput.wrap[TaskKey[String]](tsk).value
// isk := dummys.value.parsed , // should not compile: cannot use a task to define the parser
ik := { if( tsk.parsed.value == "blue") tk.value else mk.value }
)
val it1 = Def.inputTask {
tsk.parsed //"as" //dummy.value.parsed
}
val it2 = Def.inputTask {
"lit"
}
// should not compile because getting the value from a parser involves getting the value from a task
val it3: Initialize[InputTask[String]] = Def.inputTask[String] {
tsk.parsed.value
}
/* // should not compile: cannot use a task to define the parser
val it4 = Def.inputTask {
dummyt.value.parsed
}*/
// should compile: can use a setting to define the parser
val it5 = Def.inputTask {
dummys.parsed
}
val it6 = Def.inputTaskDyn {
val (x,i) = dummy3.parsed
Def.task { tk.value + i}
}
/* def bool: Initialize[Boolean] = Def.setting { true }
def enabledOnly[T](key: Initialize[T]): Initialize[Seq[T]] = Def.setting {
val keys: Seq[T] = forallIn(key).value
val enabled: Seq[Boolean] = forallIn(bool).value
@ -45,5 +93,5 @@ object Assign
}
def forallIn[T](key: Initialize[T]): Initialize[Seq[T]] = Def.setting {
key.value :: Nil
}
}*/
}

View File

@ -2,7 +2,7 @@
* Copyright 2010, 2011 Mark Harrah
*/
package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtra with sbt.impl.DependencyBuilders
with sbt.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra
with sbt.PathExtra with sbt.ProjectExtra with sbt.DependencyFilterExtra with sbt.BuildExtra with sbt.TaskMacroExtra
{
@deprecated("Renamed to CommandStrings.", "0.12.0")
val CommandSupport = CommandStrings

View File

@ -1,15 +1,14 @@
import sbt.complete.DefaultParsers._
InputKey[Unit]("check-output") <<= {
{
import sbt.complete.DefaultParsers._
val parser = token(Space ~> ( ("exists" ^^^ true) | ("absent" ^^^ false) ) )
def action(result: TaskKey[Boolean]) =
(classDirectory in Configurations.Compile, result) map { (dir, shouldExist) =>
if((dir / "Anon.class").exists != shouldExist)
error("Top level class incorrect" )
else if( (dir / "Anon$1.class").exists != shouldExist)
error("Inner class incorrect" )
else
()
}
InputTask(s => parser)(action)
InputKey[Unit]("check-output") := {
val shouldExist = parser.parsed
val dir = (classDirectory in Compile).value
if((dir / "Anon.class").exists != shouldExist)
error("Top level class incorrect" )
else if( (dir / "Anon$1.class").exists != shouldExist)
error("Inner class incorrect" )
else
()
}
}

View File

@ -4,7 +4,7 @@ import Defaults._
object B extends Build {
lazy val root = Project("root", file("."), settings = defaultSettings ++ Seq(
libraryDependencies += "org.scalatest" % "scalatest_2.9.0" % "1.6.1" % "test",
libraryDependencies += "org.scalatest" %% "scalatest" % "1.8" % "test" cross(CrossVersion.full),
parallelExecution in test := false
))
}

View File

@ -37,12 +37,16 @@ object ScriptedPlugin extends Plugin {
m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[String], classOf[String], classOf[String], classOf[Array[String]], classOf[File], classOf[Array[String]])
}
def scriptedTask: Initialize[InputTask[Unit]] = InputTask(_ => complete.Parsers.spaceDelimited("<arg>")) { result =>
(scriptedDependencies, scriptedTests, scriptedRun, sbtTestDirectory, scriptedBufferLog, scriptedSbt, scriptedScalas, sbtLauncher, scriptedLaunchOpts, result) map {
(deps, m, r, testdir, bufferlog, version, scriptedScalas, launcher, launchOpts, args) =>
try { r.invoke(m, testdir, bufferlog: java.lang.Boolean, version.toString, scriptedScalas.build, scriptedScalas.versions, args.toArray, launcher, launchOpts.toArray) }
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {
val args = Def.spaceDelimited().parsed
val prereq: Unit = scriptedDependencies.value
try {
scriptedRun.value.invoke(
scriptedTests.value, sbtTestDirectory.value, scriptedBufferLog.value: java.lang.Boolean,
scriptedSbt.value.toString, scriptedScalas.value.build, scriptedScalas.value.versions,
args.toArray, sbtLauncher.value, scriptedLaunchOpts.value.toArray)
}
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
}
val scriptedSettings = Seq(

View File

@ -4,11 +4,32 @@ package appmacro
import scala.reflect._
import macros._
import scala.tools.nsc.Global
import ContextUtil.{DynamicDependencyError, DynamicReferenceError}
object ContextUtil {
final val DynamicDependencyError = "Illegal dynamic dependency"
final val DynamicReferenceError = "Illegal dynamic reference"
/** Constructs an object with utility methods for operating in the provided macro context `c`.
* Callers should explicitly specify the type parameter as `c.type` in order to preserve the path dependent types. */
def apply[C <: Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c)
/** Helper for implementing a no-argument macro that is introduced via an implicit.
* This method removes the implicit conversion and evaluates the function `f` on the target of the conversion.
*
* Given `myImplicitConversion(someValue).extensionMethod`, where `extensionMethod` is a macro that uses this
* method, the result of this method is `f(<Tree of someValue>)`. */
def selectMacroImpl[T: c.WeakTypeTag, S: c.WeakTypeTag](c: Context)(f: c.Expr[S] => c.Expr[T]): c.Expr[T] =
{
import c.universe._
c.macroApplication match {
case Select(Apply(_, t :: Nil), _) => f( c.Expr[S](t) )
case x => unexpectedTree(x)
}
}
def unexpectedTree[C <: Context](tree: C#Tree): Nothing = error("Unexpected macro application tree (" + tree.getClass + "): " + tree)
}
/** Utility methods for macros. Several methods assume that the context's universe is a full compiler (`scala.tools.nsc.Global`).
@ -42,6 +63,50 @@ final class ContextUtil[C <: Context](val ctx: C)
vd
}
/* Tests whether a Tree is a Select on `methodName`. */
def isWrapper(methodName: String): Tree => Boolean = {
case Select(_, nme) => nme.decoded == methodName
case _ => false
}
lazy val parameterModifiers = Modifiers(Flag.PARAM)
/** Collects all definitions in the tree for use in checkReferences.
* This excludes definitions in wrapped expressions because checkReferences won't allow nested dereferencing anyway. */
def collectDefs(tree: Tree, isWrapper: Tree => Boolean): collection.Set[Symbol] =
{
val defs = new collection.mutable.HashSet[Symbol]
// adds the symbols for all non-Ident subtrees to `defs`.
val process = new Traverser {
override def traverse(t: Tree) = t match {
case _: Ident => ()
case ApplyTree(TypeApply(fun, tpe :: Nil), qual :: Nil) if isWrapper(fun) => ()
case tree =>
if(tree.symbol ne null) defs += tree.symbol;
super.traverse(tree)
}
}
process.traverse(tree)
defs
}
/** A reference is illegal if it is to an M instance defined within the scope of the macro call.
* As an approximation, disallow referenced to any local definitions `defs`. */
def illegalReference(defs: collection.Set[Symbol], sym: Symbol): Boolean =
sym != null && sym != NoSymbol && defs.contains(sym)
/** A function that checks the provided tree for illegal references to M instances defined in the
* expression passed to the macro and for illegal dereferencing of M instances. */
def checkReferences(defs: collection.Set[Symbol], isWrapper: Tree => Boolean): Tree => Unit = {
case s @ ApplyTree(TypeApply(fun, tpe :: Nil), qual :: Nil) => if(isWrapper(fun)) ctx.error(s.pos, DynamicDependencyError)
case id @ Ident(name) if illegalReference(defs, id.symbol) => ctx.error(id.pos, DynamicReferenceError + ": " + name)
case _ => ()
}
/** Constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs. */
def freshMethodParameter(tpe: Type): ValDef =
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
/** Constructs a ValDef with local modifiers and a unique name. */
def localValDef(tpt: Tree, rhs: Tree): ValDef =
ValDef(localModifiers, freshTermName("q"), tpt, rhs)
@ -75,8 +140,18 @@ final class ContextUtil[C <: Context](val ctx: C)
tc.setTypeSignature(PolyType(arg :: Nil, emptyTypeBounds))
tc
}
/** >: Nothing <: Any */
def emptyTypeBounds: TypeBounds = TypeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType)
/** Create a Tree that references the `val` represented by `vd`. */
def refVal(vd: ValDef): Tree =
{
val t = Ident(vd.name)
assert(vd.tpt.tpe != null, "val type is null: " + vd + ", tpt: " + vd.tpt.tpe)
t.setType(vd.tpt.tpe)
t
}
/** Returns the Symbol that references the statically accessible singleton `i`. */
def singleton[T <: AnyRef with Singleton](i: T)(implicit it: ctx.TypeTag[i.type]): Symbol =
it.tpe match {
@ -105,4 +180,27 @@ final class ContextUtil[C <: Context](val ctx: C)
assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc)
tc
}
/** Substitutes wrappers in tree `t` with the result of `subWrapper`.
* A wrapper is a Tree of the form `f[T](v)` for which isWrapper(<Tree of f>) returns true.
* Typically, `f` is a `Select` or `Ident`.
* The wrapper is replaced with the result of `subWrapper(<Type of T>, <Tree of v>)` */
def transformWrappers(t: Tree, isWrapper: Tree => Boolean, subWrapper: (Type, Tree) => Tree): Tree =
{
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
// plain Idents that reference the actual input value
object appTransformer extends Transformer
{
override def transform(tree: Tree): Tree =
tree match
{
case ApplyTree(TypeApply(fun, targ :: Nil), qual :: Nil) if isWrapper(fun) =>
assert(qual.tpe != null, "Internal error: null type for wrapped tree with " + qual.getClass + "\n\t" + qual + "\n in " + t)
subWrapper(targ.tpe, qual)
case _ => super.transform(tree)
}
}
appTransformer.transform(t)
}
}

View File

@ -24,24 +24,45 @@ trait MonadInstance extends Instance
{
def flatten[T](in: M[M[T]]): M[T]
}
object InputWrapper
{
def wrap[T](in: Any): T = error("This method is an implementation detail and should not be referenced.")
}
import scala.reflect._
import macros._
object InputWrapper
{
/** The name of the wrapper method should be obscure.
* Wrapper checking is based solely on this name, so it must not conflict with a user method name.
* The user should never see this method because it is compile-time only and only used internally by the task macro system.*/
final val WrapName = "wrap_\u2603\u2603"
// This method should be annotated as compile-time only when that feature is implemented
def wrap_\u2603\u2603[T](in: Any): T = error("This method is an implementation detail and should not be referenced.")
/** Wraps an arbitrary Tree in a call to the `wrap` method of this module for later processing by an enclosing macro.
* The resulting Tree is the manually constructed version of:
*
* `c.universe.reify { InputWrapper.<WrapName>[T](ts.splice) }`
*/
def wrapKey[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any]): c.Expr[T] =
{
import c.universe.{Apply=>ApplyTree,_}
val util = new ContextUtil[c.type](c)
val iw = util.singleton(InputWrapper)
val tpe = c.weakTypeOf[T]
val nme = newTermName(WrapName).encoded
val tree = ApplyTree(TypeApply(Select(Ident(iw), nme), TypeTree(tpe) :: Nil), ts.tree :: Nil)
tree.setPos(ts.tree.pos)
c.Expr[T](tree)
}
}
object Instance
{
final val DynamicDependencyError = "Illegal dynamic dependency."
final val DynamicReferenceError = "Illegal dynamic reference."
final val ApplyName = "app"
final val FlattenName = "flatten"
final val PureName = "pure"
final val MapName = "map"
final val InstanceTCName = "M"
final val WrapName = "wrap"
final class Input[U <: Universe with Singleton](val tpe: U#Type, val expr: U#Tree, val local: U#ValDef)
@ -99,41 +120,14 @@ object Instance
// A Tree that references the statically accessible Instance that provides the actual implementations of map, flatMap, ...
val instance = Ident(instanceSym)
val parameterModifiers = Modifiers(Flag.PARAM)
val wrapperSym = util.singleton(InputWrapper)
val wrapMethodSymbol = util.method(wrapperSym, WrapName)
def isWrapper(fun: Tree) = fun.symbol == wrapMethodSymbol
val isWrapper: Tree => Boolean = util.isWrapper(InputWrapper.WrapName)
type In = Input[c.universe.type]
var inputs = List[In]()
// constructs a ValDef with a parameter modifier, a unique name, with the provided Type and with an empty rhs
def freshMethodParameter(tpe: Type): ValDef =
ValDef(parameterModifiers, freshTermName("p"), TypeTree(tpe), EmptyTree)
def freshTermName(prefix: String) = newTermName(c.fresh("$" + prefix))
/* Local definitions in the macro. This is used to ensure
* references are to M instances defined outside of the macro call.*/
val defs = new collection.mutable.HashSet[Symbol]
// a reference is illegal if it is to an M instance defined within the scope of the macro call
def illegalReference(sym: Symbol): Boolean =
sym != null && sym != NoSymbol && defs.contains(sym)
// a function that checks the provided tree for illegal references to M instances defined in the
// expression passed to the macro and for illegal dereferencing of M instances.
val checkQual: Tree => Unit = {
case s @ ApplyTree(fun, qual :: Nil) => if(isWrapper(fun)) c.error(s.pos, DynamicDependencyError)
case id @ Ident(name) if illegalReference(id.symbol) => c.error(id.pos, DynamicReferenceError)
case _ => ()
}
// adds the symbols for all non-Ident subtrees to `defs`.
val defSearch: Tree => Unit = {
case _: Ident => ()
case tree => if(tree.symbol ne null) defs += tree.symbol;
}
// Local definitions in the macro. This is used to ensure references are to M instances defined outside of the macro call.
val defs = util.collectDefs(tree, isWrapper)
val checkQual: Tree => Unit = util.checkReferences(defs, isWrapper)
// transforms the original tree into calls to the Instance functions pure, map, ...,
// resulting in a value of type M[T]
@ -163,7 +157,7 @@ object Instance
def single(body: Tree, input: In): Tree =
{
val variable = input.local
val param = ValDef(parameterModifiers, variable.name, variable.tpt, EmptyTree)
val param = ValDef(util.parameterModifiers, variable.name, variable.tpt, EmptyTree)
val typeApplied = TypeApply(Select(instance, MapName), variable.tpt :: TypeTree(treeType) :: Nil)
val mapped = ApplyTree(typeApplied, input.expr :: Function(param :: Nil, body) :: Nil)
if(t.isLeft) mapped else flatten(mapped)
@ -173,7 +167,7 @@ object Instance
def arbArity(body: Tree, inputs: List[In]): Tree =
{
val result = builder.make(c)(mTC, inputs)
val param = freshMethodParameter( appliedType(result.representationC, util.idTC :: Nil) )
val param = util.freshMethodParameter( appliedType(result.representationC, util.idTC :: Nil) )
val bindings = result.extract(param)
val f = Function(param :: Nil, Block(bindings, body))
val ttt = TypeTree(treeType)
@ -192,30 +186,17 @@ object Instance
qual.foreach(checkQual)
val vd = util.freshValDef(tpe, qual.symbol)
inputs ::= new Input(tpe, qual, vd)
Ident(vd.name)
util.refVal(vd)
}
// the main tree transformer that replaces calls to InputWrapper.wrap(x) with
// plain Idents that reference the actual input value
object appTransformer extends Transformer
def sub(tpe: Type, qual: Tree): Tree =
{
override def transform(tree: Tree): Tree =
tree match
{
case ApplyTree(TypeApply(fun, t :: Nil), qual :: Nil) if isWrapper(fun) =>
val tag = c.WeakTypeTag(t.tpe)
addType(t.tpe, convert(c)(qual)(tag) )
case _ => super.transform(tree)
}
val tag = c.WeakTypeTag(tpe)
addType(tpe, convert(c)(qual)(tag) )
}
// collects all definitions in the tree. used for finding illegal references
tree.foreach(defSearch)
// applies the transformation
// resetting attributes: a) must be local b) must be done
// on the transformed tree and not the wrapped tree or else there are obscure errors
val tr = makeApp( c.resetLocalAttrs(appTransformer.transform(tree)) )
// resetting attributes must be: a) local b) done here and not wider or else there are obscure errors
val tr = makeApp( c.resetLocalAttrs( util.transformWrappers(tree, isWrapper, (tpe, tr) => sub(tpe, tr)) ) )
c.Expr[i.M[T]](tr)
}

View File

@ -79,6 +79,9 @@ trait Init[Scope]
}
def getValue[T](s: Settings[Scope], k: ScopedKey[T]) = s.get(k.scope, k.key) getOrElse error("Internal settings error: invalid reference to " + showFullKey(k))
def asFunction[T](s: Settings[Scope]): ScopedKey[T] => T = k => getValue(s, k)
def mapScope(f: Scope => Scope): MapScoped = new MapScoped {
def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope))
}
def compiled(init: Seq[Setting[_]], actual: Boolean = true)(implicit delegates: Scope => Seq[Scope], scopeLocal: ScopeLocal, display: Show[ScopedKey[_]]): CompiledMap =
{