Last active
December 18, 2020 13:57
-
-
Save abeln/9f79774bac111d99b3ae2cb9016a33e6 to your computer and use it in GitHub Desktop.
Revisions
-
abeln revised this gist
Jan 24, 2019 . 1 changed file with 3 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -14,7 +14,9 @@ - [toString](#toString) - [Binary Compatibility](#binary-compatibility) - [Flow-Sensitive Type Inference](#flow-sensitive-type-inference) - [Non-Stable Paths](#non-stable-paths) - [Logical Operators](#logical-operators) - [Inside Conditions](#inside-conditions) - [Algorithm](#algorithm) - [Unsupported Idioms](#unsupported-idioms) - [Local Type Inference](#local-type-inference) -
abeln revised this gist
Jan 24, 2019 . 1 changed file with 20 additions and 28 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -396,39 +396,38 @@ o.foo(null.asInstanceOf[String]) // ok: cast will succeed at runtime ## Flow-Sensitive Type Inference We added a simple form of flow-sensitive type inference. The idea is that if `p` is a stable path, then we can know that `p` is non-null if it's "compared" with the `null` literal. This information can then be propagated to the `then` and `else` branches of an if-statement (among other places). Example: ```scala val s: String|Null = ??? if (s != null) { // s: String } // s: String|Null ``` A similar inference can be made for the `else` case if the test is `p == null` ```scala if (s == null) { // s: String|Null } else { // s: String } ``` What exactly is considered a comparison for the purposes of the flow inference: - `==` and `!=` - `eq` and `ne` - `isInstanceOf[Null]`. Only kicks in if the type test is against `Null`: ```scala val s: String|Null if (!s.isInstanceOf[Null]) { // s: String } ``` If the test had been (`if (s.isInstanceOf[String])`), we currently don't infer non-nullness. ### Non-Stable Paths If `p` isn't stable, then inferring non-nullness is potentially unsound: ```scala var s: String|Null = "hello" @@ -437,6 +436,7 @@ if (s != null && {s = null; true}) { } ``` ### Logical Operators We also support logical operators (`&&`, `||`, and `!`): ```scala val s: String|Null = ??? @@ -455,6 +455,7 @@ if (s == null || s2 == null) { } ``` ### Inside Conditions We also support type specialization _within_ the condition, taking into account that `&&` and `||` are short-circuiting: ```scala val s: String|Null @@ -469,15 +470,6 @@ if (s == null || s.length > 0) // s: String in `s.length > 0` { } ``` ### Algorithm Let `NN(cond, true/false)` be the set of paths (`TermRefs` in the compiler) that we can infer to be non-null if `cond` is `true/false`, respectively. -
abeln revised this gist
Jan 22, 2019 . 1 changed file with 10 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -455,7 +455,7 @@ if (s == null || s2 == null) { } ``` We also support type specialization _within_ the condition, taking into account that `&&` and `||` are short-circuiting: ```scala val s: String|Null if (s != null && s.length > 0) { // s: String in `s.length > 0` @@ -469,6 +469,15 @@ if (s == null || s.length > 0) // s: String in `s.length > 0` { } ``` Finally, we also support `eq`, `ne` and `isInstanceOf` as operators. `isInstanceOf` only kicks in if the type test is against `Null`: ```scala val s: String|Null if (!s.isInstanceOf[Null]) { // s: String } ``` ### Algorithm Let `NN(cond, true/false)` be the set of paths (`TermRefs` in the compiler) that we can infer to be non-null if `cond` is `true/false`, respectively. -
abeln revised this gist
Jan 22, 2019 . 1 changed file with 6 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -161,9 +161,8 @@ We do the patching with a "nullification" function `nf` on types: 4. nf(C[R]) = C[R]|JavaNull if C is Java-defined 5. nf(C[R]) = C[nf(R)]|JavaNull if C isn't Java-defined 6. nf(A => B) = nf(A) => nf(B) 7. nf(A & B) = nf(A) & nf(B) 8. nf(T) = T otherwise (T is any other type) ``` `JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules for `nf` below with examples. @@ -225,10 +224,10 @@ We do the patching with a "nullification" function `nf` on types: `Box[T|JavaNull]|JavaNull`. This is needed because our nullability function is only applied (modularly) to the Java classes, but not to the Scala ones, so we need a way to tell `Box` that it contains a nullable value. * Rules 6 and 7 just recurse structurally on the components of the type. The implementation of rule 7 n the compiler are a bit more involved than the presentation above. Specifically, the implementation makes sure to add `| Null` only at the top level of a type: e.g. `nf(A & B) = (A & B) | JavaNull`, as opposed to `(A | JavaNull) & (B | JavaNull)`. ### JavaNull To enable method chaining on Java-returned values, we have a special `JavaNull` alias -
abeln revised this gist
Jan 21, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -513,7 +513,7 @@ We don't support ```scala val s: String|Null = ??? val s2: String|Null = ??? if (s != null && s == s2) { // s: String inferred // s2: String not inferred } -
abeln revised this gist
Jan 18, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -21,7 +21,7 @@ ## What is This? This proposal and the accompanying [pull request](https://github.com/lampepfl/dotty/pull/5747) describe a modification to the Scala type system that makes reference types (anything that extends `AnyRef`) _non-nullable_. This means the following code will no longer typecheck -
abeln revised this gist
Jan 18, 2019 . 1 changed file with 12 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,31 +44,12 @@ we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  After erasure, `Null` remains a subtype of all reference types (as forced by the JVM).  ## Unsoundness @@ -509,20 +490,20 @@ NN(cond, _) = {} otherwise ``` To type `If(cond, then, else)` 1. compute `NN(cond, true)` and `NN(cond, false)` 2. type the `then` branch with the knowledge that the paths in `NN(cond, true)` are non-nullable 3. ditto for the `else` branch and the paths in `NN(cond, false)` To propagate nullability facts _within_ a condition, when typing `A && B` 1. type `A` 2. compute `NN(A, true)` 3. type `B` augmented context with the facts in `NN(A, true)` Similarly, when typing `A || B` 1. type `A` 2. compute `NN(A, false)` 3. type `B` in an augmented context with the facts in `NN(A, false)` ### Unsupported Idioms We don't support -
abeln revised this gist
Jan 18, 2019 . 1 changed file with 34 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,13 +44,31 @@ we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  ``` +--- Null -----------------------------------+ | + Any ---+--- AnyRef ---+--- String ------------------+ | +--- List[Int] ---------------+ | +--- ... (reference types) ---+--- Nothing | + +--- AnyVal ---+--- Bool --------------------+ +--- Int ---------------------+ +--- ... (value types) -------+ ``` After erasure, `Null` remains a subtype of all reference types (as forced by the JVM). ``` Any ---+--- AnyRef -- +--- String ------------------+ | +--- List --------------------+ | +--- ... (reference types) ---+--- Null ---+ | +--- Nothing | + +--- AnyVal ---+--- Bool --------------------+ + +--- Int ---------------------+------------+ +--- ... (value types) -------+ ``` ## Unsoundness @@ -286,6 +304,9 @@ val s2 = if (ret != null) { def foo(s: String|Null) = ??? foo(s) // allowed, but within `foo` we have to work with the strict `Null` ``` - When looking for an implicit conversion from `A|JavaNull` to `B`, if we can't find it, then also look for an implicit conversion from `A` to `B`. This is done for ease of interop with Java. ### Improving Precision @@ -488,20 +509,20 @@ NN(cond, _) = {} otherwise ``` To type `If(cond, then, else)` - compute `NN(cond, true)` and `NN(cond, false)` - type the `then` branch with the knowledge that the paths in `NN(cond, true)` are non-nullable - ditto for the `else` branch and the paths in `NN(cond, false)` To propagate nullability facts _within_ a condition, when typing `A && B` - type `A` - compute `NN(A, true)` - type `B` augmented context with the facts in `NN(A, true)` Similarly, when typing `A || B` - type `A` - compute `NN(A, false)` - type `B` in an augmented context with the facts in `NN(A, false)` ### Unsupported Idioms We don't support -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 9 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -488,20 +488,20 @@ NN(cond, _) = {} otherwise ``` To type `If(cond, then, else)` 1. compute `NN(cond, true)` and `NN(cond, false)` 2. type the `then` branch with the knowledge that the paths in `NN(cond, true)` are non-nullable 3. ditto for the `else` branch and the paths in `NN(cond, false)` To propagate nullability facts _within_ a condition, when typing `A && B` 1. type `A` 2. compute `NN(A, true)` 3. type `B` augmented context with the facts in `NN(A, true)` Similarly, when typing `A || B` 1. type `A` 2. compute `NN(A, false)` 3. type `B` in an augmented context with the facts in `NN(A, false)` ### Unsupported Idioms We don't support -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -49,7 +49,8 @@ Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`. After erasure, `Null` remains a subtype of all reference types (as forced by the JVM).  ## Unsoundness -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,7 +44,7 @@ we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  After erasure, `Null` remains a subtype of all reference types (as forced by the JVM). -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 2 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -46,29 +46,10 @@ Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  After erasure, `Null` remains a subtype of all reference types (as forced by the JVM).  ## Unsoundness -
abeln revised this gist
Jan 15, 2019 . No changes.There are no files selected for viewing
-
abeln revised this gist
Jan 15, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,7 +44,7 @@ we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  ``` +--- Null -----------------------------------+ -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -44,6 +44,8 @@ we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`.  ``` +--- Null -----------------------------------+ | + -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 3 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,7 +11,7 @@ - [Nullification Function](#nullification-function) - [JavaNull](#javanull) - [Improving Precision](#improving-precision) - [toString](#toString) - [Binary Compatibility](#binary-compatibility) - [Flow-Sensitive Type Inference](#flow-sensitive-type-inference) - [Examples](#examples) @@ -583,6 +583,8 @@ Explicit nullability interacts with local type inference in a few ways: ### Kotlin TODO ## TODOs - Bootstrap Dotty with the new type system - Port the standard library to the new type system -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,7 +11,7 @@ - [Nullification Function](#nullification-function) - [JavaNull](#javanull) - [Improving Precision](#improving-precision) - [toString](#tostring) - [Binary Compatibility](#binary-compatibility) - [Flow-Sensitive Type Inference](#flow-sensitive-type-inference) - [Examples](#examples) @@ -305,7 +305,7 @@ val s2 = if (ret != null) { ### Improving Precision Our nullification function is sometimes too conservative. For example, consider the `trim` method in `java.lang.String`: ```java /** Returns a copy of the string, with leading and trailing whitespace omitted. */ -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 40 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,6 +6,7 @@ - [New Type Hierarchy](#new-type-hierarchy) - [Unsoundness](#unsoundness) - [Equality](#equality) - [Working with Null](#working-with-null) - [Java Interop](#java-interop) - [Nullification Function](#nullification-function) - [JavaNull](#javanull) @@ -120,6 +121,45 @@ class AnyRef extends Any with RefEq class Null extends Any with RefEq ``` ## Working with Null To make working with nullable values easier, we propose adding a few utilities to the standard library. So far, we have found the following useful: - An extension method `.nn` to "cast away" nullability ```scala implicit class NonNull[T](x: T|Null) extends AnyVal { def nn: T = if (x == null) { throw new NullPointerException("tried to cast away nullability, but value is null") } else { x.asInstanceOf[T] } } ``` This means that given `x: String|Null`, `x.nn` has type `String`, so we can call all the usual methods on it. Of course, `x.nn` will throw a NPE if `x` is `null`. - Implicit conversions from/to nullable arrays ```scala implicit def fromNullable1[T](a: Array[T|Null]): Array[T] = a.asInstanceOf[Array[T]] implicit def toNullable1[T](a: Array[T]): Array[T|Null] = a.asInstanceOf[Array[T|Null]] // similar methods for matrices and higher dimensions: e.g. `toNullable{2, 3, ...}` ``` These are useful because Java APIs often return nullable arrays. Additionally, because `Array` is invariant neither `Array[T] <: Array[T|Null]` nor the other way, so to go from one to the other we need casts. For example, suppose we want to write a function that sorts arrays ```scala def sort[T <: AnyRef : Ordering](a: Array[T]): Array[T] = { // error: `copyOf` expects an `Array[T|Null]` and returns an `Array[T|Null]` val a2: Array[T] = java.util.Arrays.copyOf(a, a.length).nn scala.util.Sorting.quickSort(a2) a2 } ``` To fix the error we use the implicit conversions above, which turn the third line into ```scala val a2: Array[T] = fromNullable1(java.util.Arrays.copyOf(toNullable1(a), a.length)).nn ``` Of course, this is also unsound and `a2` could end up with a `null` value in it. ## Java Interop The compiler can load Java classes in two ways: from source or from bytecode. In either case, when a Java class is loaded, we "patch" the type of its members to reflect that Java types remain implicitly nullable. -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,7 +2,7 @@ # Scala with Explicit Nulls ## Contents - [What is This?](#what-is-this) - [New Type Hierarchy](#new-type-hierarchy) - [Unsoundness](#unsoundness) - [Equality](#equality) @@ -18,7 +18,7 @@ - [Unsupported Idioms](#unsupported-idioms) - [Local Type Inference](#local-type-inference) ## What is This? This proposal and the accompanying pull request describe a modification to the Scala type system that makes reference types (anything that extends `AnyRef`) _non-nullable_. -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 18 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,6 +2,7 @@ # Scala with Explicit Nulls ## Contents - [What is this?](#what-is-this?) - [New Type Hierarchy](#new-type-hierarchy) - [Unsoundness](#unsoundness) - [Equality](#equality) @@ -17,6 +18,23 @@ - [Unsupported Idioms](#unsupported-idioms) - [Local Type Inference](#local-type-inference) ## What is this? This proposal and the accompanying pull request describe a modification to the Scala type system that makes reference types (anything that extends `AnyRef`) _non-nullable_. This means the following code will no longer typecheck ```scala val x: String = null // error: found `Null`, but required `String` ``` Instead, to mark a type as nullable we use a [type union](https://dotty.epfl.ch/docs/reference/new-types/union-types.html) ``` val x: String|Null = null // ok ``` Read on for details. ## New Type Hierarchy There are two type hierarchies with respect to `Null`, depending on whether -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 13 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -9,6 +9,7 @@ - [Nullification Function](#nullification-function) - [JavaNull](#javanull) - [Improving Precision](#improving-precision) - [toString](#toString) - [Binary Compatibility](#binary-compatibility) - [Flow-Sensitive Type Inference](#flow-sensitive-type-inference) - [Examples](#examples) @@ -67,6 +68,7 @@ Enforcing sound initialization is a non-goal of this proposal. However, once we we can use a sound initialization scheme like the one proposed by @liufengyun and @biboudis in [https://github.com/lampepfl/dotty/pull/4543](https://github.com/lampepfl/dotty/pull/4543) to eliminate this particular source of unsoundness. ## Equality Because of the unsoundness, we need to allow comparisons of the form `x == null` or `x != null` even when `x` has a non-nullable reference @@ -266,7 +268,17 @@ Currently, the whitelist is hard-coded in Dotty, but the plan is to use an annot of the Java JDK with nullability annotations produced by the [Checker Framework](https://checkerframework.org/) (hat tip to @liufengyun and Werner Dietl for suggesting this). ### toString The `toString` method is a special case where we chose to trade away soundness for precision and usability. `toString` is special because even though it lives in `scala.Any`, it actually comes from `java.lang.Object` and it could, in principle, be overriden to return `null`. However, changing `toString`'s signature to return `String|JavaNull` would break too much existing Scala code. Additionally, our assumption is that `toString` is unlikely to return null. This means the signature of `toString` remains `def toString(): String`. ## Binary Compatibility Our strategy for binary compatibility with Scala binaries that predate explicit nulls -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 20 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -245,6 +245,26 @@ val s2 = if (ret != null) { ### Improving Precision Our nullification function is sometimes too conservative. For example, consider the `toString` method in `java.lang.String`: ```java /** Returns a copy of the string, with leading and trailing whitespace omitted. */ public String trim() ``` A vanilla application of nullification will turn the above into `def trim(): String|JavaNull`. However, inspecting the function contract in the javadoc reveals that if `s` is a non-null string to begin with, then `s.trim()` should in fact never return `null` (if `s` is null, then `s.trim()` should throw). From the point of view of Scala it is then preferable to have a more precise signature for `trim` where the return type is `String`. In general, this is a problem for _many_ methods in the Java standard library: in particular, methods in commonly-used classes like `String` and `Class`. We address this by maintaining a _whitelist_ of methods that need "special" treatment during nullification (e.g. nullify only the arguments, or only the return type). Currently, the whitelist is hard-coded in Dotty, but the plan is to use an annotated [version](https://github.com/typetools/checker-framework/tree/master/checker/jdk/nullness) of the Java JDK with nullability annotations produced by the [Checker Framework](https://checkerframework.org/) (hat tip to @liufengyun and Werner Dietl for suggesting this). ## Binary Compatibility -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 14 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -112,11 +112,15 @@ Specifically, we patch We do the patching with a "nullification" function `nf` on types: ``` 1. nf(R) = R|JavaNull if R is a reference type (a subtype of AnyRef) 2. nf(R) = R if R is a value type (a subtype of AnyVal) 3. nf(T) = T|JavaNull if T is a type parameter 4. nf(C[R]) = C[R]|JavaNull if C is Java-defined 5. nf(C[R]) = C[nf(R)]|JavaNull if C isn't Java-defined 6. nf(A => B) = nf(A) => nf(B) 7. nf(A | B) = nf(A) | nf(B) 8. nf(A & B) = nf(A) & nf(B) 9. nf(T) = T otherwise (T is any other type) ``` `JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules for `nf` below with examples. @@ -178,6 +182,11 @@ We do the patching with a "nullification" function `nf` on types: `Box[T|JavaNull]|JavaNull`. This is needed because our nullability function is only applied (modularly) to the Java classes, but not to the Scala ones, so we need a way to tell `Box` that it contains a nullable value. * Rules 6, 7, and 8 just recurse structurally on the components of the type. The implementation of rules 7 and 8 in the compiler are a bit more involved than the presentation above. Specifically, they make sure to add `| Null` only at the top level of a type: e.g. `nf(A | B) = A | B | JavaNull`, as opposed to `(A | JavaNull) | (B | JavaNull)`. ### JavaNull To enable method chaining on Java-returned values, we have a special `JavaNull` alias ```scala -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -454,7 +454,7 @@ Explicit nullability interacts with local type inference in a few ways: def foo2(): String|Null = "hello" val x = foo2() // x: String|Null inferred ``` Otherwise, the nullable type `String|Null` would be collapsed to `Any`, which loses too much information. - Nullable unions in prototypes: the compiler currently doesn't allow unions in prototypes ```scala -
abeln revised this gist
Jan 15, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -471,7 +471,7 @@ Explicit nullability interacts with local type inference in a few ways: The problem is that the compiler sees a prototype of `Array[String|Int]|Int`, and because the outer type is a union it discards the prototype and switches to synthesis. See [https://github.com/lampepfl/dotty/commit/8067b952875426d640968be865773f6ef3783f3c](https://github.com/lampepfl/dotty/commit/8067b952875426d640968be865773f6ef3783f3c) for why this was done. We've changed this behaviour so that nullable unions can be used in prototypes -
abeln revised this gist
Jan 14, 2019 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -234,6 +234,10 @@ val s2 = if (ret != null) { foo(s) // allowed, but within `foo` we have to work with the strict `Null` ``` ### Improving Precision ## Binary Compatibility Our strategy for binary compatibility with Scala binaries that predate explicit nulls -
abeln revised this gist
Jan 14, 2019 . 1 changed file with 0 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,7 +8,6 @@ - [Java Interop](#java-interop) - [Nullification Function](#nullification-function) - [JavaNull](#javanull) - [Improving Precision](#improving-precision) - [Binary Compatibility](#binary-compatibility) - [Flow-Sensitive Type Inference](#flow-sensitive-type-inference) @@ -235,8 +234,6 @@ val s2 = if (ret != null) { foo(s) // allowed, but within `foo` we have to work with the strict `Null` ``` ## Binary Compatibility Our strategy for binary compatibility with Scala binaries that predate explicit nulls -
abeln revised this gist
Jan 14, 2019 . 1 changed file with 50 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,7 @@ ## Contents - [New Type Hierarchy](#new-type-hierarchy) - [Unsoundness](#unsoundness) - [Equality](#equality) - [Java Interop](#java-interop) - [Nullification Function](#nullification-function) @@ -48,9 +49,57 @@ Any ---+--- AnyRef -- +--- String ------------------+ +--- ... (value types) -------+ ``` ## Unsoundness The new type system is unsound with respect to `null`. This means there are still instances where an expressions has a non-nullable type like `String`, but its value is `null`. The unsoundness happens because uninitialized fields in a class start out as `null`: ```scala class C { val f: String = foo(f) def foo(f2: String): String = if (f2 == null) "field is null" else f2 } val c = new C() // c.f == "field is null" ``` Enforcing sound initialization is a non-goal of this proposal. However, once we have a type system where nullability is explicit, we can use a sound initialization scheme like the one proposed by @liufengyun and @biboudis in [https://github.com/lampepfl/dotty/pull/4543](https://github.com/lampepfl/dotty/pull/4543) to eliminate this particular source of unsoundness. ## Equality Because of the unsoundness, we need to allow comparisons of the form `x == null` or `x != null` even when `x` has a non-nullable reference type (but not a value type). This is so we have an "escape hatch" for when we know `x` is nullable even when the type says it shouldn't be. ```scala val x: String|Null = null x == null // ok: x is a nullable string "hello" == null // ok: String is a reference type 1 == null // error: Int is a value type ``` ### Reference Equality Recall that `Null` is now a direct subtype of `Any`, as opposed to `AnyRef`. However, we also need to allow reference equality comparisons: ```scala val x: String = null x eq null // ok: could return `true` because of unsoundness ``` We implement this by moving the `eq` and `ne` methods out of `AnyRef` and into a new trait `RefEq` that is extended by both `AnyRef` and `Null`: ```scala trait RefEq { def eq(that: RefEq): Boolean def ne(that: RefEq): Boolean } class AnyRef extends Any with RefEq class Null extends Any with RefEq ``` ## Java Interop -
abeln revised this gist
Jan 11, 2019 . 1 changed file with 9 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,8 +3,7 @@ ## Contents - [New Type Hierarchy](#new-type-hierarchy) - [Equality](#equality) - [Java Interop](#java-interop) - [Nullification Function](#nullification-function) - [JavaNull](#javanull) @@ -16,31 +15,26 @@ - [Algorithm](#algorithm) - [Unsupported Idioms](#unsupported-idioms) - [Local Type Inference](#local-type-inference) ## New Type Hierarchy There are two type hierarchies with respect to `Null`, depending on whether we're _before_ or _after_ erasure. Before erasure, `Null` is no longer a subtype of all reference types. Additionally, `Null` is a subtype of `Any` directly, as opposed to `AnyRef`. ``` +--- Null -----------------------------------+ | + Any ---+--- AnyRef ---+--- String ------------------+ | +--- List[Int] ---------------+ | +--- ... (reference types) ---+--- Nothing | + +--- AnyVal ---+--- Bool --------------------+ +--- Int ---------------------+ +--- ... (value types) -------+ ``` After erasure, `Null` remains a subtype of all reference types (as forced by the JVM). ``` @@ -54,6 +48,10 @@ Any ---+--- AnyRef -- +--- String ------------------+ +--- ... (value types) -------+ ``` ## Equality // TODO: talk about both "==" and "eq". Mention `RefEq`. ## Java Interop The compiler can load Java classes in two ways: from source or from bytecode. In either case, when a Java class is loaded, we "patch" the type of its members to reflect that Java types remain implicitly nullable. -
abeln revised this gist
Jan 10, 2019 . 1 changed file with 44 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -387,9 +387,50 @@ We don't support ``` ## Local Type Inference Explicit nullability interacts with local type inference in a few ways: - Suppor for SAM types: if `S` is a SAM type, then we consider `S|Null` as a SAM type as well. This allows ```scala trait S { def foo(x: Int): Int } val s: S|Null = (x: Int) => x ``` - Inferred nullable unions: Dotty currently never infers a union type; instead, the union is eagerly widened. (see [#4687](https://github.com/lampepfl/dotty/issues/4867)). ```scala def foo(): Int|String = 42 val x = foo() // x: Any inferred ``` We changed the inference rules so that unions of the form `T|Null` are inferred: ```scala def foo2(): String|Null = "hello" val x = foo2() // x: String|Null inferred ``` Otherwise, the nullable type `String|Null` would be collapsed to `AnyRef`, which loses too much information. - Nullable unions in prototypes: the compiler currently doesn't allow unions in prototypes ```scala def bar(a: Array[String|Int]|Int) = () bar(Array(42)) 4 | bar(Array(42)) | ^^^^^^^^^ | found: Array[Int] | required: Array[String | Int] | Int ``` Notice that the type of `Array(42)` is synthesized as `Array[Int]`, even though the (correct) type `Array[String|Int]` could be inherited. The problem is that the compiler sees a prototype of `Array[String|Int]|Int`, and because the outer type is a union it discards the prototype and switches to synthesis. See [here](https://github.com/lampepfl/dotty/commit/8067b952875426d640968be865773f6ef3783f3c) for why this was done. We've changed this behaviour so that nullable unions can be used in prototypes ```scala def bar(a: Array[String|Null]|Null) = () bar(Array("hello")) // ok: inferred type parameter via prototype is String|Null` ``` ## Other Languages
NewerOlder