Skip to content

Commit ecb6905

Browse files
authored
Merge pull request #221 from jvican/fix-asf
Fix typesafehub#113 and apply several other fixes
2 parents 511ad6c + c3714f4 commit ecb6905

File tree

17 files changed

+183
-141
lines changed

17 files changed

+183
-141
lines changed

Diff for: internal/compiler-bridge/src-2.10/main/scala/xsbt/ExtractAPI.scala

+9-12
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,15 @@ class ExtractAPI[GlobalType <: Global](
136136
def renaming(symbol: Symbol): Option[String] = renameTo.get(symbol)
137137
}
138138

139-
// call back to the xsbti.SafeLazy class in main sbt code to construct a SafeLazy instance
140-
// we pass a thunk, whose class is loaded by the interface class loader (this class's loader)
141-
// SafeLazy ensures that once the value is forced, the thunk is nulled out and so
142-
// references to the thunk's classes are not retained. Specifically, it allows the interface classes
143-
// (those in this subproject) to be garbage collected after compilation.
144-
private[this] val safeLazy = Class.forName("xsbti.SafeLazy").getMethod("apply", classOf[xsbti.F0[_]])
145-
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] =
146-
{
147-
val z = safeLazy.invoke(null, Message(s)).asInstanceOf[xsbti.api.Lazy[S]]
148-
pending += z
149-
z
150-
}
139+
/**
140+
* Construct a lazy instance from a by-name parameter that will null out references to once
141+
* the value is forced and therefore references to thunk's classes will be garbage collected.
142+
*/
143+
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] = {
144+
val lazyImpl = xsbti.api.SafeLazy.apply(Message(s))
145+
pending += lazyImpl
146+
lazyImpl
147+
}
151148

