From 49e7214fe3205b3bf471fed7e1a78524324e2874 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 2 Nov 2012 14:20:17 -0400 Subject: [PATCH] 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`. --- main/Defaults.scala | 56 +++++--- main/settings/Def.scala | 23 ++- main/settings/InputTask.scala | 28 +++- main/settings/Structure.scala | 18 +-- main/settings/TaskMacro.scala | 134 ++++++++++++++++-- main/settings/src/test/scala/UsageTest.scala | 60 +++++++- sbt/package.scala | 2 +- .../sbt-test/java/track-anonymous/build.sbt | 25 ++-- .../tests/test-quick/project/Build.scala | 2 +- scripted/plugin/ScriptedPlugin.scala | 14 +- util/appmacro/ContextUtil.scala | 98 +++++++++++++ util/appmacro/Instance.scala | 99 ++++++------- util/collection/Settings.scala | 3 + 13 files changed, 430 insertions(+), 132 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index 9fe25dc9d..ba42eb4c2 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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(""))(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) diff --git a/main/settings/Def.scala b/main/settings/Def.scala index c378500d0..6c49012e3 100644 --- a/main/settings/Def.scala +++ b/main/settings/Def.scala @@ -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 = ""): 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] = ??? } \ No newline at end of file diff --git a/main/settings/InputTask.scala b/main/settings/InputTask.scala index ecc89244c..9e8bff80f 100644 --- a/main/settings/InputTask.scala +++ b/main/settings/InputTask.scala @@ -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) } diff --git a/main/settings/Structure.scala b/main/settings/Structure.scala index 0fce1cd0d..acdc16081 100644 --- a/main/settings/Structure.scala +++ b/main/settings/Structure.scala @@ -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) diff --git a/main/settings/TaskMacro.scala b/main/settings/TaskMacro.scala index 362576f47..7a1838dd9 100644 --- a/main/settings/TaskMacro.scala +++ b/main/settings/TaskMacro.scala @@ -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[] + val vd = util.freshValDef(keyType, qual.symbol) // val $x: TaskKey[] + 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[, ](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 diff --git a/main/settings/src/test/scala/UsageTest.scala b/main/settings/src/test/scala/UsageTest.scala index 06cb53c94..f1f7a51bb 100644 --- a/main/settings/src/test/scala/UsageTest.scala +++ b/main/settings/src/test/scala/UsageTest.scala @@ -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 - } + }*/ } \ No newline at end of file diff --git a/sbt/package.scala b/sbt/package.scala index 84b5dfad0..a0e1da127 100644 --- a/sbt/package.scala +++ b/sbt/package.scala @@ -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 diff --git a/sbt/src/sbt-test/java/track-anonymous/build.sbt b/sbt/src/sbt-test/java/track-anonymous/build.sbt index a9fad27c1..891fcfbb6 100644 --- a/sbt/src/sbt-test/java/track-anonymous/build.sbt +++ b/sbt/src/sbt-test/java/track-anonymous/build.sbt @@ -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 + () + } } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/test-quick/project/Build.scala b/sbt/src/sbt-test/tests/test-quick/project/Build.scala index d8004918c..e535ddf91 100755 --- a/sbt/src/sbt-test/tests/test-quick/project/Build.scala +++ b/sbt/src/sbt-test/tests/test-quick/project/Build.scala @@ -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 )) } diff --git a/scripted/plugin/ScriptedPlugin.scala b/scripted/plugin/ScriptedPlugin.scala index f3286ede6..3204fa3fa 100644 --- a/scripted/plugin/ScriptedPlugin.scala +++ b/scripted/plugin/ScriptedPlugin.scala @@ -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("")) { 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( diff --git a/util/appmacro/ContextUtil.scala b/util/appmacro/ContextUtil.scala index 3fd45207a..a62846e78 100644 --- a/util/appmacro/ContextUtil.scala +++ b/util/appmacro/ContextUtil.scala @@ -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()`. */ + 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() returns true. + * Typically, `f` is a `Select` or `Ident`. + * The wrapper is replaced with the result of `subWrapper(, )` */ + 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) + } } \ No newline at end of file diff --git a/util/appmacro/Instance.scala b/util/appmacro/Instance.scala index 1dd51e26b..f70941dd0 100644 --- a/util/appmacro/Instance.scala +++ b/util/appmacro/Instance.scala @@ -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.[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) } diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index faa2577cf..d85876496 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -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 = {