Skip to content

Commit 7e5e37e

Browse files
authored
Merge pull request #245 from OlegYch/scala3-fixes
Fix getting deps from inherited members on scala3
2 parents a57c672 + c8ac3f9 commit 7e5e37e

22 files changed

+162
-68
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
*.ipr
44
target
55
out
6+
.sbt
7+
.bsp

macros/src/main/scala-3/com/softwaremill/macwire/internals/EligibleValuesFinder.scala

+99-68
Original file line numberDiff line numberDiff line change
@@ -16,74 +16,32 @@ private[macwire] class EligibleValuesFinder[Q <: Quotes](log: Logger)(using val
1616
val wiredOwner = wiredDef.owner
1717

1818
def doFind(symbol: Symbol, scope: Scope): EligibleValues = {
19-
def hasModuleAnnotation(symbol: Symbol): Boolean =
20-
symbol.annotations.map(_.tpe.show).exists(_ == "com.softwaremill.macwire.Module")
21-
22-
def inspectModule(scope: Scope, tpe: TypeRepr, expr: Tree): Option[EligibleValues] = {
23-
// it might be a @Module, let's see
24-
val hasSymbol = expr.symbol != null // sometimes expr has no symbol...
25-
val valIsModule = hasSymbol && hasModuleAnnotation(expr.symbol)
26-
// the java @Inherited meta-annotation does not seem to be understood by scala-reflect...
27-
val valParentIsModule = hasSymbol && !valIsModule && tpe.baseClasses.exists(hasModuleAnnotation)
28-
29-
if (valIsModule || valParentIsModule) {
30-
log.withBlock(s"Inspecting module $tpe") {
31-
val r = expr.symbol.declarations.map(_.tree).map {
32-
case m: ValDef =>
33-
EligibleValue(
34-
m.tpt.tpe,
35-
This(expr.symbol.owner).select(expr.symbol).select(m.symbol)
36-
)
37-
case m: DefDef if m.termParamss.flatMap(_.params).isEmpty =>
38-
EligibleValue(m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe), Select.unique(Ref(expr.symbol), m.name))
39-
}
40-
41-
Some(EligibleValues(Map(scope.widen -> r)))
42-
}
43-
} else {
44-
None
45-
}
46-
}
47-
48-
def buildEligibleValue(scope: Scope): PartialFunction[Tree, EligibleValues] = {
49-
case m: ValDef =>
50-
merge(
51-
inspectModule(scope.widen, m.rhs.map(_.tpe).getOrElse(m.tpt.tpe), m).getOrElse(EligibleValues.empty),
52-
EligibleValues(Map(scope -> List(EligibleValue(m.rhs.map(_.tpe).getOrElse(m.tpt.tpe), m))))
53-
)
54-
55-
case m: DefDef if m.termParamss.flatMap(_.params).isEmpty =>
56-
merge(
57-
inspectModule(scope.widen, m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe), m).getOrElse(EligibleValues.empty),
58-
EligibleValues(Map(scope -> List(EligibleValue(m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe), m))))
59-
)
60-
61-
}
62-
6319
def handleClassDef(scope: Scope, s: Symbol): EligibleValues = {
64-
(s.declaredMethods ::: s.declaredFields)
65-
.filter(m => !m.fullName.startsWith("java.lang.Object") && !m.fullName.startsWith("scala.Any"))
66-
.map(_.tree)
67-
.foldLeft(EligibleValues.empty)((values, tree) =>
68-
merge(values, buildEligibleValue(scope).applyOrElse(tree, _ => EligibleValues.empty))
69-
)
70-
20+
val declared = EligibleValues.build(
21+
symbol,
22+
scope,
23+
s.declarations.filter(nonSyntethic).map(_.tree)
24+
)
25+
val inherited = EligibleValues.build(
26+
symbol,
27+
scope.widen,
28+
(s.memberFields ::: s.memberMethods)
29+
.filter(m => nonSyntethic(m) && isPublic(m) && !s.declarations.contains(m))
30+
.map(_.tree)
31+
)
32+
EligibleValues.merge(declared, inherited)
7133
}
7234