152149
/**
153150
* Force all lazy structures. This is necessary so that we see the symbols/types at this phase and

Diff for: internal/compiler-bridge/src/main/scala/xsbt/Dependency.scala

+11-27
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
5858
dependencyExtractor.localInheritanceDependencies foreach processDependency(context = LocalDependencyByInheritance)
5959
processTopLevelImportDependencies(dependencyExtractor.topLevelImportDependencies)
6060
} else {
61-
throw new UnsupportedOperationException("Turning off name hashing is not supported in class-based dependency trackging.")
61+
throw new UnsupportedOperationException(Feedback.NameHashingDisabled)
6262
}
6363
/*
6464
* Registers top level import dependencies as coming from a first top level class/trait/object declared
@@ -75,13 +75,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
7575
deps foreach { dep =>
7676
processDependency(context = DependencyByMemberRef)(ClassDependency(firstClassSymbol, dep))
7777
}
78-
case None =>
79-
reporter.warning(
80-
unit.position(0),
81-
"""|Found top level imports but no class, trait or object is defined in the compilation unit.
82-
|The incremental compiler cannot record the dependency information in such case.
83-
|Some errors like unused import referring to a non-existent class might not be reported.""".stripMargin
84-
)
78+
case None => reporter.warning(unit.position(0), Feedback.OrphanTopLevelImports)
8579
}
8680
}
8781
/*
@@ -163,10 +157,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
163157
}
164158
}
165159
private def addClassDependency(deps: HashSet[ClassDependency], fromClass: Symbol, dep: Symbol): Unit = {
166-
assert(
167-
fromClass.isClass,
168-
s"The ${fromClass.fullName} defined at ${fromClass.fullLocationString} is not a class symbol."
169-
)
160+
assert(fromClass.isClass, Feedback.expectedClassSymbol(fromClass))
170161
val depClass = enclOrModuleClass(dep)
171162
if (fromClass.associatedFile != depClass.associatedFile) {
172163
deps += ClassDependency(fromClass, depClass)
@@ -182,25 +173,18 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
182173
}
183174
}
184175

185-
@inline
186-
def ignoreType(tpe: Type) =
187-
tpe == null ||
188-
tpe == NoType ||
189-
tpe.typeSymbol == EmptyPackageClass
190-
191176
private def addTreeDependency(tree: Tree): Unit = {
192177
addDependency(tree.symbol)
193178
val tpe = tree.tpe
194-
if (!ignoreType(tpe))
195-
foreachSymbolInType(tpe)(addDependency)
179+
if (!ignoredType(tpe))
180+
foreachNotPackageSymbolInType(tpe)(addDependency)
196181
()
197182
}
198183
private def addDependency(dep: Symbol): Unit = {
199184
val fromClass = resolveDependencySource().fromClass
200-
if (fromClass == NoSymbol || fromClass.hasPackageFlag) {
185+
if (ignoredSymbol(fromClass) || fromClass.hasPackageFlag) {
201186
if (inImportNode) addTopLevelImportDependency(dep)
202-
else
203-
devWarning(s"No enclosing class. Discarding dependency on $dep (currentOwner = $currentOwner).")
187+
else devWarning(Feedback.missingEnclosingClass(dep, currentOwner))
204188
} else {
205189
addClassDependency(_memberRefDependencies, fromClass, dep)
206190
}
@@ -276,12 +260,12 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
276260
traverseTrees(body)
277261

278262
// In some cases (eg. macro annotations), `typeTree.tpe` may be null. See sbt/sbt#1593 and sbt/sbt#1655.
279-
case typeTree: TypeTree if !ignoreType(typeTree.tpe) =>
280-
symbolsInType(typeTree.tpe) foreach addDependency
263+
case typeTree: TypeTree if !ignoredType(typeTree.tpe) =>
264+
foreachNotPackageSymbolInType(typeTree.tpe)(addDependency)
281265
case m @ MacroExpansionOf(original) if inspectedOriginalTrees.add(original) =>
282266
traverse(original)
283267
super.traverse(m)
284-
case _: ClassDef | _: ModuleDef if tree.symbol != null && tree.symbol != NoSymbol =>
268+
case _: ClassDef | _: ModuleDef if !ignoredSymbol(tree.symbol) =>
285269
// make sure we cache lookups for all classes declared in the compilation unit; the recorded information
286270
// will be used in Analyzer phase
287271
val sym = if (tree.symbol.isModule) tree.symbol.moduleClass else tree.symbol
@@ -295,7 +279,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
295279
addDependency(symbol)
296280
}
297281
val addSymbolsFromType: Type => Unit = { tpe =>
298-
foreachSymbolInType(tpe)(addDependency)
282+
foreachNotPackageSymbolInType(tpe)(addDependency)
299283
}
300284
}
301285

Diff for: internal/compiler-bridge/src/main/scala/xsbt/ExtractAPI.scala

+10-13
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,15 @@ class ExtractAPI[GlobalType <: Global](
136136
def renaming(symbol: Symbol): Option[String] = renameTo.get(symbol)
137137
}
138138

139-
// call back to the xsbti.SafeLazy class in main sbt code to construct a SafeLazy instance
140-
// we pass a thunk, whose class is loaded by the interface class loader (this class's loader)
141-
// SafeLazy ensures that once the value is forced, the thunk is nulled out and so
142-
// references to the thunk's classes are not retained. Specifically, it allows the interface classes
143-
// (those in this subproject) to be garbage collected after compilation.
144-
private[this] val safeLazy = Class.forName("xsbti.SafeLazy").getMethod("apply", classOf[xsbti.F0[_]])
145-
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] =
146-
{
147-
val z = safeLazy.invoke(null, Message(s)).asInstanceOf[xsbti.api.Lazy[S]]
148-
pending += z
149-
z
150-
}
139+
/**
140+
* Construct a lazy instance from a by-name parameter that will null out references to once
141+
* the value is forced and therefore references to thunk's classes will be garbage collected.
142+
*/
143+
private def lzy[S <: AnyRef](s: => S): xsbti.api.Lazy[S] = {
144+
val lazyImpl = xsbti.api.SafeLazy.apply(Message(s))
145+
pending += lazyImpl
146+
lazyImpl
147+
}
151148

