-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Dotty with explicit nulls (and flow typing) #7546
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
Changes from 3 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
bc972b2
remove flow typing from main branch
noti0na1 b48643f
remove NonNullTermRef
noti0na1 d59a792
Merge pull request #43 from noti0na1/dotty-explicit-nulls-only
abeln 763b05c
add extractor for Null ops; remove useless imports; reduce side effects
noti0na1 00d6607
merge upstream (Nullability Analysis without NotNull #7556)
noti0na1 49a35e3
rewrite widenUnion
noti0na1 2df913f
Update types in DottyPredef
noti0na1 f009775
add comments for extractors
noti0na1 56cdadc
optimize widenUnion
noti0na1 5c7c312
modify eq tests
noti0na1 1b28c63
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 5ddcab6
remove redundent test
noti0na1 35dda77
Merge pull request #44 from noti0na1/dotty-explicit-nulls-only
noti0na1 334a430
fix normalizing nullable intersection type
noti0na1 b9cbc1f
remove JavaEnumValue from AfterLoadFlags
noti0na1 539051a
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 a5b6447
Merge pull request #45 from noti0na1/dotty-explicit-nulls-only
noti0na1 2a8cc49
Disallow comparison between object and null
noti0na1 423ef59
Fix case process
noti0na1 708250c
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 b12415f
Move explicit null case
noti0na1 a423df5
Merge pull request #46 from noti0na1/dotty-explicit-nulls-only
noti0na1 d12a9f1
add notNull to tree
noti0na1 b49aca9
add null check for paths
noti0na1 6a321ad
fix long stable path
noti0na1 b813439
better return type for Var; inline without extra val assign; better n…
noti0na1 ef5864e
add flow 'tests
noti0na1 7d40d5c
Fix isStable; fix var track in lazy val
noti0na1 ce606b6
Fix closure check
noti0na1 50b70ca
Add while, match tests
noti0na1 1eed780
Remove unused code
noti0na1 c5aab82
Update comments
noti0na1 465823d
Update doc, WIP
noti0na1 49d550e
Add flow typing to doc
noti0na1 bdbde1d
Simplify case
noti0na1 58db0c0
Refine comments
noti0na1 91b54aa
fix assert
noti0na1 61ca5e6
Add more comments and examples
noti0na1 46fc5cd
Merge pull request #47 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 0230180
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 3bb1b82
Merge pull request #48 from noti0na1/dotty-explicit-nulls-notNull
abeln 77df754
add usedOutOfOrder
noti0na1 6bdb9f0
Add tests
noti0na1 7dc2e76
Optimize NullOps; add helper functions
noti0na1 0acba32
Edit comments
noti0na1 dea268a
Add suggested NotNull annots
noti0na1 d26b424
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 a8f3dc1
Merge pull request #49 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 2f42d1b
Update comments
noti0na1 1c15bef
Merge pull request #50 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 9a3a625
Fix typos
noti0na1 2af339f
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 c41b926
Rewrite the if stat
noti0na1 85c0855
Merge pull request #51 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 characters
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 characters
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 characters
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 characters
156 changes: 156 additions & 0 deletions
156
compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala
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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package dotty.tools.dotc.core | ||
|
||
import dotty.tools.dotc.core.Contexts.Context | ||
import dotty.tools.dotc.core.Flags.JavaDefined | ||
import dotty.tools.dotc.core.StdNames.{jnme, nme} | ||
import dotty.tools.dotc.core.Symbols._ | ||
import dotty.tools.dotc.core.Types._ | ||
import NullOpsDecorator._ | ||
|
||
/** This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, | ||
* as Scala types, which are explicitly nullable. | ||
* | ||
* The transformation is (conceptually) a function `n` that adheres to the following rules: | ||
* (1) n(T) = T|JavaNull if T is a reference type | ||
* (2) n(T) = T if T is a value type | ||
* (3) n(C[T]) = C[T]|JavaNull if C is Java-defined | ||
* (4) n(C[T]) = C[n(T)]|JavaNull if C is Scala-defined | ||
* (5) n(A|B) = n(A)|n(B)|JavaNull | ||
* (6) n(A&B) = n(A) & n(B) | ||
* (7) n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R | ||
* (8) n(T) = T otherwise | ||
* | ||
* Treatment of generics (rules 3 and 4): | ||
* - if `C` is Java-defined, then `n(C[T]) = C[T]|JavaNull`. That is, we don't recurse | ||
* on the type argument, and only add JavaNull on the outside. This is because | ||
* `C` itself will be nullified, and in particular so will be usages of `C`'s type argument within C's body. | ||
* e.g. calling `get` on a `java.util.List[String]` already returns `String|Null` and not `String`, so | ||
* we don't need to write `java.util.List[String|Null]`. | ||
* - if `C` is Scala-defined, however, then we want `n(C[T]) = C[n(T)]|JavaNull`. This is because | ||
* `C` won't be nullified, so we need to indicate that its type argument is nullable. | ||
* | ||
* Notice that since the transformation is only applied to types attached to Java symbols, it doesn't need | ||
* to handle the full spectrum of Scala types. Additionally, some kinds of symbols like constructors and | ||
* enum instances get special treatment. | ||
*/ | ||
object JavaNullInterop { | ||
|
||
/** Transforms the type `tp` of Java member `sym` to be explicitly nullable. | ||
* `tp` is needed because the type inside `sym` might not be set when this method is called. | ||
* | ||
* e.g. given a Java method | ||
* String foo(String arg) { return arg; } | ||
* | ||
* After calling `nullifyMember`, Scala will see the method as | ||
* | ||
* def foo(arg: String|JavaNull): String|JavaNull | ||
* | ||
* This nullability function uses `JavaNull` instead of vanilla `Null`, for usability. | ||
* This means that we can select on the return of `foo`: | ||
* | ||
* val len = foo("hello").length | ||
* | ||
* But the selection can throw an NPE if the returned value is `null`. | ||
*/ | ||
def nullifyMember(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { | ||
assert(ctx.explicitNulls) | ||
assert(sym.is(JavaDefined), "can only nullify java-defined members") | ||
|
||
// Some special cases when nullifying the type | ||
if (sym.name == nme.TYPE_ || sym.isAllOf(Flags.JavaEnumValue)) | ||
// Don't nullify the `TYPE` field in every class and Java enum instances | ||
tp | ||
else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym)) | ||
// Don't nullify the return type of the `toString` method. | ||
// Don't nullify the return type of constructors. | ||
// Don't nullify the return type of methods with a not-null annotation. | ||
nullifyExceptReturnType(tp) | ||
else | ||
// Otherwise, nullify everything | ||
nullifyType(tp) | ||
} | ||
|
||
private def hasNotNullAnnot(sym: Symbol)(implicit ctx: Context): Boolean = | ||
ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) | ||
|
||
/** If tp is a MethodType, the parameters and the inside of return type are nullified, | ||
* but the result return type is not nullable. | ||
* If tp is a type of a field, the inside of the type is nullified, | ||
* but the result type is not nullable. | ||
*/ | ||
private def nullifyExceptReturnType(tp: Type)(implicit ctx: Context): Type = | ||
new JavaNullMap(true)(ctx)(tp) | ||
|
||
/** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ | ||
private def nullifyType(tp: Type)(implicit ctx: Context): Type = | ||
new JavaNullMap(false)(ctx)(tp) | ||
|
||
/** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| JavaNull` | ||
* in the right places to make the nulls explicit in Scala. | ||
* | ||
* @param outermostLevelAlreadyNullable whether this type is already nullable at the outermost level. | ||
* For example, `Array[String]|JavaNull` is already nullable at the | ||
* outermost level, but `Array[String|JavaNull]` isn't. | ||
* If this parameter is set to true, then the types of fields, and the return | ||
* types of methods will not be nullified. | ||
* This is useful for e.g. constructors, and also so that `A & B` is nullified | ||
* to `(A & B) | JavaNull`, instead of `(A|JavaNull & B|JavaNull) | JavaNull`. | ||
*/ | ||
private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { | ||
/** Should we nullify `tp` at the outermost level? */ | ||
def needsNull(tp: Type): Boolean = | ||
!outermostLevelAlreadyNullable && (tp match { | ||
case tp: TypeRef => | ||
// We don't modify value types because they're non-nullable even in Java. | ||
!tp.symbol.isValueClass && | ||
// We don't modify `Any` because it's already nullable. | ||
!tp.isRef(defn.AnyClass) && | ||
// We don't nullify Java varargs at the top level. | ||
// Example: if `setNames` is a Java method with signature `void setNames(String... names)`, | ||
// then its Scala signature will be `def setNames(names: (String|JavaNull)*): Unit`. | ||
// This is because `setNames(null)` passes as argument a single-element array containing the value `null`, | ||
// and not a `null` array. | ||
!tp.isRef(defn.RepeatedParamClass) | ||
case _ => true | ||
}) | ||
|
||
override def apply(tp: Type): Type = { | ||
// Fast version of Type::toJavaNullableUnion that doesn't check whether the type | ||
// is already a union. | ||
def toJavaNullableUnion(tpe: Type): Type = OrType(tpe, defn.JavaNullAliasType) | ||
|
||
tp match { | ||
case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) | ||
case appTp @ AppliedType(tycon, targs) => | ||
val oldOutermostNullable = outermostLevelAlreadyNullable | ||
// We don't make the outmost levels of type arguements nullable if tycon is Java-defined. | ||
// This is because Java classes are _all_ nullified, so both `java.util.List[String]` and | ||
// `java.util.List[String|Null]` contain nullable elements. | ||
outermostLevelAlreadyNullable = tp.classSymbol.is(JavaDefined) | ||
val targs2 = targs map this | ||
outermostLevelAlreadyNullable = oldOutermostNullable | ||
val appTp2 = derivedAppliedType(appTp, tycon, targs2) | ||
if (needsNull(tycon)) toJavaNullableUnion(appTp2) else appTp2 | ||
case ptp: PolyType => | ||
derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) | ||
case mtp: MethodType => | ||
val oldOutermostNullable = outermostLevelAlreadyNullable | ||
outermostLevelAlreadyNullable = false | ||
val paramInfos2 = mtp.paramInfos map this | ||
outermostLevelAlreadyNullable = oldOutermostNullable | ||
derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) | ||
case tp: TypeAlias => mapOver(tp) | ||
case tp: AndType => | ||
// nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add | ||
// duplicate `JavaNull`s at the outermost level inside `A` and `B`. | ||
outermostLevelAlreadyNullable = true | ||
toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) | ||
case tp: TypeParamRef if needsNull(tp) => toJavaNullableUnion(tp) | ||
// In all other cases, return the type unchanged. | ||
// In particular, if the type is a ConstantType, then we don't nullify it because it is the | ||
// type of a final non-nullable field. | ||
case _ => tp | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.