From 1ed94bff16430a21179afbf5d944006cacf854f7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 12 Dec 2021 01:49:11 -0500 Subject: [PATCH] Port Converted --- build.sbt | 48 +-- .../sbt/internal/util/appmacro/Instance.scala | 0 .../internal/util/appmacro/KListBuilder.scala | 0 .../internal/util/appmacro/LinterDSL.scala | 0 .../internal/util/appmacro/MixedBuilder.scala | 0 .../util/appmacro/StringTypeTag.scala | 0 .../internal/util/appmacro/TupleBuilder.scala | 0 .../util/appmacro/TupleNBuilder.scala | 0 .../internal/util/appmacro/ContextUtil.scala | 397 +++--------------- .../sbt/internal/util/appmacro/Convert.scala | 112 +++-- .../test/scala/sbt/internal/ConvertTest.scala | 15 + .../scala/sbt/internal/ConvertTestMacro.scala | 42 ++ .../sbt/internal/util/TypeFunctions.scala | 24 +- .../main/scala/sbt/internal/util/Types.scala | 6 +- 14 files changed, 237 insertions(+), 407 deletions(-) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/Instance.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/KListBuilder.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/LinterDSL.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/MixedBuilder.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/StringTypeTag.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/TupleBuilder.scala (100%) rename core-macros/src/main/{scala => scala-2}/sbt/internal/util/appmacro/TupleNBuilder.scala (100%) create mode 100644 core-macros/src/test/scala/sbt/internal/ConvertTest.scala create mode 100644 core-macros/src/test/scala/sbt/internal/ConvertTestMacro.scala diff --git a/build.sbt b/build.sbt index deba99856..6d5ec0392 100644 --- a/build.sbt +++ b/build.sbt @@ -181,7 +181,7 @@ val scriptedSbtReduxMimaSettings = Def.settings(mimaPreviousArtifacts := Set()) lazy val sbtRoot: Project = (project in file(".")) // .aggregate(nonRoots: _*) - .aggregate(collectionProj) + .aggregate(collectionProj, coreMacrosProj) .settings( minimalSettings, onLoadMessage := { @@ -494,7 +494,7 @@ lazy val testingProj = (project in file("testing")) exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestItemEvent.copy$default$*"), exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy"), exclude[DirectMissingMethodProblem]("sbt.protocol.testing.TestStringEvent.copy$default$1"), - //no reason to use + // no reason to use exclude[DirectMissingMethodProblem]("sbt.JUnitXmlTestsListener.testSuite"), ) ) @@ -787,15 +787,8 @@ lazy val commandProj = (project in file("main-command")) lazy val coreMacrosProj = (project in file("core-macros")) .dependsOn(collectionProj) .settings( - baseSettings :+ (crossScalaVersions := (scala212 :: scala213 :: Nil)), + testedBaseSettings :+ (crossScalaVersions := (scala212 :: scala213 :: Nil)), name := "Core Macros", - libraryDependencies += { - if (scalaBinaryVersion.value == "3") { - "org.scala-lang" % "scala-compiler" % scala213 - } else { - "org.scala-lang" % "scala-compiler" % scalaVersion.value - } - }, SettingKey[Boolean]("exportPipelining") := false, mimaSettings, ) @@ -1110,7 +1103,8 @@ lazy val serverTestProj = (project in file("server-test")) |} """.stripMargin } - val file = (Test / target).value / "generated" / "src" / "test" / "scala" / "testpkg" / "TestProperties.scala" + val file = + (Test / target).value / "generated" / "src" / "test" / "scala" / "testpkg" / "TestProperties.scala" IO.write(file, content) file :: Nil }, @@ -1373,8 +1367,7 @@ lazy val lowerUtilProjects = lazy val nonRoots = allProjects.map(p => LocalProject(p.id)) ThisBuild / scriptedBufferLog := true -ThisBuild / scriptedPrescripted := { _ => -} +ThisBuild / scriptedPrescripted := { _ => } def otherRootSettings = Seq( @@ -1442,21 +1435,24 @@ def customCommands: Seq[Setting[_]] = Seq( import extracted._ val sv = get(scalaVersion) val projs = structure.allProjectRefs - val ioOpt = projs find { case ProjectRef(_, id) => id == "ioRoot"; case _ => false } + val ioOpt = projs find { case ProjectRef(_, id) => id == "ioRoot"; case _ => false } val utilOpt = projs find { case ProjectRef(_, id) => id == "utilRoot"; case _ => false } - val lmOpt = projs find { case ProjectRef(_, id) => id == "lmRoot"; case _ => false } + val lmOpt = projs find { case ProjectRef(_, id) => id == "lmRoot"; case _ => false } val zincOpt = projs find { case ProjectRef(_, id) => id == "zincRoot"; case _ => false } - (ioOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: - (utilOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: - (lmOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: - (zincOpt map { - case ProjectRef(build, _) => - val zincSv = get((ProjectRef(build, "zinc") / scalaVersion)) - val csv = get((ProjectRef(build, "compilerBridge") / crossScalaVersions)).toList - (csv flatMap { bridgeSv => - s"++$bridgeSv" :: ("{" + build.toString + "}compilerBridge/publishLocal") :: Nil - }) ::: - List(s"++$zincSv", "{" + build.toString + "}/publishLocal") + (ioOpt map { case ProjectRef(build, _) => "{" + build.toString + "}/publishLocal" }).toList ::: + (utilOpt map { case ProjectRef(build, _) => + "{" + build.toString + "}/publishLocal" + }).toList ::: + (lmOpt map { case ProjectRef(build, _) => + "{" + build.toString + "}/publishLocal" + }).toList ::: + (zincOpt map { case ProjectRef(build, _) => + val zincSv = get((ProjectRef(build, "zinc") / scalaVersion)) + val csv = get((ProjectRef(build, "compilerBridge") / crossScalaVersions)).toList + (csv flatMap { bridgeSv => + s"++$bridgeSv" :: ("{" + build.toString + "}compilerBridge/publishLocal") :: Nil + }) ::: + List(s"++$zincSv", "{" + build.toString + "}/publishLocal") }).getOrElse(Nil) ::: List(s"++$sv", "publishLocal") ::: state diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/Instance.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/Instance.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/Instance.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/KListBuilder.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/KListBuilder.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/KListBuilder.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/LinterDSL.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/LinterDSL.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/LinterDSL.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/MixedBuilder.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/MixedBuilder.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/MixedBuilder.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/StringTypeTag.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/StringTypeTag.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/StringTypeTag.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/StringTypeTag.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/TupleBuilder.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/TupleBuilder.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/TupleBuilder.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala b/core-macros/src/main/scala-2/sbt/internal/util/appmacro/TupleNBuilder.scala similarity index 100% rename from core-macros/src/main/scala/sbt/internal/util/appmacro/TupleNBuilder.scala rename to core-macros/src/main/scala-2/sbt/internal/util/appmacro/TupleNBuilder.scala diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala index 17d8672cb..9969c0df4 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala @@ -1,338 +1,77 @@ -/* - * sbt - * Copyright 2011 - 2018, Lightbend, Inc. - * Copyright 2008 - 2010, Mark Harrah - * Licensed under Apache License 2.0 (see LICENSE) - */ +package sbt.internal.util.appmacro -package sbt.internal.util -package appmacro +import sbt.internal.util.Types.Id +import scala.compiletime.summonInline +import scala.quoted.* +import scala.reflect.TypeTest -import scala.reflect._ -import macros._ -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 <: blackbox.Context with Singleton](c: C): ContextUtil[C] = new ContextUtil(c: 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]( - c: blackbox.Context - )(f: (c.Expr[Any], c.Position) => c.Expr[T]): c.Expr[T] = { - import c.universe._ - - c.macroApplication match { - case s @ Select(Apply(_, t :: Nil), _) => f(c.Expr[Any](t), s.pos) - case a @ Apply(_, t :: Nil) => f(c.Expr[Any](t), a.pos) - case x => unexpectedTree(x) - } - } - - def unexpectedTree[C <: blackbox.Context](tree: C#Tree): Nothing = - sys.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`). This is not thread safe due to the underlying Context and related - * data structures not being thread safe. Use `ContextUtil[c.type](c)` to construct. - */ -final class ContextUtil[C <: blackbox.Context](val ctx: C) { - import ctx.universe.{ Apply => ApplyTree, _ } - import internal.decorators._ - - val powerContext = ctx.asInstanceOf[reflect.macros.runtime.Context] - val global: powerContext.universe.type = powerContext.universe - def callsiteTyper: global.analyzer.Typer = powerContext.callsiteTyper - val initialOwner: Symbol = callsiteTyper.context.owner.asInstanceOf[ctx.universe.Symbol] - - lazy val alistType = ctx.typeOf[AList[KList]] - lazy val alist: Symbol = alistType.typeSymbol.companion - lazy val alistTC: Type = alistType.typeConstructor - - /** Modifiers for a local val. */ - lazy val localModifiers = Modifiers(NoFlags) - - def getPos(sym: Symbol) = if (sym eq null) NoPosition else sym.pos - - /** - * Constructs a unique term name with the given prefix within this Context. (The current - * implementation uses Context.freshName, which increments - */ - def freshTermName(prefix: String) = TermName(ctx.freshName("$" + prefix)) - - /** - * Constructs a new, synthetic, local ValDef Type `tpe`, a unique name, Position `pos`, an empty - * implementation (no rhs), and owned by `owner`. - */ - def freshValDef(tpe: Type, pos: Position, owner: Symbol): ValDef = { - val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet] - val sym = owner.newTermSymbol(freshTermName("q"), pos, SYNTHETIC) - setInfo(sym, tpe) - val vd = internal.valDef(sym, EmptyTree) - vd.setPos(pos) - vd - } - - 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: (String, Type, 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(Select(_, nme), tpe :: Nil), qual :: Nil) - if isWrapper(nme.decodedName.toString, tpe.tpe, qual) => - () - 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, mType: Type): Boolean = - sym != null && sym != NoSymbol && defs.contains(sym) && { - sym match { - case m: MethodSymbol => m.returnType.erasure <:< mType - case _ => sym.typeSignature <:< mType - } - } - - /** - * 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 = - illegalReference(defs, sym, weakTypeOf[Any]) - - type PropertyChecker = (String, Type, Tree) => Boolean - - /** - * 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: PropertyChecker, - mType: Type - ): Tree => Unit = { - case s @ ApplyTree(TypeApply(Select(_, nme), tpe :: Nil), qual :: Nil) => - if (isWrapper(nme.decodedName.toString, tpe.tpe, qual)) { - ctx.error(s.pos, DynamicDependencyError) - } - case id @ Ident(name) if illegalReference(defs, id.symbol, mType) => - ctx.error(id.pos, DynamicReferenceError + ": " + name) - case _ => () - } - - @deprecated("Use that variant that specifies the M instance types to exclude", since = "1.3.0") - /** - * 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: PropertyChecker): Tree => Unit = - checkReferences(defs, isWrapper, weakTypeOf[Any]) - - /** - * 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) - - /** Constructs a tuple value of the right TupleN type from the provided inputs. */ - def mkTuple(args: List[Tree]): Tree = - global.gen.mkTuple(args.asInstanceOf[List[global.Tree]]).asInstanceOf[ctx.universe.Tree] - - def setSymbol[_Tree](t: _Tree, sym: Symbol): Unit = { - t.asInstanceOf[global.Tree].setSymbol(sym.asInstanceOf[global.Symbol]) - () - } - def setInfo(sym: Symbol, tpe: Type): Unit = { - sym.asInstanceOf[global.Symbol].setInfo(tpe.asInstanceOf[global.Type]) - () - } - - /** Creates a new, synthetic type variable with the specified `owner`. */ - def newTypeVariable(owner: Symbol, prefix: String = "T0"): TypeSymbol = - owner - .asInstanceOf[global.Symbol] - .newSyntheticTypeParam(prefix, 0L) - .asInstanceOf[ctx.universe.TypeSymbol] - - /** The type representing the type constructor `[X] X` */ - lazy val idTC: Type = { - val tvar = newTypeVariable(NoSymbol) - internal.polyType(tvar :: Nil, refVar(tvar)) - } - - /** A Type that references the given type variable. */ - def refVar(variable: TypeSymbol): Type = variable.toTypeConstructor - - /** - * Constructs a new, synthetic type variable that is a type constructor. For example, in type - * Y[L[x]], L is such a type variable. - */ - def newTCVariable(owner: Symbol): TypeSymbol = { - val tc = newTypeVariable(owner) - val arg = newTypeVariable(tc, "x"); - tc.setInfo(internal.polyType(arg :: Nil, emptyTypeBounds)) - tc - } - - /** >: Nothing <: Any */ - def emptyTypeBounds: TypeBounds = - internal.typeBounds(definitions.NothingClass.toType, definitions.AnyClass.toType) - - /** Creates a new anonymous function symbol with Position `pos`. */ - def functionSymbol(pos: Position): Symbol = - callsiteTyper.context.owner - .newAnonymousFunctionValue(pos.asInstanceOf[global.Position]) - .asInstanceOf[ctx.universe.Symbol] - - def functionType(args: List[Type], result: Type): Type = { - val tpe = global.definitions - .functionType(args.asInstanceOf[List[global.Type]], result.asInstanceOf[global.Type]) - tpe.asInstanceOf[Type] - } - - /** - * Create a Tree that references the `val` represented by `vd`, copying attributes from - * `replaced`. - */ - def refVal(replaced: Tree, vd: ValDef): Tree = - treeCopy.Ident(replaced, vd.name).setSymbol(vd.symbol) - - /** - * Creates a Function tree using `functionSym` as the Symbol and changing `initialOwner` to - * `functionSym` in `body`. - */ - def createFunction(params: List[ValDef], body: Tree, functionSym: Symbol): Tree = { - changeOwner(body, initialOwner, functionSym) - val f = Function(params, body) - setSymbol(f, functionSym) - f - } - - def changeOwner(tree: Tree, prev: Symbol, next: Symbol): Unit = - new ChangeOwnerAndModuleClassTraverser( - prev.asInstanceOf[global.Symbol], - next.asInstanceOf[global.Symbol] - ).traverse(tree.asInstanceOf[global.Tree]) - - // Workaround copied from scala/async:can be removed once https://github.com/scala/scala/pull/3179 is merged. - private[this] class ChangeOwnerAndModuleClassTraverser( - oldowner: global.Symbol, - newowner: global.Symbol - ) extends global.ChangeOwnerTraverser(oldowner, newowner) { - override def traverse(tree: global.Tree): Unit = { - tree match { - case _: global.DefTree => change(tree.symbol.moduleClass) - case _ => - } - super.traverse(tree) - } - } - - /** 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 { - case SingleType(_, sym) if !sym.isFreeTerm && sym.isStatic => sym - case x => sys.error("Instance must be static (was " + x + ").") - } - - def select(t: Tree, name: String): Tree = Select(t, TermName(name)) - - /** Returns the symbol for the non-private method named `name` for the class/module `obj`. */ - def method(obj: Symbol, name: String): Symbol = { - val ts: Type = obj.typeSignature - val m: global.Symbol = ts.asInstanceOf[global.Type].nonPrivateMember(global.newTermName(name)) - m.asInstanceOf[Symbol] - } +trait ContextUtil[C <: Quotes & scala.Singleton](val qctx: C): + import qctx.reflect.* + given qctx.type = qctx /** * Returns a Type representing the type constructor tcp.. For example, given `object Demo { - * type M[x] = List[x] }`, the call `extractTC(Demo, "M")` will return a type representing the - * type constructor `[x] List[x]`. + * type M[x] = List[x] }`, the call `extractTypeCon(Demo, "M")` will return a type representing + * the type constructor `[x] List[x]`. */ - def extractTC(tcp: AnyRef with Singleton, name: String)(implicit - it: ctx.TypeTag[tcp.type] - ): ctx.Type = { - val itTpe = it.tpe.asInstanceOf[global.Type] - val m = itTpe.nonPrivateMember(global.newTypeName(name)) - val tc = itTpe.memberInfo(m).asInstanceOf[ctx.universe.Type] - assert(tc != NoType && tc.takesTypeArgs, "Invalid type constructor: " + tc) - tc - } + def extractTypeCon(tcp: AnyRef & scala.Singleton, name: String)(using + tcpt: Type[tcp.type] + ): TypeRepr = + val tcpTpe = TypeRepr.of[tcp.type] + val fSym = tcpTpe.typeSymbol.declaredType(name).head + val typeConTpe: TypeRepr = tcpTpe.memberType(fSym) + val hiRepr = typeConTpe match + case TypeBounds(low, TypeLambda(_, _, AppliedType(tc, _))) => tc + hiRepr /** - * Substitutes wrappers in tree `t` with the result of `subWrapper`. A wrapper is a Tree of the - * form `f[T](v)` for which isWrapper(, , .target) returns true. - * Typically, `f` is a `Select` or `Ident`. The wrapper is replaced with the result of - * `subWrapper(, , )` + * Returns a reference given a singleton/termref */ - def transformWrappers( - t: Tree, - subWrapper: (String, Type, Tree, Tree) => Converted[ctx.type] - ): 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(Select(_, nme), targ :: Nil), qual :: Nil) => - subWrapper(nme.decodedName.toString, targ.tpe, qual, tree) match { - case Converted.Success(t, finalTx) => - changeOwner( - qual, - currentOwner, - initialOwner - ) // Fixes https://github.com/sbt/sbt/issues/1150 - finalTx(t) - case Converted.Failure(p, m) => ctx.abort(p, m) - case _: Converted.NotApplicable[_] => super.transform(tree) - } - case _ => super.transform(tree) - } - } - appTransformer.atOwner(initialOwner) { - appTransformer.transform(t) - } - } -} + def extractSingleton[A: Type]: Expr[A] = + def termRef(r: TypeRepr)(using rtt: TypeTest[TypeRepr, TermRef]): Ref = r match + case rtt(ref) => Ref.term(ref) + case _ => sys.error(s"expected termRef but got $r") + termRef(TypeRepr.of[A]).asExprOf[A] + + private var counter: Int = -1 + def freshName(prefix: String): String = + counter = counter + 1 + s"$$${prefix}${counter}" + + /** + * Constructs a new, synthetic, local var with type `tpe`, a unique name, initialized to + * zero-equivalent (Zero[A]), and owned by `parent`. + */ + def freshValDef(parent: Symbol, tpe: TypeRepr, rhs: Term): ValDef = + tpe.asType match + case '[a] => + val sym = + Symbol.newVal( + parent, + freshName("q"), + tpe, + Flags.Synthetic, + Symbol.noSymbol + ) + ValDef(sym, rhs = Some(rhs)) + + def typed[A: Type](value: Term): Term = + Typed(value, TypeTree.of[A]) + + def tupleTypeRepr(param: List[TypeRepr]): TypeRepr = + param match + case x :: xs => TypeRepr.of[scala.*:].appliedTo(List(x, tupleTypeRepr(xs))) + case Nil => TypeRepr.of[EmptyTuple] + + final class Input( + val tpe: TypeRepr, + val term: Term, + val name: String + ) + + trait TermTransform[F[_]]: + def apply(in: Term): Term + end TermTransform + + def idTransform: TermTransform[Id] = in => in +end ContextUtil diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala index a3e3a754b..f5741d651 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Convert.scala @@ -5,47 +5,75 @@ * Licensed under Apache License 2.0 (see LICENSE) */ -package sbt.internal.util -package appmacro +package sbt.internal.util.appmacro -import scala.reflect._ -import macros._ -import Types.idFun +import sbt.internal.util.Types +import scala.quoted.* -abstract class Convert { - def apply[T: c.WeakTypeTag](c: blackbox.Context)(nme: String, in: c.Tree): Converted[c.type] - def asPredicate(c: blackbox.Context): (String, c.Type, c.Tree) => Boolean = - (n, tpe, tree) => { - val tag = c.WeakTypeTag(tpe) - apply(c)(n, tree)(tag).isSuccess - } -} -sealed trait Converted[C <: blackbox.Context with Singleton] { - def isSuccess: Boolean - def transform(f: C#Tree => C#Tree): Converted[C] -} -object Converted { - def NotApplicable[C <: blackbox.Context with Singleton] = new NotApplicable[C] - final case class Failure[C <: blackbox.Context with Singleton]( - position: C#Position, - message: String - ) extends Converted[C] { - def isSuccess = false - def transform(f: C#Tree => C#Tree): Converted[C] = new Failure(position, message) - } - final class NotApplicable[C <: blackbox.Context with Singleton] extends Converted[C] { - def isSuccess = false - def transform(f: C#Tree => C#Tree): Converted[C] = this - } - final case class Success[C <: blackbox.Context with Singleton]( - tree: C#Tree, - finalTransform: C#Tree => C#Tree - ) extends Converted[C] { - def isSuccess = true - def transform(f: C#Tree => C#Tree): Converted[C] = Success(f(tree), finalTransform) - } - object Success { - def apply[C <: blackbox.Context with Singleton](tree: C#Tree): Success[C] = - Success(tree, idFun) - } -} +/** + * Convert is a glorified partial function to scan through the AST for the purpose of substituting + * the matching term with something else. + * + * This is driven by calling transformWrappers(...) method. The filtering is limited to the shape of + * code matched using `appTransformer`, which is a generic function with a single type param and a + * single term param like `X.wrapInit[A](...)`. + */ +trait Convert[C <: Quotes & Singleton](override val qctx: C) extends ContextUtil[C]: + // with TupleBuilder[C]: + import qctx.reflect.* + + def convert[A: Type](nme: String, in: Term): Converted + + def asPredicate[A]: (String, Type[A], Term) => Boolean = + (n, tpe, tree) => + val tag = tpe + convert(n, tree)(tag).isSuccess + + /** + * Substitutes wrappers in tree `t` with the result of `subWrapper`. A wrapper is a Tree of the + * form `f[T](v)` for which isWrapper(, , .target) returns true. + * Typically, `f` is a `Select` or `Ident`. The wrapper is replaced with the result of + * `subWrapper(, , )` + */ + def transformWrappers( + tree: Term, + subWrapper: (String, TypeRepr, Term, Term) => Converted, + owner: Symbol, + ): Term = + // the main tree transformer that replaces calls to InputWrapper.wrap(x) with + // plain Idents that reference the actual input value + object appTransformer extends TreeMap: + override def transformTerm(tree: Term)(owner: Symbol): Term = + tree match + case Apply(TypeApply(Select(_, nme), targ :: Nil), qual :: Nil) => + subWrapper(nme, targ.tpe, qual, tree) match + case Converted.Success(tree, finalTransform) => + finalTransform(tree) + case Converted.Failure(position, message) => + report.error(message, position) + sys.error("macro error: " + message) + case _ => + super.transformTerm(tree)(owner) + case _ => + super.transformTerm(tree)(owner) + end appTransformer + appTransformer.transformTerm(tree)(owner) + + object Converted: + def success(tree: Term) = Converted.Success(tree, Types.idFun) + + enum Converted: + def isSuccess: Boolean = this match + case Success(_, _) => true + case _ => false + + def transform(f: Term => Term): Converted = this match + case Success(tree, finalTransform) => Success(f(tree), finalTransform) + case x: Failure => x + case x: NotApplicable => x + + case Success(tree: Term, finalTransform: Term => Term) extends Converted + case Failure(position: Position, message: String) extends Converted + case NotApplicable() extends Converted + end Converted +end Convert diff --git a/core-macros/src/test/scala/sbt/internal/ConvertTest.scala b/core-macros/src/test/scala/sbt/internal/ConvertTest.scala new file mode 100644 index 000000000..c36f6b6ae --- /dev/null +++ b/core-macros/src/test/scala/sbt/internal/ConvertTest.scala @@ -0,0 +1,15 @@ +package sbt.internal + +import sbt.internal.util.appmacro.* +import verify.* +import ConvertTestMacro._ + +object ConvertTest extends BasicTestSuite: + test("convert") { + // assert(someMacro(ConvertTest.wrapInit(1) == 2)) + assert(someMacro(ConvertTest.wrapInit(1).toString == "Some(2)")) + } + + def wrapInitTask[A](a: A): Int = 2 + def wrapInit[A](a: A): Int = 2 +end ConvertTest diff --git a/core-macros/src/test/scala/sbt/internal/ConvertTestMacro.scala b/core-macros/src/test/scala/sbt/internal/ConvertTestMacro.scala new file mode 100644 index 000000000..afdade511 --- /dev/null +++ b/core-macros/src/test/scala/sbt/internal/ConvertTestMacro.scala @@ -0,0 +1,42 @@ +package sbt.internal + +import sbt.internal.util.appmacro.* +import scala.quoted.* + +object ConvertTestMacro: + final val WrapInitName = "wrapInit" + final val WrapInitTaskName = "wrapInitTask" + + inline def someMacro(inline expr: Boolean): Boolean = + ${ someMacroImpl('expr) } + + def someMacroImpl(expr: Expr[Boolean])(using qctx: Quotes) = + val convert1: Convert[qctx.type] = new InputInitConvert(qctx) + import convert1.qctx.reflect.* + def addTypeCon(tpe: TypeRepr, qual: Term, selection: Term): Term = + tpe.asType match + case '[a] => + '{ + Option[a](${ selection.asExprOf[a] }) + }.asTerm + def substitute(name: String, tpe: TypeRepr, qual: Term, replace: Term) = + convert1.convert[Boolean](name, qual) transform { (tree: Term) => + addTypeCon(tpe, tree, replace) + } + convert1.transformWrappers(expr.asTerm, substitute, Symbol.spliceOwner).asExprOf[Boolean] + + class InputInitConvert[C <: Quotes & scala.Singleton](override val qctx: C) + extends Convert[C](qctx) + with ContextUtil[C](qctx): + // with TupleBuilder[C](qctx) + // with TupleNBuilder[C](qctx): + import qctx.reflect.* + def convert[A: Type](nme: String, in: Term): Converted = + nme match + case WrapInitName => Converted.success(in) + case WrapInitTaskName => Converted.Failure(in.pos, initTaskErrorMessage) + case _ => Converted.NotApplicable() + + private def initTaskErrorMessage = "Internal sbt error: initialize+task wrapper not split" + end InputInitConvert +end ConvertTestMacro diff --git a/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala b/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala index faee1aaec..a205d2baa 100644 --- a/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala +++ b/util-collection/src/main/scala/sbt/internal/util/TypeFunctions.scala @@ -7,11 +7,12 @@ package sbt.internal.util -/* -trait TypeFunctions { - import TypeFunctions._ +trait TypeFunctions: type Id[X] = X type NothingK[X] = Nothing + + /* + import TypeFunctions._ sealed trait Const[A] { type Apply[B] = A } sealed trait ConstK[A] { type l[L[x]] = A } sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] } @@ -24,8 +25,10 @@ trait TypeFunctions { final val right: Id ~> Right[Nothing, *] = λ[Id ~> AnyRight](Right(_)).setToString("TypeFunctions.right") final val some: Id ~> Some[*] = λ[Id ~> Some](Some(_)).setToString("TypeFunctions.some") - final def idFun[T]: T => T = ((t: T) => t).setToString("TypeFunctions.id") - final def const[A, B](b: B): A => B = ((_: A) => b).setToString(s"TypeFunctions.const($b)") + */ + final def idFun[A]: A => A = ((a: A) => a) // .setToString("TypeFunctions.id") + final def const[A, B](b: B): A => B = ((_: A) => b) // .setToString(s"TypeFunctions.const($b)") +/* final def idK[M[_]]: M ~> M = λ[M ~> M](m => m).setToString("TypeFunctions.idK") def nestCon[M[_], N[_], G[_]](f: M ~> N): (M ∙ G)#l ~> (N ∙ G)#l = @@ -35,9 +38,13 @@ trait TypeFunctions { type Endo[T] = T => T type ~>|[A[_], B[_]] = A ~> Compose[Option, B]#Apply -} + */ + +end TypeFunctions + +/* +object TypeFunctions extends TypeFunctions: -object TypeFunctions extends TypeFunctions { private implicit class Ops[T[_], R[_]](val underlying: T ~> R) extends AnyVal { def setToString(string: String): T ~> R = new (T ~> R) { override def apply[U](a: T[U]): R[U] = underlying(a) @@ -54,7 +61,8 @@ object TypeFunctions extends TypeFunctions { override def hashCode: Int = f.hashCode } } -} + +end TypeFunctions */ /* diff --git a/util-collection/src/main/scala/sbt/internal/util/Types.scala b/util-collection/src/main/scala/sbt/internal/util/Types.scala index 4d01d963e..5ae6dbb02 100644 --- a/util-collection/src/main/scala/sbt/internal/util/Types.scala +++ b/util-collection/src/main/scala/sbt/internal/util/Types.scala @@ -7,10 +7,12 @@ package sbt.internal.util -object Types extends Types +object Types extends TypeFunctions -trait Types /* extends TypeFunctions */ { +/* +trait Types extends TypeFunctions { // val :^: = KCons // type :+:[H, T <: HList] = HCons[H, T] // val :+: = HCons } + */