7335
def handleDefDef(scope: Scope, s: Symbol): EligibleValues =
7436
s.tree match {
7537
case DefDef(_, _, _, Some(Match(_, cases))) =>
7638
report.throwError(s"Wire for deconstructed case is not supported yet") //TODO
7739
case DefDef(s, lpc, tt, ot) =>
78-
lpc
79-
.flatMap(_.params)
80-
.foldLeft(EligibleValues.empty)((values, tree) =>
81-
merge(values, buildEligibleValue(scope).applyOrElse(tree, _ => EligibleValues.empty))
82-
)
40+
EligibleValues.build(symbol, scope, lpc.flatMap(_.params))
8341
}
8442

8543
if symbol.isNoSymbol then EligibleValues.empty
86-
else if symbol.isDefDef then merge(handleDefDef(scope, symbol), doFind(symbol.maybeOwner, scope))
44+
else if symbol.isDefDef then EligibleValues.merge(handleDefDef(scope, symbol), doFind(symbol.maybeOwner, scope))
8745
else if symbol.isClassDef && !symbol.isPackageDef then handleClassDef(scope.widen, symbol)
8846
else if symbol == defn.RootPackage then EligibleValues.empty
8947
else if symbol == defn.RootClass then EligibleValues.empty
@@ -93,17 +51,19 @@ private[macwire] class EligibleValuesFinder[Q <: Quotes](log: Logger)(using val
9351
doFind(Symbol.spliceOwner, Scope.init)
9452
}
9553

96-
private def merge(
97-
ev1: EligibleValues,
98-
ev2: EligibleValues
99-
): EligibleValues =
100-
EligibleValues(merge(ev1.values, ev2.values))
54+
private def nonSyntethic(member: Symbol): Boolean = {
55+
!member.fullName.startsWith("java.lang.Object") &&
56+
!member.fullName.startsWith("scala.Any") &&
57+
!member.fullName.startsWith("scala.AnyRef") &&
58+
!member.fullName.endsWith("<init>") &&
59+
!member.fullName.endsWith("$init$") &&
60+
!member.fullName.contains("$default$") && //default params for copy on case classes
61+
!member.fullName.matches(".*_\\d+") //tuple methods on case classes
62+
}
10163

102-
private def merge(
103-
m1: Map[Scope, List[EligibleValue]],
104-
m2: Map[Scope, List[EligibleValue]]
105-
): Map[Scope, List[EligibleValue]] =
106-
(m1.toSeq ++ m2.toSeq).groupBy(_._1).view.mapValues(_.flatMap(_._2).toList).toMap
64+
private def isPublic(member: Symbol): Boolean = {
65+
!((member.flags is Flags.Private) || (member.flags is Flags.Protected))
66+
}
10767

