Skip to content

Fix #5948: Be more careful with capturing #5957

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

Merged
merged 24 commits into from
Feb 23, 2019
Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Feb 20, 2019

No description provided.

@odersky
Copy link
Contributor Author

odersky commented Feb 21, 2019

Comment on last commit:

Delete DottyByteCodeTest

I am giving up. This is the worst sort of blindly testing the status quo! It tests some random
code and if that code has changed (because there is some optimization applied now) it fails.
Great! Problem is there's no way to automatically update the code. One has to go in manually and
fix the expected program including all labels! Copy pasting from the diff does not work and there's no
--update-check.

As long as things are like this I am afraid that bytecode tests are throwaway tests. They
blindly test the status quo, and if that changes our best action is to throw away the test.

The problem is that while the old test indeed tested nothing of value, a new version of it would
test something interesting: That an unused cast is eliminated. But it's just too hard to update the test, so as long as the testing framework is not upgraded to be more user friendly, these tests are
not as useful as they could be.

 - it should not be Erased since that will remove the complete call, including the expression that
   is casted. It should be StableRealizable, so that a pure expression followed by a synthesized cast
   is still recognized as pure.

 - to make up for the loss of Erased, we take the method into account specifically when deciding
   whether a tree is erased.
Replace all instances of compiler-generated type casts by calls
to `Any_typeCast` via `tpd.cast`. This will assume that all these
calls are pure.
If tree is not a subtype of expected type, try to capture wildcard types
in its type with fresh skolems.
Capture conversion is generally sound only if the LHS of a comparison
is a path.
The previous code for `ModuleSerializationProxyConstructor`
required capture conversion to work.
I am giving up. This is the worst sort of blindly testing the status quo! It tests some random
code and if that code has changed (because there is some optimization applied now) it fails.
Great! Problem is there's no way to automatically update the code. One has to go in manually and
fix the expected program including all labels! Copy pasting from the diff does not work and there's no
--update-check.

As long as things are like this I am afraid that bytecode tests are throwaway tests. They
blindly test the status quo, and if that changes our best action is to throw away the test.
It seems this is necessary for code generation. We got abstract method errors in
typer/TermRefSet otherwise:

```
 java.lang.AbstractMethodError: Method dotty/tools/dotc/typer/TermRefSet$$anon$1.accept(Ljava/lang/Object;Ljava/lang/Object;)V is abstract, took 2.601 sec
[error]     at dotty.tools.dotc.typer.TermRefSet$$anon$1.accept(Implicits.scala)
[error]     at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
[error]     at dotty.tools.dotc.typer.TermRefSet.foreach(Implicits.scala:1638)
[error]     at dotty.tools.dotc.typer.TermRefSet.$plus$plus$eq(Implicits.scala:1634)
```
@odersky
Copy link
Contributor Author

odersky commented Feb 21, 2019

We now have a much simpler conversion scheme e05c1a7, where we just need to skolemize the type of a tree as a whole, no need to go replace parameters by TypeBoxes.

@odersky
Copy link
Contributor Author

odersky commented Feb 21, 2019

test performance please

@dottybot
Copy link
Member

performance test scheduled: 2 job(s) in queue, 1 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/5957/ to see the changes.

Benchmarks is based on merging with master (daf4ce9)

Test case pos/capture.scala contains an explanation why a different scheme
we tried does not work.
@odersky
Copy link
Contributor Author

odersky commented Feb 22, 2019

Unfortunately the simplified scheme failed a test: hmm-shonan/Lifters.scala. I have therefore reverted to the old scheme with TypeBoxes.

The essence of the failure is condensed in test pos/capture.scala. That test also explains the difference between the two schemes I have tried.

@odersky
Copy link
Contributor Author

odersky commented Feb 22, 2019

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 0 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/5957/ to see the changes.

Benchmarks is based on merging with master (daf4ce9)

A SkolemType is not a symbol whose info is transformed separately. That's why mapping
a SkolemType must involve mapping its info.

A test case is the new version of pos_valueclasses/t7818.scala.
Since we now map the info of a SkolemType as part of mapping the SkolemType
itself, it follows that SkolemTypes can't recursively refer to themselves.
So `captureWildcards` in `Typer` avoids recursion now by using an approximating
substitution instead (I wonder now how we ever could live without approximating
substitutions!). This means that SkolemTypes don't need to be mutable anymore
since there is no fixed point to construct.
@odersky
Copy link
Contributor Author

odersky commented Feb 22, 2019

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 1 running.

@dottybot
Copy link
Member

Performance test finished successfully:

Visit http://dotty-bench.epfl.ch/5957/ to see the changes.

Benchmarks is based on merging with master (fda1250)

@odersky
Copy link
Contributor Author

odersky commented Feb 22, 2019

Here's the status:

I replaced the previous unsound capture conversion by two measures:

  1. Apply capture conversion in isSubArgs only if the LHS is a path (then it is sound)
  2. Skolemize wildcards in adapt if a tree does not conform to its expected type.

(2) alone would be sufficient, i.e. all tests pass with it. But performance tests indicate that it's better to also do (1). And, besides, (1) is in a sense less intrusive since we do not need to skolemize. So we should do it when we can.

@odersky
Copy link
Contributor Author

odersky commented Feb 23, 2019

@smarter I am done now. Can you give it another look?

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

  • Your comment in Fix #5948: Be more careful with capturing #5957 (comment) explaining how capture conversion is actually not required but it makes sense to have it here anyway is informative, I think this information should be preserved in a comment in the code too.

  • I still think we could avoid having leftRoot be a mutable variable by nesting isSubArgs in isSubType, but maybe there's a good reason not to do that?

@@ -2697,10 +2698,47 @@ class Typer extends Namer
case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(pt)).withType(pt)
}

/** Replace every top-level occurrence of a wildcard type argument by
Copy link
Member

Choose a reason for hiding this comment

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

I'm not 100% clear on the notion of "top-level occurence" here, why is in an occurence in a branch of an AndOrType top-level, but not in the widened type of an ExprType for example ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's because we have already widened the type before we pass it to captureWildcards.

@odersky
Copy link
Contributor Author

odersky commented Feb 23, 2019

  • I still think we could avoid having leftRoot be a mutable variable by nesting isSubArgs in isSubType, but maybe there's a good reason not to do that?

Yes, it's because that way we don't thread a lot of parameters through all recursive calls (in the implementation, not the source). That's why recur is outside isSubArgs.

I'll make a note explaining that.

@odersky odersky merged commit e777f34 into scala:master Feb 23, 2019
@Blaisorblade Blaisorblade deleted the fix-#5948 branch February 24, 2019 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants