Skip to content
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

Backport compiler determinism to 2.12.x [ci: last-only] #7203

Merged
merged 16 commits into from Oct 27, 2018

Conversation

retronym
Copy link
Member

@retronym retronym commented Sep 12, 2018

Backports:

Which together resolve scala/scala-dev#405

A risk of the backport is that we create binary incompatibilities
with code compiled with -opt:inline that refers to fresh names
that are now changed. I had this problem with partest on 2.13.x:

scala/sbt-scala-module@7aee2b5#diff-b296be07ef3dd86a176475283d8583a4

Another risk is that there are serialization incompatibilities
that aren't covered by SerializationStabilityTest.

@scala-jenkins scala-jenkins added this to the 2.12.7 milestone Sep 12, 2018
@adriaanm
Copy link
Contributor

Going through the reviews of the 2.13 versions. Some concerns (will edit as I make my way through):

  • should we avoid breaking macro annotations? 87be453

@adriaanm adriaanm modified the milestones: 2.12.7, 2.12.8 Sep 12, 2018
@adriaanm adriaanm self-assigned this Oct 16, 2018
retronym and others added 15 commits October 16, 2018 13:47
By slightly modifying an existing test to force creation of
default getters for both `bar` methods _before_ typechecking the
application, I was able to show a latent bug in the way the default
getter is looked up in scope.

The bespoke `Context.lookup` method did not respect shadowing,
but rather considered the two, same-named default getters as
overloaded. Because the overloaded symbol had NoSymbol as its
owner, which didn't match the expected owner, neither default
was eligible.

This commit brings the code more into line with `Context.lookupSymbol`
and respects shadowing.

(cherry picked from commit 86f2028)
In scala#5700, I fixed a bug in the companion lookup, which ensured
they were defined in the same scope.

The same approach applies well to the lookup of default getters.

You may ask, we can't just use:

```
context.lookupSymbol(name, _.owner == expectedOwner)
```

That doesn't individually lookup the entry in each enclosing
nested scopes, but rather relies on the outer scope delegation
in `Scope.lookupEntry` itself. This in turn relies on the way that
nested scopes share the `elems` table with the enclosing scope:

```
  final def newNestedScope(outer: Scope): Scope = {
    val nested = newScope
    nested.elems = outer.elems
    nested.nestinglevel = outer.nestinglevel + 1
    ...
  }
```

If the outer scope is later mutated, in our case by lazily adding
the default getter, the inner scope won't see the new elems.
Context.lookupSymbol will jump immediately jump to search of the
enclosing prefix.

Perhaps a better design would be for the inner scope to retain a
reference to the outer one, rather than just to the head of its
elems linked list at the time the nested scope was created.

(cherry picked from commit da14e9c)
The annotation removed in this diff was actually from `R`!

(cherry picked from commit 623589a)
Small refactoring designed to make the subsequent
commit more reviewable.

Tightens up the definition of "companion"-s so that
we no longer add a type alias in a package object into
the pickle of a same-named module.

(cherry picked from commit b41e651)
The pickle format does not explicitly enccode the order of
decls. Instead, symbols are entered into an index in the
order that they are found by the pickler, either as a definition
or as a reference. During unpickling, symbols are read and entered
into the owner's decls in that order.

This is a cause of unstable compiler output: a class that mixes
in members from some trait will have a different order if it is
compiled jointly with / separately from that trait.

This commit modifies the pickler with an initial pass that reserves
index entries for all declarations in the declaration order.

The pickle format and the unpickler are unchanged.

(cherry picked from commit 8cc7e56)
  - Factor out differences between constructors and regular methods
    into an virtual call to a helper class
  - Tease apart symbol creation/entry from synthesis of the default
    getter tree to prepare for a subsequent commit that will perform
    the first part eagerly.
  - Add a test to show the unstable order of the default getter
    symbols in the owner's scope.

(cherry picked from commit f44e1bf)
This stabilizes the order they appear in the owners scope. Previously,
their order was goverened by the order that the methods bearing
default parameters were type completed.

Make macro annotations compatible with these changes

(cherry picked from commit 87be453)
(cherry picked from commit befc337)
Fresh names are created using a FreshNameCreator, which appends
an increasing number to the given prefix.