10868
case class EligibleValue(tpe: TypeRepr, expr: Tree) {
10969
// equal trees should have equal hash codes; if trees are equal structurally they should have the same toString?
@@ -165,8 +125,79 @@ private[macwire] class EligibleValuesFinder[Q <: Quotes](log: Logger)(using val
165125

166126
object EligibleValues {
167127
val empty: EligibleValues = new EligibleValues(Map.empty)
168-
}
169128

129+
private def inspectModule(scope: Scope, tpe: TypeRepr, expr: Tree) = {
130+
def hasModuleAnnotation(symbol: Symbol): Boolean =
131+
symbol.annotations.map(_.tpe.show).exists(_ == "com.softwaremill.macwire.Module")
132+
133+
// it might be a @Module, let's see
134+
val hasSymbol = expr.symbol != null // sometimes expr has no symbol...
135+
val valIsModule = hasSymbol && hasModuleAnnotation(expr.symbol)
136+
// the java @Inherited meta-annotation does not seem to be understood by scala-reflect...
137+
val valParentIsModule = hasSymbol && !valIsModule && tpe.baseClasses.exists(hasModuleAnnotation)
138+
139+
if (valIsModule || valParentIsModule) {
140+
log.withBlock(s"Inspecting module $tpe") {
141+
val r = (expr.symbol.memberFields ::: expr.symbol.memberMethods)
142+
.filter(m => nonSyntethic(m) && isPublic(m))
143+
.map(_.tree)
144+
.collect {
145+
case m: ValDef =>
146+
EligibleValue(
147+
m.tpt.tpe,
148+
This(expr.symbol.owner).select(expr.symbol).select(m.symbol)
149+
)
150+
case m: DefDef if m.termParamss.flatMap(_.params).isEmpty =>
151+
EligibleValue(
152+
m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe),
153+
This(expr.symbol.owner).select(expr.symbol).select(m.symbol)
154+
)
155+
}
156+
EligibleValues(Map(scope.widen -> r))
157+
}
158+
} else {
159+
EligibleValues.empty
160+
}
161+
}
162+
163+
private def buildEligibleValue(symbol: Symbol, scope: Scope): PartialFunction[Tree, EligibleValues] = {
164+
case m: ValDef =>
165+
merge(
166+
inspectModule(scope.widen, m.rhs.map(_.tpe).getOrElse(m.tpt.tpe), m),
167+
EligibleValues(
168+
Map(
169+
scope -> List(
170+
EligibleValue(
171+
m.rhs.map(_.tpe).getOrElse(m.tpt.tpe),
172+
if symbol.isClassDef then This(symbol).select(m.symbol) else m
173+
)
174+
)
175+
)
176+
)
177+
)
178+
case m: DefDef if m.termParamss.flatMap(_.params).isEmpty =>
179+
merge(
180+
inspectModule(scope.widen, m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe), m),
181+
EligibleValues(
182+
Map(
183+
scope -> List(
184+
EligibleValue(
185+
m.rhs.map(_.tpe).getOrElse(m.returnTpt.tpe),
186+
if symbol.isClassDef then This(symbol).select(m.symbol) else m
187+
)
188+
)
189+
)
190+
)
191+
)
192+
}
193+
194+
def build(symbol: Symbol, scope: Scope, l: Iterable[Tree]) = l.foldLeft(empty)((values, tree) =>
195+
merge(values, buildEligibleValue(symbol, scope).applyOrElse(tree, _ => EligibleValues.empty))
196+
)
197+
198+
def merge(ev1: EligibleValues, ev2: EligibleValues): EligibleValues =
199+
EligibleValues((ev1.values.toSeq ++ ev2.values.toSeq).groupBy(_._1).view.mapValues(_.flatMap(_._2).toList).toMap)
200+
}
170201
}
171202

172203
object EligibleValuesFinder {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include commonSimpleClasses
2+
3+
trait Base {
4+
protected lazy val protectedA: A = A()
5+
lazy val publicA: A = A()
6+
}
7+
8+
object Module extends Base {
9+
lazy val b: B = wire[B]
10+
lazy val c: C = wire[C]
11+
}
12+
13+
require(Module.c.a eq Module.publicA)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include commonSimpleClasses
2+
3+
trait Base {
4+
val aA: A = A()
5+
}
6+
7+
object Module extends Base {
8+
val theA: A = A()
9+
lazy val b: B = wire[B]
10+
lazy val c: C = wire[C]
11+
}
12+
13+
require(Module.c.a eq Module.theA)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include commonSimpleClasses
2+
3+
@Module
4+
class AModule {
5+
lazy val a = wire[A]
6+
}
7+
case class X(aModule: AModule) {
8+
lazy val b = wire[B]
9+
lazy val c = wire[C]
10+
}
11+
object CaseTest {
12+
lazy val aModule = wire[AModule]
13+
lazy val X = wire[X]
14+
}
15+
16+
require(CaseTest.X.c.a eq CaseTest.aModule.a)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include commonSimpleClasses
2+
3+
class Parent {
4+
protected def bb = wire[B]
5+
lazy val b = wire[B]
6+
}
7+
@Module
8+
class AModule extends Parent {
9+
protected def x(i:Int) = wire[B]
10+
protected def d: D = ???
11+
protected lazy val dd: D = ???
12+
lazy val a = wire[A]
13+
}
14+
object ParentTest {
15+
lazy val aModule = wire[AModule]
16+
lazy val c = wire[C]
17+
}
18+
19+
require(ParentTest.c.a eq ParentTest.aModule.a)

0 commit comments

Comments
 (0)