152149
/**
153150
* Force all lazy structures. This is necessary so that we see the symbols/types at this phase and
@@ -624,4 +621,4 @@ class ExtractAPI[GlobalType <: Global](
624621
implicit def compat(ann: AnnotationInfo): IsStatic = new IsStatic(ann)
625622
annotations.filter(_.isStatic)
626623
}
627-
}
624+
}

Diff for: internal/compiler-bridge/src/main/scala/xsbt/ExtractUsedNames.scala

+8-32
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ import scala.collection.mutable
2222
* Names mentioned in Import nodes are handled properly but require some special logic for two
2323
* reasons:
2424
*
25-
* 1. import node itself has a term symbol associated with it with a name `<import`>.
26-
* I (gkossakowski) tried to track down what role this symbol serves but I couldn't.
27-
* It doesn't look like there are many places in Scala compiler that refer to
28-
* that kind of symbols explicitly.
29-
* 2. ImportSelector is not subtype of Tree therefore is not processed by `Tree.foreach`
25+
* 1. The `termSymbol` of Import nodes point to the symbol of the prefix it imports from
26+
* (not the actual members that we import, that are represented as names).
27+
* 2. ImportSelector is not subtype of Tree therefore is not processed by `Tree.foreach`.
3028
*
3129
* Another type of tree nodes that requires special handling is TypeTree. TypeTree nodes
3230
* has a little bit odd representation:
@@ -65,12 +63,7 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
6563
val firstClassName = className(firstClassSymbol)
6664
traverser.usedNamesFromClass(firstClassName) ++= namesUsedAtTopLevel.map(decodeName)
6765
case None =>
68-
reporter.warning(
69-
unit.position(0),
70-
"""|Found names used at the top level but no class, trait or object is defined in the compilation unit.
71-
|The incremental compiler cannot record used names in such case.
72-
|Some errors like unused import referring to a non-existent class might not be reported.""".stripMargin
73-
)
66+
reporter.warning(unit.position(0), Feedback.OrphanNames)
7467
}
7568
}
7669

@@ -106,14 +99,15 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
10699
}
107100

108101
/** Returns mutable set with all names from given class used in current context */
109-
def usedNamesFromClass(className: String): collection.mutable.Set[String] =
102+
def usedNamesFromClass(className: String): collection.mutable.Set[String] = {
110103
usedNamesFromClasses.get(className) match {
111104
case None =>
112105
val emptySet = scala.collection.mutable.Set.empty[String]
113106
usedNamesFromClasses.put(className, emptySet)
114107
emptySet
115108
case Some(setForClass) => setForClass
116109
}
110+
}
117111

118112
/*
119113
* Some macros appear to contain themselves as original tree.
@@ -130,10 +124,6 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
130124

131125
private def handleClassicTreeNode(tree: Tree): Unit = tree match {
132126
case _: DefTree | _: Template => ()
133-
// turns out that Import node has a TermSymbol associated with it
134-
// I (Grzegorz) tried to understand why it's there and what does it represent but
135-
// that logic was introduced in 2005 without any justification I'll just ignore the
136-
// import node altogether and just process the selectors in the import node
137127
case Import(_, selectors: List[ImportSelector]) =>
138128
val enclosingNonLocalClass = resolveEnclosingNonLocalClass()
139129
def usedNameInImportSelector(name: Name): Unit =
@@ -154,7 +144,7 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
154144
case t if t.hasSymbolField =>
155145
addSymbol(t.symbol)
156146
if (t.tpe != null)
157-
foreachSymbolInType(t.tpe)(addSymbol)
147+
foreachNotPackageSymbolInType(t.tpe)(addSymbol)
158148
case _ =>
159149
}
160150

@@ -205,22 +195,8 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
205195
if (s.isModule) s.moduleClass else s.enclClass
206196
}
207197

208-
/**
209-
* Needed for compatibility with Scala 2.8 which doesn't define `tpnme`
210-
*/
211-
private object tpnme {
212-
val EMPTY = nme.EMPTY.toTypeName
213-
val EMPTY_PACKAGE_NAME = nme.EMPTY_PACKAGE_NAME.toTypeName
214-
}
215-
216198
private def eligibleAsUsedName(symbol: Symbol): Boolean = {
217-
def emptyName(name: Name): Boolean = name match {
218-
case nme.EMPTY | nme.EMPTY_PACKAGE_NAME | tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => true
219-
case _ => false
220-
}
221-
222199
// Synthetic names are no longer included. See https://github.com/sbt/sbt/issues/2537
223-
(symbol != NoSymbol) &&
224-
!emptyName(symbol.name)
200+
!ignoredSymbol(symbol) && !isEmptyName(symbol.name)
225201
}
226202
}

Diff for: internal/compiler-bridge/src/main/scala/xsbt/GlobalHelpers.scala

+39-7
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,31 @@ trait GlobalHelpers {
66
val global: Global
77
import global._
88

9-
def symbolsInType(tp: Type): Set[Symbol] = {
10-
val typeSymbolCollector =
11-
new CollectTypeCollector({
12-
case tpe if (tpe != null) && !tpe.typeSymbolDirect.hasPackageFlag => tpe.typeSymbolDirect
13-
})
9+
/** Return true if type shall be ignored, false otherwise. */
10+
@inline def ignoredType(tpe: Type) = {
11+
tpe == null ||
12+
tpe == NoType ||
13+
tpe.typeSymbol == EmptyPackageClass
14+
}
15+
16+
/** Return true if symbol shall be ignored, false otherwise. */
17+
@inline def ignoredSymbol(symbol: Symbol) = {
18+
symbol == null ||
19+
symbol == NoSymbol ||
20+
symbol == EmptyPackageClass
21+
}
1422

15-
typeSymbolCollector.collect(tp).toSet
23+
/** Return true if name is empty, false otherwise. */
24+
def isEmptyName(name: Name): Boolean = {
25+
name match {
26+
case nme.EMPTY | nme.EMPTY_PACKAGE_NAME |
27+
tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => true
28+
case _ => false
29+
}
1630
}
1731

18-
def foreachSymbolInType(tpe: Type)(op: Symbol => Unit): Unit = {
32+
/** Apply `op` on every type symbol which doesn't represent a package. */
33+
def foreachNotPackageSymbolInType(tpe: Type)(op: Symbol => Unit): Unit = {
1934
new ForEachTypeTraverser(_ match {
2035
case null =>
2136
case tpe =>
@@ -45,4 +60,21 @@ trait GlobalHelpers {
4560
}.headOption
4661
}
4762
}
63+
64+
/** Define common error messages for error reporting and assertions. */
65+
object Feedback {
66+
val NameHashingDisabled = "Turning off name hashing is not supported in class-based dependency trackging."
67+
val OrphanTopLevelImports = noTopLevelMember("top level imports")
68+
val OrphanNames = noTopLevelMember("names")
69+
70+
def expectedClassSymbol(culprit: Symbol): String =
71+
s"The ${culprit.fullName} defined at ${culprit.fullLocationString} is not a class symbol."
72+
def missingEnclosingClass(culprit: Symbol, owner: Symbol): String =
73+
s"No enclosing class. Discarding dependency on $culprit (currentOwner = $owner)."
74+
def noTopLevelMember(found: String) = s"""
75+
|Found $found but no class, trait or object is defined in the compilation unit.
76+
|The incremental compiler cannot record the dependency information in such case.
77+
|Some errors like unused import referring to a non-existent class might not be reported.
78+
""".stripMargin
79+
}
4880
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package xsbti.api;
2+
3+
/**
4+
* Implement a Scala `lazy val` in Java for the facing sbt interface.
5+
*
6+
* It holds a reference to a thunk that is lazily evaluated and then
7+
* its reference is clear to avoid memory leaks in memory-intensive code.
8+
* It needs to be defined in [[xsbti]] or a subpackage, see
9+
* [[xsbti.api.Lazy]] or [[xsbti.F0]] for similar definitions.
10+
*/
11+
public final class SafeLazy {
12+
13+
/* We do not use conversions from and to Scala functions because [[xsbti]]
14+
* cannot hold any reference to Scala code nor the Scala library. */
15+
16+
/** Return a sbt [[xsbti.api.Lazy]] from a given Scala parameterless function. */
17+
public static <T> xsbti.api.Lazy<T> apply(xsbti.F0<T> sbtThunk) {
18+
return new Impl<T>(sbtThunk);
19+
}
20+
21+
/** Return a sbt [[xsbti.api.Lazy]] from a strict value. */
22+
public static <T> xsbti.api.Lazy<T> strict(T value) {
23+
// Convert strict parameter to sbt function returning it
24+
return apply(new xsbti.F0<T>() {
25+
public T apply() {
26+
return value;
27+
}
28+
});
29+
}
30+
31+
private static final class Impl<T> extends xsbti.api.AbstractLazy<T> {
32+
private xsbti.F0<T> thunk;
33+
private T result;
34+
private boolean flag = false;
35+
36+
Impl(xsbti.F0<T> thunk) {
37+
this.thunk = thunk;
38+
}
39+
40+
/**
41+
* Return cached result or force lazy evaluation.
42+
*
43+
* Don't call it in a multi-threaded environment.
44+
*/
45+
public T get() {
46+
if (flag) return result;
47+
else {
48+
result = thunk.apply();
49+
flag = true;
50+
// Clear reference so that thunk is GC'ed
51+
thunk = null;
52+
return result;
53+
}
54+
}
55+
}
56+
}
57+
58+

0 commit comments

Comments
 (0)