```
scala> val fresh = new scala.reflect.internal.util.FreshNameCreator()
fresh: scala.reflect.internal.util.FreshNameCreator = scala.reflect.internal.util.FreshNameCreator@42b84286

scala> List("foo$", "bar$", "foo$").map(fresh.newName(_))
res1: List[String] = List(foo$1, bar$1, foo$2)
```

Each compilation unit had its own fresh name creator, which is used
in the regular compiler. Macros and quasiquotes make use of a global
creator (at least, as of scala#3401).

Both of these are too broadly scoped to help achieve deterministic
fresh names: if sources are recompiled in a different order or separately
recompiled, the fresh name counters can be different. Methods in a given
compilation unit are not necessarily typechecked in a linear fashion;
they might be typechecked ahead of time to provide an inferred type
to a caller.

This commit:

  - Changes all known fresh name creations within the typer phase (in which
    out-of-order typechecking is a factor) to use a fineer grained fresh
    name creator. How fine grained? A fresh name generated as some position
    `p` shares the fresh name generator scoped at the closest method or
    class that encloses that the outermost enclosing tree at the same
    position. This definition is designed to give a shared fresh name
    creator for all fresh names generated in `macro1(macro2())`, even if
    the fresh names are requiested from with a Typer in the macro enclosed
    by a synthetic method.
  - Changes macro fresh names to use the same fresh naming scheme as the regular
    typechecker. An opt-out compiler option allows the old behaviour, but I'm
    interested to find real-world cases where the new scheme actually causes
    a problem

In addition, a small change is made to lambda lift to lift local methods in the
order that they are encountered during traversal, rather than sorting them
based on `Symbol.isLess` (which include `Symbol.id`, an order-of-typechecking
dependent value).

(cherry picked from commit 69d60cb)
I've used this to flush out the corner cases fixed in the previous commit.

(cherry picked from commit dfaefa0)
(cherry picked from commit c5cc71f)
Previously, it was done while typechecking super calls, and would fail
to see the fact that a yet-to-be-typechecked super constructor itself
had a parameter aliased by a grand-parent class.

(cherry picked from commit 3ae11c1)
typedRefinement defers the setting of this flag until the end
of the compilation unit, which means that inferred types that are
derived from the written refinement type can be unstable depending
on whether they were computed before or after the flag was set.

An alternative fix might be to just remove the setting of OVERRIDE
in typedRefinement.unitToCheck.

(cherry picked from commit f6ca3dd)
As per the discussion at scala@7184fe0#r29651930,
all we want to avoid is name collision among the holes in a single
pattern. For that, `c.freshName(<a constant>)` itself is sufficient
and the randomness is not needed. This is what scala.meta does,
and works just as well.

This fixes scala/bug#11008

(cherry picked from commit 0336c14)
@adriaanm
Copy link
Contributor

Preparing macro-annot PR at https://github.com/adriaanm/paradise/pull/new/2.12.8

@mkeskells
Copy link
Contributor

Great to see this being backported.
This will open up new possibilities with our CI plant both for reductions in compile and for test

@adriaanm
Copy link
Contributor

adriaanm commented Oct 17, 2018

I added a community build for 2.12.8-bin-e764204-SNAPSHOT to the queue (https://scala-ci.typesafe.com/job/scala-2.12.x-integrate-community-build/3650). It's using a custom config (adriaanm/community-builds@bddc889) that uses macro-paradise at https://github.com/scalamacros/paradise/tree/2.12.8 (I incorporated the community build fixes)

/cc @SethTisue FYI on queuing the build

@adriaanm
Copy link
Contributor

After a green community build, this is ready to merge!

@SethTisue SethTisue self-assigned this Oct 17, 2018
@SethTisue
Copy link
Member

SethTisue commented Oct 18, 2018

there were some failures in run 3650. the enumeratum one, at least, looks spurious, but let's see how a rebuild does:
https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3652/

@adriaanm
Copy link
Contributor

unexpected FAILED: classutil
unexpected FAILED: curryhoward
unexpected FAILED: enumeratum
unexpected FAILED: lift-json
unexpected FAILED: scala-refactoring
unexpected FAILED: scalatex
SUCCESS 179 FAILED 6 DID NOT RUN 4 TOTAL 189
UNEXPECTED 6
BLOCKERS:
2 enumeratum
1 lift-json
1 classutil

@adriaanm
Copy link
Contributor

All test failures -- could they all be spurious?? Hitting rebuild one more time

@SethTisue
Copy link
Member

for comparison, we should see whether any of the same failures crop up in https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3653/ which is a plain run that's running now

@retronym
Copy link
Member Author

retronym commented Oct 19, 2018

The lift-json looks like a change in behaviour, or bug, in scalap. Scalap contains an independant implementation of the unpickler.

I thought the changes to the pickler were backwards compatible, I just changed made the order of entries predictable.

[lift-json] [info] 
[lift-json] [error] ! Either can't be deserialized with type hints
[lift-json] [error]  net.liftweb.json.MappingException: Can't find constructor for class net.liftweb.json.Eith (Meta.scala:227)
[lift-json] [error] net.liftweb.json.Meta$.fail(Meta.scala:227)
[lift-json] [error] net.liftweb.json.ScalaSigReader$.$anonfun$readConstructor$1(ScalaSig.scala:25)
[lift-json] [error] net.liftweb.json.ScalaSigReader$.readConstructor(ScalaSig.scala:25)
[lift-json] [error] net.liftweb.json.Meta$.$anonfun$mappingOf$5(Meta.scala:143)
[lift-json] [error] net.liftweb.json.Meta$.parameterizedTypeOpt$1(Meta.scala:141)
[lift-json] [error] net.liftweb.json.Meta$.mkConstructor$1(Meta.scala:152)

Oh, that's not scalap, it is yet-another-unpickler implementation.

This fixed a regression in lift-json, which programattically uses
scalap, and expects that method parameters are children of the method
symbol.
@retronym
Copy link
Member Author

retronym commented Oct 19, 2018

I've pushed a fix for lift-json.

Scheduled a community build: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3655/

@adriaanm
Copy link
Contributor

adriaanm commented Oct 19, 2018

I merged Seth's changes to the community build config into my branch that points to a different branch of paradise, and bumped the scala version to use 3dcbf47. Let's see how this one fares: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3656

@SethTisue
Copy link
Member

followup run https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/3662/

failed: classutil, curryhoward, scala-refactoring, enumeratum, scalatex

the enumeratum failure is unrelated (and now fixed by scala/community-builds/commit/32151444479ae4e1ecdc8d0202336ff64913f0a6)

the scalatex failure is unrelated

classutil, curryhoward, scala-refactoring look like they might be overly sensitive tests, hard to tell at a glance

only 3 projects didn't run because of upstream failures (pureconfig, refined, scalasti)

@retronym
Copy link
Member Author

retronym commented Oct 24, 2018

classutil is a picky test. Submitted a patch in bmc/classutil#31

@retronym
Copy link
Member Author

The curryhoward also appears to be benign. Making its test more tolerant: Chymyst/curryhoward#32

retronym added a commit to retronym/scala-refactoring that referenced this pull request Oct 24, 2018
@retronym
Copy link
Member Author

Scala refactoring: scala-ide/scala-refactoring#201

@retronym
Copy link
Member Author

I'm happy that the community build hasn't found any bugs in this change. @SethTisue Should we update the community build to use my patches to the three projects above before we merge this?

@SethTisue
Copy link
Member

I think due diligence has now been done, I don't think it's necessary to do another run

@mkeskells
Copy link
Contributor

Is this waiting on any more reviews or can it be merged ?
We are keen to trial this in our CI toolchain

@@ -27,7 +27,7 @@ abstract class ScalaSigSymbol extends Symbol {
def entry: ScalaSig#Entry
def index = entry.index

lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (sym => sym.parent == Some(this) && !sym.isParam)
lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (sym => sym.parent == Some(this) && (this match { case _: MethodSymbol => true case _ => !sym.isParam}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mabye a little ad-hoc, to filter class type params, but not method type params?

@lrytz
Copy link
Member

lrytz commented Oct 26, 2018

It looks ready, but I'll let @adriaanm merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes worth highlighting in next release notes
7 participants