|
| 1 | +/* |
| 2 | + * Copyright 2022 Typelevel |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package org.typelevel.sbt |
| 18 | + |
| 19 | +import scala.annotation.tailrec |
| 20 | +import scala.reflect.macros.blackbox |
| 21 | + |
| 22 | +private[sbt] object CrossRootProjectMacros { |
| 23 | + |
| 24 | + // Copied from sbt.std.KeyMacro |
| 25 | + def definingValName(c: blackbox.Context, invalidEnclosingTree: String => String): String = { |
| 26 | + import c.universe.{Apply => ApplyTree, _} |
| 27 | + val methodName = c.macroApplication.symbol.name |
| 28 | + def processName(n: Name): String = |
| 29 | + n.decodedName |
| 30 | + .toString |
| 31 | + .trim // trim is not strictly correct, but macros don't expose the API necessary |
| 32 | + @tailrec def enclosingVal(trees: List[c.Tree]): String = { |
| 33 | + trees match { |
| 34 | + case ValDef(_, name, _, _) :: _ => processName(name) |
| 35 | + case (_: ApplyTree | _: Select | _: TypeApply) :: xs => enclosingVal(xs) |
| 36 | + // lazy val x: X = <methodName> has this form for some reason (only when the explicit type is present, though) |
| 37 | + case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: _ if mods.hasFlag(Flag.LAZY) => |
| 38 | + processName(name) |
| 39 | + case _ => |
| 40 | + c.error(c.enclosingPosition, invalidEnclosingTree(methodName.decodedName.toString)) |
| 41 | + "<error>" |
| 42 | + } |
| 43 | + } |
| 44 | + enclosingVal(enclosingTrees(c).toList) |
| 45 | + } |
| 46 | + |
| 47 | + // Copied from sbt.std.KeyMacro |
| 48 | + def enclosingTrees(c: blackbox.Context): Seq[c.Tree] = |
| 49 | + c.asInstanceOf[reflect.macros.runtime.Context] |
| 50 | + .callsiteTyper |
| 51 | + .context |
| 52 | + .enclosingContextChain |
| 53 | + .map(_.tree.asInstanceOf[c.Tree]) |
| 54 | + |
| 55 | + def crossRootProjectImpl(c: blackbox.Context): c.Expr[CrossRootProject] = { |
| 56 | + import c.universe._ |
| 57 | + |
| 58 | + val enclosingValName = definingValName( |
| 59 | + c, |
| 60 | + methodName => |
| 61 | + s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `org.typelevel.sbt.CrossRootProject.apply`""" |
| 62 | + ) |
| 63 | + |
| 64 | + val name = c.Expr[String](Literal(Constant(enclosingValName))) |
| 65 | + |
| 66 | + reify { CrossRootProject(name.splice) } |
| 67 | + } |
| 68 | +} |
0 commit comments