-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Pattern matcher needs to check for outer #2156
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
Comments
I'm having a look at this |
Hah, that must have been pretty hard to find. How is this feature used for semantic names exactly? |
@smarter, Martin's design uses inner classes and pattern-matches over them. |
Have a fix, running full test suite to verify it. |
The bug has specifically to do with the |
I've made a proper fix for this, unfortunatelly: it broke bootstrap in https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/io/ClassPath.scala#L245
For some reason, Scalac doesn't emmit an outer-class check here. @odersky do you know what's the difference between those two examples? |
I think it's a bug in scalac. I played with it a little. First, with the code the map expands to:
Here the outer check is omitted. But now change the parameter's type to
The outer check reappears! This makes no sense. A selector of type My advice would be to delete the whole |
Note that scalac omits the outer check for final nested classes
|
@odersky did you try Scala 2.11? It seems to be fixed in 2.12. Here's a reduced example:
With 2.11.8
With 2.12.1:
|
@lrytz I tried an earlier 2.12. Good that it's fixed in the meantime. I also see that ClassPath has changed. It cannot find anymore the problematic test. We should adopt that also for dotty. But another question: What's the reason for omitting the test for final classes? |
I don't know the full history, but a final nested class doesn't have an outer field. I assume the goal was to avoid capturing the outer instance when serializing a lambda. |
We rely on this trick in 2.12 for local classes defined within closures. If you have class A {
def f = () => { class C; serialize(new C) }
} In 2.11.x, the class class A {
def $anonfun { class C; serialize(new C) }
} so |
@lrytz: What I don't understand: Why would it be safe to special case for final classes? What's different for them? |
It's not safe, as shown by my example above. I guess it's another example of re-using the For final classes the outer field can be elided if it's not otherwise used within the class. In SI-1419, @adriaanm says
I'm not actually sure when this is the case, example welcome |
this makes it sound like there may not need more then one outer-accessor. Here's a counter-example class A{ class B }
class C extends A {self =>
class D {
final class E extends self.B // needs two outer-accessors to support all patter-matchings
}
} |
To quote paulp, "These are semantics out of a nightmare" (https://groups.google.com/forum/#!msg/scala-internals/vw8Kek4zlZ8/LAeakfeR3RoJ). There's a bug report with critical priority open: https://issues.scala-lang.org/browse/SI-4440 |
SI-4400 doesn't apply to dotty. I've created a PR with the same test to make sure it never applies. |
@lrytz In dotty, if a subclass needs an outer accessor, it gets one. I still don't see why the (non-final) superclass would then need an outer accessor as well. Dotty also has the distinction that some classes always need an outer accessor and others need an outer accessor only if referenced. But finality has nothing to do with it. Instead, classes need an outer accessor only when outer is referenced if the class is local to a term or private, i.e. we can check all possible references that need an outer. Note that this would handle the anonymous functions in the same way as the finality check. |
In separate compilation, how can you decide a class ever doesn't need an outer accessor? A match could be added in a future compilation run that needs the outer check. |
That's why it should be kept :-), eliding it is unsound. |
Agreed. |
@adriaanm Inner classes that are visible in other compilation units always get an outer accessor. |
As they should, but that's not the case in Scala 2 as far as I recall. (And that's a bug, I'd say.) |
@adriaanm Yes, I think so as well. I am not sure anymore what was the motivation to do it the way scalac does it. Performance? Is there a common use case where a publicily visible class should not get an outer accessor? I can't think of one right now. |
I think it was memory footprint.
…On Fri, Mar 31, 2017 at 12:32 odersky ***@***.***> wrote:
@adriaanm <https://github.com/adriaanm> Yes, I think so as well. I am not
sure anymore what was the motivation to do it the way scalac does it.
Performance? Is there a common use case where a publicily visible class
should not get an outer accessor? I can't think of one right now.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2156 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAFjyxSsPiN1oJoBYJEseNOiPzqBp1xTks5rrVTEgaJpZM4MuwyP>
.
|
class C {
val f1 = (x: Int) => x
val f2 = {
final class anonfun extends Function1[Int, Int] with Serializable { def apply(x: Int) = x }
new anonfun
}
val f3 = {
class anonfun extends Function1[Int, Int] with Serializable { def apply(x: Int) = x }
new anonfun
}
}
object Test {
def serializeDeserialize[T <: AnyRef](obj: T): T = {
import java.io._
val buffer = new ByteArrayOutputStream
val out = new ObjectOutputStream(buffer)
out.writeObject(obj)
val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
in.readObject.asInstanceOf[T]
}
def main(args: Array[String]): Unit = {
val c = new C
import c._
println(serializeDeserialize(f1)(1))
println(serializeDeserialize(f2)(2))
println(serializeDeserialize(f3)(3))
}
} With 2.11.8:
|
Those aren't named inner classes. In dotty they won't get outer accessors
if they don't need them.
…On 4 April 2017 21:43:05 Lukas Rytz ***@***.***> wrote:
```scala
class C {
val f1 = (x: Int) => x
val f2 = {
final class anonfun extends Function1[Int, Int] with Serializable { def
apply(x: Int) = x }
new anonfun
}
val f3 = {
class anonfun extends Function1[Int, Int] with Serializable { def apply(x:
Int) = x }
new anonfun
}
}
object Test {
def serializeDeserialize[T <: AnyRef](obj: T): T = {
import java.io._
val buffer = new ByteArrayOutputStream
val out = new ObjectOutputStream(buffer)
out.writeObject(obj)
val in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray))
in.readObject.asInstanceOf[T]
}
def main(args: Array[String]): Unit = {
val c = new C
import c._
println(serializeDeserialize(f1)(1))
println(serializeDeserialize(f2)(2))
println(serializeDeserialize(f3)(3))
}
}
```
With 2.11.8:
```
$ scalac11 Test.scala && scala11 Test
1
2
java.io.NotSerializableException: C
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
```
--
You are receiving this because you were assigned.
Reply to this email directly or view it on GitHub:
#2156 (comment)
|
Uh oh!
There was an error while loading. Please reload this page.
When testing against an inner class, the pattern matcher needs to test outer pointers. Consider the following program:
We expect the
isInstanceOf
test incheckInstance
to returntrue
but the other three tests should returnfalse
. Yes, I know it's contorted, but these Scala rules were a compromise betweennot losing performance and being precise. Essentially we accept that we lose precision in
isInstanceOf
because otherwise there would be no way to avoid the "outer test tax". But we demand that pattern matchers test outer pointers. Indeed you will find specific code doing that after patmat when you compile the code withscalac
:But such code is currently not emitted by
dotc
which is why the last three asserts fail.The discrepancy is the reason why the semantic names PR fails the bootstrap.
The text was updated successfully, but these errors were encountered: