-
Notifications
You must be signed in to change notification settings - Fork 1
WIP SIP-23 #12
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
WIP SIP-23 #12
Conversation
|
|
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 could serve as the vehicle for summoning the type's inhabitant.
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.
Why not have a macro for this?
|
This looks amazing, @adriaanm! I'm playing with it at the moment. Got a few questions:
Hope this is helpful. I'm super excited to see this moving forward! 💯 👍 |
|
Thanks for the feedback -- very helpful! I have to work on my devoxx talk for the next couple of days, but will get back to coding on this soon. In the mean time, PRs accepted on my sip23 branch ;-) |
|
I'll definitely spend quite some time with it! |
|
Regarding the last rough edge, it's probably related to lack of type equality between ConstantType and LiteralType. Still mulling that one over. |
|
Type inference for vals becomes more specific, is this intended? |
|
It is, though it's up for debate. Under the hood, a |
|
What's the plan for class literals? The corresponding Again, I think tend to think we should not infer these types unless guided by a bound of |
|
I was planning to only support syntactic literals. Right now, Symbols are not supported. |
|
|
The problem with Symbols is similar to classOf, they are method calls underneath: |
Do you have any snags in mind if we do? |
|
Just the general problem of accidentally inferring too specific a type for some public API that ties your hands down the track. IIUC, that's why we deconst in type inference today, rather than just to ensure side effects aren't folded away. I'd expect var and val infer the same type. |
|
As a compromise, we could widen for non-local vals. I think it's useful to preserve precise types locally. |
|
Different rules for different contexts doesn't sound ideal to me. But we can give it some thought. |
|
It feels desirable to me. |
|
@odersky, what do you think? Is inferring |
|
While we ponder that one, here's a curious wrinkle: |
|
Nice... covariant array ftw |
|
Probably a widen in varargs code? |
|
Thanks! |
|
Here's a failing test case with Java varargs. https://github.com/retronym/scala/compare/topic/sip23-java-varargs?expand=1 In The latter type is incorrectly printed as |
|
This works as expected, but is the sort of thing we'll have to add test cases for: |
|
This seems inconsistent: vs |
|
The spec for erasure (in "03. Types") currently says:
We should review that area of the spec and implementation holistically for that change and SIP-23. |
e.g., val x : -1 => -1 = ???
TODO: deriving this from type param bounds is not the best way,
should consider all constraints that we encounter
scala> def stable[T <: 1](x: T): T = x
stable: [T <: 1](x: T)T
scala> stable(1)
res0: 1 = 1
scala> stable(2)
<console>:9: error: inferred type arguments [2] do not conform to method stable's type parameter bounds [T <: 1]
stable(2)
^
<console>:9: error: type mismatch;
found : Int(2)
required: T
stable(2)
^
scala> def stable[T <: Singleton](x: T): T = x
stable: [T <: Singleton](x: T)T
scala> val x: Int = 2
x: Int = 2
scala> val y: x.type = x
y: x.type = 2
scala> stable(y)
res3: x.type = 2
this bootstraps
``` scala> val x = "a" x: "a" = a scala> x.asInstanceOf["a"] res1: "a" = a scala> "1".asInstanceOf["2"] x asInstanceOf Throwable java.lang.ClassCastException ... 33 elided scala> "1".asInstanceOf["1"] res3: "1" = 1 scala> 1.asInstanceOf[1] res4: 1 = 1 scala> 1.asInstanceOf[2] x asInstanceOf Throwable java.lang.ClassCastException ... 33 elided ```
f67a7bc to
61a16f5
Compare
|
What's the perspective here regarding union types? If we have something like |
|
Likely, but union types are still too far in the future to know for sure how they'll interact with this feature. They are primarily about class types -- you could already experiment in dotty and see what p.type | q.type means, where p and q refer to strings... |
93cb02e to
3410d25
Compare
7d6747a to
43289bc
Compare
|
(Will reopen when I get a chance to work on this again!) |
- Avoid boxing the {Object, Int, ...}Ref itself by storing it in
a val, not a var
- Avoid box/unbox of primitive lazy expressions due which are added
to "adapt" it to the erased type of the fictional syncronized
method, by using a `return`. We have to add a dummy throw after
the synchronized block, but this is elimnated by the always-on
DCE in the code generator.
```
⚡ qscalac -Xprint:mixin $(f "class C { def foo = { lazy val x = 42; x }}"); javap -private -c -cp . C
[[syntax trees at end of mixin]] // a.scala
package <empty> {
class C extends Object {
def foo(): Int = {
lazy <artifact> val x$lzy: scala.runtime.LazyInt = new scala.runtime.LazyInt();
C.this.x$1(x$lzy)
};
final private[this] def x$1(x$lzy$1: scala.runtime.LazyInt): Int = {
x$lzy$1.synchronized({
if (x$lzy$1.initialized().unary_!())
{
x$lzy$1.initialized_=(true);
x$lzy$1.value_=(42)
};
return x$lzy$1.value()
});
throw null
};
def <init>(): C = {
C.super.<init>();
()
}
}
}
Compiled from "a.scala"
public class C {
public int foo();
Code:
0: new #12 // class scala/runtime/LazyInt
3: dup
4: invokespecial #16 // Method scala/runtime/LazyInt."<init>":()V
7: astore_1
8: aload_1
9: invokestatic #20 // Method x$1:(Lscala/runtime/LazyInt;)I
12: ireturn
private static final int x$1(scala.runtime.LazyInt);
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: invokevirtual scala#31 // Method scala/runtime/LazyInt.initialized:()Z
8: ifne 22
11: aload_0
12: iconst_1
13: invokevirtual scala#35 // Method scala/runtime/LazyInt.initialized_$eq:(Z)V
16: aload_0
17: bipush 42
19: invokevirtual scala#39 // Method scala/runtime/LazyInt.value_$eq:(I)V
22: aload_0
23: invokevirtual scala#42 // Method scala/runtime/LazyInt.value:()I
26: istore_2
27: goto 33
30: aload_1
31: monitorexit
32: athrow
33: aload_1
34: monitorexit
35: iload_2
36: ireturn
Exception table:
from to target type
4 30 30 Class java/lang/Throwable
public C();
Code:
0: aload_0
1: invokespecial scala#43 // Method java/lang/Object."<init>":()V
4: return
}
```
Top level modules in Scala currently desugar as:
```
class C; object O extends C { toString }
```
```
public final class O$ extends C {
public static final O$ MODULE$;
public static {};
Code:
0: new #2 // class O$
3: invokespecial #12 // Method "<init>":()V
6: return
private O$();
Code:
0: aload_0
1: invokespecial #13 // Method C."<init>":()V
4: aload_0
5: putstatic #15 // Field MODULE$:LO$;
8: aload_0
9: invokevirtual #21 // Method java/lang/Object.toString:()Ljava/lang/String;
12: pop
13: return
}
```
The static initalizer `<clinit>` calls the constructor `<init>`, which
invokes superclass constructor, assigns `MODULE$= this`, and then runs
the remainder of the object's constructor (`toString` in the example
above.)
It turns out that this relies on a bug in the JVM's verifier: assignment to a
static final must occur lexically within the <clinit>, not from within `<init>`
(even if the latter is happens to be called by the former).
I'd like to move the assignment to <clinit> but that would
change behaviour of "benign" cyclic references between modules.
Example:
```
package p1; class CC { def foo = O.bar}; object O {new CC().foo; def bar = println(1)};
// Exiting paste mode, now interpreting.
scala> p1.O
1
```
This relies on the way that we assign MODULE$ field after the super class constructors
are finished, but before the rest of the module constructor is called.
Instead, this commit removes the ACC_FINAL bit from the field. It actually wasn't
behaving as final at all, precisely the issue that the stricter verifier
now alerts us to.
```
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package p1; object O
// Exiting paste mode, now interpreting.
scala> val O1 = p1.O
O1: p1.O.type = p1.O$@ee7d9f1
scala> scala.reflect.ensureAccessible(p1.O.getClass.getDeclaredConstructor()).newInstance()
res0: p1.O.type = p1.O$@64cee07
scala> O1 eq p1.O
res1: Boolean = false
```
We will still achieve safe publication of the assignment to other threads
by virtue of the fact that `<clinit>` is executed within the scope of
an initlization lock, as specified by:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5
Fixes scala/scala-dev#SD-194
WIP PR to centralize discussion, accept improvements/tests/polish/docs/...
Everyone is welcome to look for the checkboxes/TODO items here and in the discussion below and take them on! If you're not sure where/how to start, just ask!
Testing
Spec/docs
ConstantType/LiteralTypeandscala.Singleton.Type inference / type checking
x.isInstanceOf[3]should expand tox == 3, andx.asInstanceOf[3]should beif (x != 3) throw new ClassCastException(..)Corner cases
val t: 1 = t(NOTE: t has value 0...)Design space
()asLiteralType? (I say 'no': () is not a syntactic literal, it's also too deeply ingrained to incorporate here)Feature interaction