-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exclude anon classes from dependency registration #1507
Conversation
These can show in edge cases like: ``` package demo class SRC[_] class Filter class Refined { def select(cols: SRC[_]*) = new { def where(filter: Filter => Filter)(implicit d: DummyImplicit) = { new { def using(opt: Box[SRC[_]] => Some[SRC[_]])(implicit d: DummyImplicit) = opt } } } } ``` ``` package demo class Client { def temp = new Refined().select(null).where(null).using(null) } ``` This can leak an existential type owned by an anon-class into `Client`, at least prior to this compiler fix: scala/scala#10940
* } | ||
* | ||
* Or the status quo might just be perfectly fine -- if compilation doesn't need to force `Foo`, | ||
* then there isn't a real dependency. | ||
*/ | ||
val fqn = fullName(targetSymbol, '.', targetSymbol.moduleSuffix, false) | ||
global.findAssociatedFile(fqn) match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the last line I can leave comment, but a few lines down is
case Some((at, true)) =>
processExternalDependency(fqn, at)
which could explain the additional library (external) dependency. I think normally we check local, subproject, then library in order, but confusing the dependency level could potentially lead to a weird invalidation down the line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
confusing the dependency level could potentially lead to a weird invalidation down the line.
The customer report we're investigating was using direct-to-JAR compilation, which I can imagine cold trigger over-compilation if the spurious external is declared on the entire JAR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, if I:
- set
exportJars := true
- add
p1/src/main/demo/Other.scala
p2/Compile/compile
Then, Zinc overcompiles p2/src/main/demo/Client.scala
:
[debug] IncrementalCompile.incrementalCompile
[debug] previous = Stamps for: 1 products, 1 sources, 2 libraries
[debug] current source = Set(${BASE}/p2/src/main/scala/Client.scala)
[debug] Invalidating '${BASE}/p1/target/scala-2.12/p1_2.12-0.1.0-SNAPSHOT.jar' because could not find class demo.Refined.select$$anon.where$$anon on the classpath.
[debug] > initialChanges = InitialChanges(Changes(added = Set(), removed = Set(), changed = Set(), unmodified = ...),Set(),Set(${BASE}/p1/target/scala-2.12/p1_2.12-0.1.0-SNAPSHOT.jar),API Changes: Set())
[debug]
[debug] Initial source changes:
[debug] removed: Set()
[debug] added: Set()
[debug] modified: Set()
[debug] Invalidated products: Set()
[debug] External API changes: API Changes: Set()
[debug] Modified binary dependencies: Set(${BASE}/p1/target/scala-2.12/p1_2.12-0.1.0-SNAPSHOT.jar)
[debug] Initial directly invalidated classes: Set()
[debug] Sources indirectly invalidated by:
[debug] product: Set()
[debug] binary dep: Set(${BASE}/p2/src/main/scala/Client.scala)
[debug] external source: Set()
[debug] all 1 sources are invalidated
[debug] Created transactional ClassFileManager with tempDir = /Users/jz/code/zinc-refinement/p2/target/scala-2.12/classes.bak
[debug] Initial set of included nodes:
[debug] Recompiling all sources: number of invalidated sources > 50.0 percent of all sources
[debug] About to delete class files:
[debug] Client.class
[debug] We backup class files:
[debug] Client.class
[debug] compilation cycle 1
[info] compiling 1 Scala source to /Users/jz/code/zinc-refinement/p2/target/scala-2.12/classes ...
Co-authored-by: Jiahui (Jerry) Tan <66892505+Friendseeker@users.noreply.github.com>
I made a GitHub repo containing retronym's reproduction for people to play around with: https://github.com/Friendseeker/zinc-1507 Also made some minor simplification to the reproduction. |
Just want to double check if my understanding of the issue is accurate here, as I am not familiar with compiler internal. So an The 3 compiler gurus (Eugene, Dale, Lukas) likely grasp the issue much better than me, but I guess there's probably no edge case that can emerge from not tracking class level dependency with anon class? Say an anon class is changed, then some name hash of the enclosing top-level named class will change, so tracking class level dep with anon class is completely unnecessary. |
This is basically correct. The compiler really shouldn't let the symbol for this anonymous class within the method "leak" out into method return type, even in a seemingly innocuous way as above where it just the owner of a symbol. We're fixing that in the compiler. But Zinc also can stand to be more discriminating in its interpretation of the Run this to see what I mean
Yep, any changes to the anonymous class that are relevant to incremental compilation are reflected in the structural type of the method, which is determines its API hash. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
These can show in edge cases like:
This can leak an existential type owned by an anon-class into
Client
, at least prior to this compiler fix:scala/scala#10940