From 1c5679b61669557b2f4aa89d292f41130abb008b Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Thu, 4 Apr 2019 13:45:55 -0400 Subject: [PATCH 01/54] Add an explicit nulls mode to Dotty This commit adds a `-Yexplicit-nulls` experimental flag which, if enabled, modifies the type system so that reference types are non-nullable. Nullability is then expressed explicitly via type unions (e.g. `val s: String|Null = null`). At a high level, the changes to the compiler are: * A new type hierarchy for `Null`, which is now a subtype of `Any` directly, as opposed to being a subtype of _every_ refernce type. * A "translation layer" from Java types to Scala types so that Java interop can happen soundly yet conveniently. * An implementation of flow-typing so we can support a more natural style of programming with explicit nulls. --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/core/Contexts.scala | 15 +- .../dotty/tools/dotc/core/Definitions.scala | 110 +++-- .../src/dotty/tools/dotc/core/Flags.scala | 4 +- .../src/dotty/tools/dotc/core/FlowTyper.scala | 242 +++++++++++ .../tools/dotc/core/JavaNullInterop.scala | 186 +++++++++ .../tools/dotc/core/NullOpsDecorator.scala | 147 +++++++ .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 14 +- .../dotty/tools/dotc/core/TypeErasure.scala | 4 +- .../src/dotty/tools/dotc/core/Types.scala | 131 ++++-- .../dotc/core/classfile/ClassfileParser.scala | 2 + .../transform/CollectNullableFields.scala | 6 +- .../tools/dotc/transform/FirstTransform.scala | 23 +- .../dotty/tools/dotc/transform/LazyVals.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 1 + .../dotc/transform/SyntheticMembers.scala | 5 +- .../tools/dotc/transform/TypeTestsCasts.scala | 1 + .../tools/dotc/transform/patmat/Space.scala | 62 ++- .../dotty/tools/dotc/typer/Applications.scala | 18 +- .../src/dotty/tools/dotc/typer/Namer.scala | 70 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 104 +++-- .../dotty/tools/dotc/CompilationTests.scala | 19 + .../tools/vulpix/TestConfiguration.scala | 3 + docs/docs/internals/explicit-nulls.md | 186 +++++++++ .../other-new-features/explicit-nulls.md | 382 ++++++++++++++++++ .../explicit-nulls-type-hierarchy.png | Bin 0 -> 57752 bytes .../scala/ExplicitNulls.scala | 30 ++ tests/explicit-nulls/neg-patmat/patmat1.scala | 38 ++ tests/explicit-nulls/neg/alias.scala | 24 ++ tests/explicit-nulls/neg/basic.scala | 11 + tests/explicit-nulls/neg/default.scala | 13 + tests/explicit-nulls/neg/eq.scala | 26 ++ tests/explicit-nulls/neg/eq2.scala | 15 + tests/explicit-nulls/neg/erasure.scala | 6 + .../neg/flow-conservative.scala | 55 +++ .../explicit-nulls/neg/flow-implicitly.scala | 10 + tests/explicit-nulls/neg/flow.scala | 182 +++++++++ tests/explicit-nulls/neg/flow2.scala | 18 + tests/explicit-nulls/neg/flow3.scala | 23 ++ tests/explicit-nulls/neg/flow4.scala | 18 + tests/explicit-nulls/neg/flow5.scala | 66 +++ tests/explicit-nulls/neg/flow6.scala | 70 ++++ tests/explicit-nulls/neg/flow7.scala | 11 + .../neg/interop-array-src/J.java | 3 + .../neg/interop-array-src/S.scala | 10 + .../neg/interop-java-enum-src/Planet.java | 27 ++ .../neg/interop-java-enum-src/S.scala | 6 + .../explicit-nulls/neg/interop-javanull.scala | 8 + .../neg/interop-method-src/J.java | 5 + .../neg/interop-method-src/S.scala | 10 + .../neg/interop-polytypes.scala | 7 + .../neg/interop-propagate.scala | 11 + tests/explicit-nulls/neg/interop-return.scala | 14 + tests/explicit-nulls/neg/java-null.scala | 10 + .../explicit-nulls/neg/null-subtype-any.scala | 17 + tests/explicit-nulls/neg/strip.scala | 16 + tests/explicit-nulls/neg/type-arg.scala | 13 + tests/explicit-nulls/pos/array.scala | 5 + .../pos/dont-widen-singleton.scala | 9 + .../explicit-nulls/pos/dont-widen-src/J.java | 3 + .../explicit-nulls/pos/dont-widen-src/S.scala | 7 + tests/explicit-nulls/pos/dont-widen.scala | 8 + tests/explicit-nulls/pos/flow-singleton.scala | 9 + tests/explicit-nulls/pos/flow.scala | 160 ++++++++ tests/explicit-nulls/pos/flow2.scala | 11 + tests/explicit-nulls/pos/flow3.scala | 9 + tests/explicit-nulls/pos/flow4.scala | 24 ++ tests/explicit-nulls/pos/flow5.scala | 19 + .../pos/interop-constructor-src/J.java | 6 + .../pos/interop-constructor-src/S.scala | 6 + .../pos/interop-constructor.scala | 7 + .../pos/interop-enum-src/Day.java | 6 + .../pos/interop-enum-src/Planet.java | 19 + .../pos/interop-enum-src/S.scala | 6 + .../pos/interop-generics/J.java | 9 + .../pos/interop-generics/S.scala | 7 + .../pos/interop-javanull-src/J.java | 8 + .../pos/interop-javanull-src/S.scala | 6 + .../explicit-nulls/pos/interop-javanull.scala | 10 + .../explicit-nulls/pos/interop-nn-src/J.java | 4 + .../explicit-nulls/pos/interop-nn-src/S.scala | 15 + .../pos/interop-poly-src/J.java | 14 + .../pos/interop-poly-src/S.scala | 14 + .../pos/interop-static-src/J.java | 4 + .../pos/interop-static-src/S.scala | 6 + .../explicit-nulls/pos/interop-tostring.scala | 9 + .../pos/interop-valuetypes.scala | 6 + tests/explicit-nulls/pos/java-null.scala | 16 + tests/explicit-nulls/pos/nn.scala | 20 + tests/explicit-nulls/pos/nn2.scala | 10 + tests/explicit-nulls/pos/nullable-union.scala | 14 + .../explicit-nulls/pos/pattern-matching.scala | 38 ++ tests/explicit-nulls/pos/ref-eq.scala | 31 ++ tests/explicit-nulls/pos/throw-null.scala | 8 + tests/explicit-nulls/pos/tref-caching.scala | 19 + .../run/flow-extension-methods.scala | 44 ++ tests/explicit-nulls/run/flow.check | 1 + tests/explicit-nulls/run/flow.scala | 30 ++ .../run/generic-java-array-src/JA.java | 13 + .../run/generic-java-array-src/Test.scala | 21 + .../run/instanceof-nothing.scala | 25 ++ .../run/interop-unsound-src/J.java | 17 + .../run/interop-unsound-src/S.scala | 33 ++ tests/explicit-nulls/run/java-null.scala | 17 + tests/explicit-nulls/run/nn.scala | 21 + tests/explicit-nulls/run/subtype-any.scala | 28 ++ tests/pos/interop-tostring.scala | 8 + tests/pos/interop-type-field.scala | 5 + 109 files changed, 3225 insertions(+), 132 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/FlowTyper.scala create mode 100644 compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala create mode 100644 compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala create mode 100644 docs/docs/internals/explicit-nulls.md create mode 100644 docs/docs/reference/other-new-features/explicit-nulls.md create mode 100644 docs/images/explicit-nulls/explicit-nulls-type-hierarchy.png create mode 100644 library/src-bootstrapped/scala/ExplicitNulls.scala create mode 100644 tests/explicit-nulls/neg-patmat/patmat1.scala create mode 100644 tests/explicit-nulls/neg/alias.scala create mode 100644 tests/explicit-nulls/neg/basic.scala create mode 100644 tests/explicit-nulls/neg/default.scala create mode 100644 tests/explicit-nulls/neg/eq.scala create mode 100644 tests/explicit-nulls/neg/eq2.scala create mode 100644 tests/explicit-nulls/neg/erasure.scala create mode 100644 tests/explicit-nulls/neg/flow-conservative.scala create mode 100644 tests/explicit-nulls/neg/flow-implicitly.scala create mode 100644 tests/explicit-nulls/neg/flow.scala create mode 100644 tests/explicit-nulls/neg/flow2.scala create mode 100644 tests/explicit-nulls/neg/flow3.scala create mode 100644 tests/explicit-nulls/neg/flow4.scala create mode 100644 tests/explicit-nulls/neg/flow5.scala create mode 100644 tests/explicit-nulls/neg/flow6.scala create mode 100644 tests/explicit-nulls/neg/flow7.scala create mode 100644 tests/explicit-nulls/neg/interop-array-src/J.java create mode 100644 tests/explicit-nulls/neg/interop-array-src/S.scala create mode 100644 tests/explicit-nulls/neg/interop-java-enum-src/Planet.java create mode 100644 tests/explicit-nulls/neg/interop-java-enum-src/S.scala create mode 100644 tests/explicit-nulls/neg/interop-javanull.scala create mode 100644 tests/explicit-nulls/neg/interop-method-src/J.java create mode 100644 tests/explicit-nulls/neg/interop-method-src/S.scala create mode 100644 tests/explicit-nulls/neg/interop-polytypes.scala create mode 100644 tests/explicit-nulls/neg/interop-propagate.scala create mode 100644 tests/explicit-nulls/neg/interop-return.scala create mode 100644 tests/explicit-nulls/neg/java-null.scala create mode 100644 tests/explicit-nulls/neg/null-subtype-any.scala create mode 100644 tests/explicit-nulls/neg/strip.scala create mode 100644 tests/explicit-nulls/neg/type-arg.scala create mode 100644 tests/explicit-nulls/pos/array.scala create mode 100644 tests/explicit-nulls/pos/dont-widen-singleton.scala create mode 100644 tests/explicit-nulls/pos/dont-widen-src/J.java create mode 100644 tests/explicit-nulls/pos/dont-widen-src/S.scala create mode 100644 tests/explicit-nulls/pos/dont-widen.scala create mode 100644 tests/explicit-nulls/pos/flow-singleton.scala create mode 100644 tests/explicit-nulls/pos/flow.scala create mode 100644 tests/explicit-nulls/pos/flow2.scala create mode 100644 tests/explicit-nulls/pos/flow3.scala create mode 100644 tests/explicit-nulls/pos/flow4.scala create mode 100644 tests/explicit-nulls/pos/flow5.scala create mode 100644 tests/explicit-nulls/pos/interop-constructor-src/J.java create mode 100644 tests/explicit-nulls/pos/interop-constructor-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-constructor.scala create mode 100644 tests/explicit-nulls/pos/interop-enum-src/Day.java create mode 100644 tests/explicit-nulls/pos/interop-enum-src/Planet.java create mode 100644 tests/explicit-nulls/pos/interop-enum-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-generics/J.java create mode 100644 tests/explicit-nulls/pos/interop-generics/S.scala create mode 100644 tests/explicit-nulls/pos/interop-javanull-src/J.java create mode 100644 tests/explicit-nulls/pos/interop-javanull-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-javanull.scala create mode 100644 tests/explicit-nulls/pos/interop-nn-src/J.java create mode 100644 tests/explicit-nulls/pos/interop-nn-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-poly-src/J.java create mode 100644 tests/explicit-nulls/pos/interop-poly-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-static-src/J.java create mode 100644 tests/explicit-nulls/pos/interop-static-src/S.scala create mode 100644 tests/explicit-nulls/pos/interop-tostring.scala create mode 100644 tests/explicit-nulls/pos/interop-valuetypes.scala create mode 100644 tests/explicit-nulls/pos/java-null.scala create mode 100644 tests/explicit-nulls/pos/nn.scala create mode 100644 tests/explicit-nulls/pos/nn2.scala create mode 100644 tests/explicit-nulls/pos/nullable-union.scala create mode 100644 tests/explicit-nulls/pos/pattern-matching.scala create mode 100644 tests/explicit-nulls/pos/ref-eq.scala create mode 100644 tests/explicit-nulls/pos/throw-null.scala create mode 100644 tests/explicit-nulls/pos/tref-caching.scala create mode 100644 tests/explicit-nulls/run/flow-extension-methods.scala create mode 100644 tests/explicit-nulls/run/flow.check create mode 100644 tests/explicit-nulls/run/flow.scala create mode 100644 tests/explicit-nulls/run/generic-java-array-src/JA.java create mode 100644 tests/explicit-nulls/run/generic-java-array-src/Test.scala create mode 100644 tests/explicit-nulls/run/instanceof-nothing.scala create mode 100644 tests/explicit-nulls/run/interop-unsound-src/J.java create mode 100644 tests/explicit-nulls/run/interop-unsound-src/S.scala create mode 100644 tests/explicit-nulls/run/java-null.scala create mode 100644 tests/explicit-nulls/run/nn.scala create mode 100644 tests/explicit-nulls/run/subtype-any.scala create mode 100644 tests/pos/interop-tostring.scala create mode 100644 tests/pos/interop-type-field.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index fc50873a4e68..170ef67762f4 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -160,6 +160,7 @@ class ScalaSettings extends Settings.SettingGroup { // Extremely experimental language features val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Enable kind polymorphism (see http://dotty.epfl.ch/docs/reference/kind-polymorphism.html). Potentially unsound.") + val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 67e08f14f411..47edff7ca316 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -37,6 +37,8 @@ import xsbti.AnalysisCallback import plugins._ import java.util.concurrent.atomic.AtomicInteger +import dotty.tools.dotc.core.FlowTyper.FlowFacts + object Contexts { private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]() @@ -47,7 +49,8 @@ object Contexts { private val (compilationUnitLoc, store6) = store5.newLocation[CompilationUnit]() private val (runLoc, store7) = store6.newLocation[Run]() private val (profilerLoc, store8) = store7.newLocation[Profiler]() - private val initialStore = store8 + private val (flowFactsLoc, store9) = store8.newLocation[FlowFacts](FlowTyper.emptyFlowFacts) + private val initialStore = store9 /** A context is passed basically everywhere in dotc. * This is convenient but carries the risk of captured contexts in @@ -143,6 +146,9 @@ object Contexts { protected def gadt_=(gadt: GadtConstraint): Unit = _gadt = gadt final def gadt: GadtConstraint = _gadt + /** The terms currently known to be non-null (in spite of their declared type) */ + def flowFacts: FlowFacts = store(flowFactsLoc) + /** The history of implicit searches that are currently active */ private[this] var _searchHistory: SearchHistory = null protected def searchHistory_= (searchHistory: SearchHistory): Unit = _searchHistory = searchHistory @@ -420,6 +426,9 @@ object Contexts { def useColors: Boolean = base.settings.color.value == "always" + /** Is the explicit nulls option set? */ + def explicitNulls: Boolean = base.settings.YexplicitNulls.value + protected def init(outer: Context, origin: Context): this.type = { util.Stats.record("Context.fresh") _outer = outer @@ -536,6 +545,10 @@ object Contexts { def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this } def setGadt(gadt: GadtConstraint): this.type = { this.gadt = gadt; this } def setFreshGADTBounds: this.type = setGadt(gadt.fresh) + def addFlowFacts(facts: FlowFacts): this.type = { + assert(settings.YexplicitNulls.value) + updateStore(flowFactsLoc, store(flowFactsLoc) ++ facts) + } def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this } def setSource(source: SourceFile): this.type = { this.source = source; this } def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index be936383aa08..1984e9d96976 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -4,7 +4,7 @@ package core import scala.annotation.{threadUnsafe => tu} import Types._, Contexts._, Symbols._, SymDenotations._, StdNames._, Names._ -import Flags._, Scopes._, Decorators._, NameOps._, Periods._ +import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._ import unpickleScala2.Scala2Unpickler.ensureConstructor import scala.collection.mutable import collection.mutable @@ -278,7 +278,7 @@ class Definitions { @tu lazy val ObjectClass: ClassSymbol = { val cls = ctx.requiredClass("java.lang.Object") assert(!cls.isCompleted, "race for completing java.lang.Object") - cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope) + cls.info = ClassInfo(cls.owner.thisType, cls, AnyType :: Nil, newScope) cls.setFlag(NoInits) // The companion object doesn't really exist, so it needs to be marked as @@ -295,8 +295,18 @@ class Definitions { @tu lazy val AnyRefAlias: TypeSymbol = enterAliasType(tpnme.AnyRef, ObjectType) def AnyRefType: TypeRef = AnyRefAlias.typeRef - @tu lazy val Object_eq: TermSymbol = enterMethod(ObjectClass, nme.eq, methOfAnyRef(BooleanType), Final) - @tu lazy val Object_ne: TermSymbol = enterMethod(ObjectClass, nme.ne, methOfAnyRef(BooleanType), Final) + @tu lazy val Object_eq: TermSymbol = { + // If explicit nulls is enabled, then we want to allow `(x: String).eq(null)`, so we need + // to adjust the signature of `eq` accordingly. + val tpe = if (ctx.explicitNulls) methOfAnyRefOrNull(BooleanType) else methOfAnyRef(BooleanType) + enterMethod(ObjectClass, nme.eq, tpe, Final) + } + @tu lazy val Object_ne: TermSymbol = { + // If explicit nulls is enabled, then we want to allow `(x: String).ne(null)`, so we need + // to adjust the signature of `ne` accordingly. + val tpe = if (ctx.explicitNulls) methOfAnyRefOrNull(BooleanType) else methOfAnyRef(BooleanType) + enterMethod(ObjectClass, nme.ne, tpe, Final) + } @tu lazy val Object_synchronized: TermSymbol = enterPolyMethod(ObjectClass, nme.synchronized_, 1, pt => MethodType(List(pt.paramRefs(0)), pt.paramRefs(0)), Final) @tu lazy val Object_clone: TermSymbol = enterMethod(ObjectClass, nme.clone_, MethodType(Nil, ObjectType), Protected) @@ -329,18 +339,41 @@ class Definitions { pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0))) /** Method representing a throw */ - @tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, - MethodType(List(ThrowableType), NothingType)) + @tu lazy val throwMethod: TermSymbol = { + val argTpe = if (ctx.explicitNulls) OrType(ThrowableType, NullType) else ThrowableType + enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(argTpe), NothingType)) + } @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) def NothingType: TypeRef = NothingClass.typeRef @tu lazy val RuntimeNothingModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Nothing") - @tu lazy val NullClass: ClassSymbol = enterCompleteClassSymbol( - ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef)) + @tu lazy val NullClass: ClassSymbol = { + val parents = List(if (ctx.explicitNulls) AnyType else ObjectType) + enterCompleteClassSymbol(ScalaPackageClass, tpnme.Null, AbstractFinal, parents) + } def NullType: TypeRef = NullClass.typeRef @tu lazy val RuntimeNullModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Null") + /** An alias for null values that originate in Java code. + * This type gets special treatment in the Typer. Specifically, `JavaNull` can be selected through: + * e.g. + * ``` + * // x: String|Null + * x.length // error: `Null` has no `length` field + * // x2: String|JavaNull + * x2.length // allowed by the Typer, but unsound (might throw NPE) + * ``` + */ + lazy val JavaNullAlias: TypeSymbol = { + assert(ctx.explicitNulls) + enterAliasType(tpnme.JavaNull, NullType) + } + def JavaNullAliasType: TypeRef = { + assert(ctx.explicitNulls) + JavaNullAlias.typeRef + } + @tu lazy val ImplicitScrutineeTypeSym = newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef @@ -507,12 +540,16 @@ class Definitions { @tu lazy val BoxedNumberClass: ClassSymbol = ctx.requiredClass("java.lang.Number") @tu lazy val ClassCastExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ClassCastException") @tu lazy val ClassCastExceptionClass_stringConstructor: TermSymbol = ClassCastExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { - case List(pt) => (pt isRef StringClass) + case List(pt) => + val pt1 = if (ctx.explicitNulls) pt.stripNull else pt + pt1 isRef StringClass case _ => false }).symbol.asTerm @tu lazy val ArithmeticExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ArithmeticException") @tu lazy val ArithmeticExceptionClass_stringConstructor: TermSymbol = ArithmeticExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { - case List(pt) => (pt isRef StringClass) + case List(pt) => + val pt1 = if (ctx.explicitNulls) pt.stripNull else pt + pt1 isRef StringClass case _ => false }).symbol.asTerm @@ -776,6 +813,7 @@ class Definitions { def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) def methOfAnyRef(tp: Type): MethodType = MethodType(List(ObjectType), tp) + def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(OrType(ObjectType, NullType)), tp) // Derived types @@ -935,10 +973,23 @@ class Definitions { name.length > prefix.length && name.drop(prefix.length).forall(_.isDigit)) - def isBottomClass(cls: Symbol): Boolean = + def isBottomClass(cls: Symbol): Boolean = { + if (ctx.explicitNulls && !ctx.phase.erasedTypes) cls == NothingClass + else isBottomClassAfterErasure(cls) + } + + def isBottomClassAfterErasure(cls: Symbol): Boolean = { cls == NothingClass || cls == NullClass - def isBottomType(tp: Type): Boolean = + } + + def isBottomType(tp: Type): Boolean = { + if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.derivesFrom(NothingClass) + else isBottomTypeAfterErasure(tp) + } + + def isBottomTypeAfterErasure(tp: Type): Boolean = { tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) + } /** Is a function class. * - FunctionXXL @@ -1032,9 +1083,12 @@ class Definitions { () => ScalaPackageVal.termRef ) - val PredefImportFns: List[() => TermRef] = List[() => TermRef]( + lazy val PredefImportFns: List[() => TermRef] = List[() => TermRef]( () => ScalaPredefModule.termRef, - () => DottyPredefModule.termRef + () => DottyPredefModule.termRef, + // TODO(abeln): is this in the right place? + // And is it ok to import this unconditionally? + () => ctx.requiredModuleRef("scala.ExplicitNullsOps") ) @tu lazy val RootImportFns: List[() => TermRef] = @@ -1281,18 +1335,22 @@ class Definitions { // ----- Initialization --------------------------------------------------- /** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ - @tu lazy val syntheticScalaClasses: List[TypeSymbol] = List( - AnyClass, - AnyRefAlias, - AnyKindClass, - andType, - orType, - RepeatedParamClass, - ByNameParamClass2x, - AnyValClass, - NullClass, - NothingClass, - SingletonClass) + @tu lazy val syntheticScalaClasses: List[TypeSymbol] = { + val synth = List( + AnyClass, + AnyRefAlias, + AnyKindClass, + andType, + orType, + RepeatedParamClass, + ByNameParamClass2x, + AnyValClass, + NullClass, + NothingClass, + SingletonClass) + + if (ctx.explicitNulls) synth :+ JavaNullAlias else synth + } @tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List( EmptyPackageVal, diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index a4aa72347ac2..8b160575f4b3 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -450,8 +450,8 @@ object Flags { * is completed) */ val AfterLoadFlags: FlagSet = commonFlags( - FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined) - + FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, + Enum, StableRealizable) // TODO: change to JavaEnumValue in future, blocked by possible bug in FlagSet union /** A value that's unstable unless complemented with a Stable flag */ val UnstableValueFlags: FlagSet = Mutable | Method diff --git a/compiler/src/dotty/tools/dotc/core/FlowTyper.scala b/compiler/src/dotty/tools/dotc/core/FlowTyper.scala new file mode 100644 index 000000000000..0d81efedc9e7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/FlowTyper.scala @@ -0,0 +1,242 @@ +package dotty.tools.dotc.core + +import dotty.tools.dotc.ast.tpd._ +import StdNames.nme +import dotty.tools.dotc.ast.Trees.{Apply, Block, If, Select, Ident} +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Types.{NonNullTermRef, TermRef, Type} + +import scala.annotation.internal.sharable + +/** Flow-sensitive typer */ +object FlowTyper { + + /** A set of `TermRef`s known to be non-null at the current program point */ + type FlowFacts = Set[TermRef] + + /** The initial state where no `TermRef`s are known to be non-null */ + @sharable val emptyFlowFacts = Set.empty[TermRef] + + /** Tries to improve the precision of `tpe` using flow-sensitive type information. + * For nullability, is `tpe` is a `TermRef` declared as nullable but known to be non-nullable because of the + * contextual info, returns the non-nullable version of the type. + * If the precision of the type can't be improved, then returns the type unchanged. + */ + def refineType(tpe: Type)(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + tpe match { + case tref: TermRef if ctx.flowFacts.contains(tref) => NonNullTermRef.apply(tref.prefix, tref.designator) + case _ => tpe + } + } + + /** Nullability facts inferred from a condition. + * @param ifTrue are the terms known to be non-null if the condition is true. + * @param ifFalse are the terms known to be non-null if the condition is false. + */ + case class Inferred(ifTrue: FlowFacts, ifFalse: FlowFacts) { + // Let `NN(e, true/false)` be the set of terms that are non-null if `e` evaluates to `true/false`. + // We can use De Morgan's laws to underapproximate `NN` via `Inferred`. + // e.g. say `e = e1 && e2`. Then if `e` is `false`, we know that either `!e1` or `!e2`. + // Let `t` be a term that is in both `NN(e1, false)` and `NN(e2, false)`. + // Then it follows that `t` must be in `NN(e, false)`. This means that if we set + // `Inferred(e1 && e2, false) = Inferred(e1, false) ∩ Inferred(e2, false)`, we'll have + // `Inferred(e1 && e2, false) ⊂ NN(e1 && e2, false)` (formally, we'd do a structural induction on `e`). + // This means that when we infer something we do so soundly. The methods below use this approach. + + /** If `this` corresponds to a condition `e1` and `other` to `e2`, calculate the inferred facts for `e1 && e2`. */ + def combineAnd(other: Inferred): Inferred = Inferred(ifTrue.union(other.ifTrue), ifFalse.intersect(other.ifFalse)) + + /** If `this` corresponds to a condition `e1` and `other` to `e2`, calculate the inferred facts for `e1 || e2`. */ + def combineOr(other: Inferred): Inferred = Inferred(ifTrue.intersect(other.ifTrue), ifFalse.union(other.ifFalse)) + + /** The inferred facts for the negation of this condition. */ + def negate: Inferred = Inferred(ifFalse, ifTrue) + } + + object Inferred { + /** Create a singleton inferred fact containing `tref`. */ + def apply(tref: TermRef, ifTrue: Boolean): Inferred = { + if (ifTrue) Inferred(Set(tref), emptyFlowFacts) + else Inferred(emptyFlowFacts, Set(tref)) + } + } + + /** Analyze the tree for a condition `cond` to learn new flow facts. + * Supports ands, ors, and unary negation. + * + * Example: + * (1) + * ``` + * val x: String|Null = "foo" + * if (x != null) { + * // x: String in the "then" branch + * } + * ``` + * Notice that `x` must be stable for the above to work. + * + * Let NN(cond, true/false) be the set of paths (`TermRef`s) that we can infer to be non-null + * if `cond` is true/false, respectively. Then define NN by (basically De Morgan's laws): + * + * NN(p == null, true) = {} we also handle `eq` + * NN(p == null, false) = {p} if p is stable + * NN(p != null, true) = {p} if p is stable we also handle `ne` + * NN(p != null, false) = {} + * NN(p.isInstanceOf[Null], true) = {} + * NN(p.isInstanceOf[Null], false) = {p} if p is stable + * NN(A && B, true) = ∪(NN(A, true), NN(B, true)) + * NN(A && B, false) = ∩(NN(A, false), NN(B, false)) + * NN(A || B, true) = ∩(NN(A, true), NN(B, true)) + * NN(A || B, false) = ∪(NN(A, false), NN(B, false)) + * NN(!A, true) = NN(A, false) + * NN(!A, false) = NN(A, true) + * NN({S1; ...; Sn, cond}, true/false) = NN(cond, true/false) + * NN(cond, _) = {} otherwise + */ + def inferFromCond(cond: Tree)(implicit ctx: Context): Inferred = { + assert(ctx.explicitNulls) + /** Combine two sets of facts according to `op`. */ + def combine(lhs: Inferred, op: Name, rhs: Inferred): Inferred = { + op match { + case _ if op == nme.ZAND => lhs.combineAnd(rhs) + case _ if op == nme.ZOR => lhs.combineOr(rhs) + } + } + + val emptyFacts = Inferred(emptyFlowFacts, emptyFlowFacts) + val nullLit = tpd.Literal(Constant(null)) + + /** Recurse over a conditional to extract flow facts. */ + def recur(tree: Tree): Inferred = { + tree match { + // `==` and `!=` are methods in `Any`, so `x == y` gets desugared to + // `x.==(y)` + case Apply(Select(lhs, op), List(rhs)) => + if (op == nme.ZAND || op == nme.ZOR) combine(recur(lhs), op, recur(rhs)) + else if (op == nme.EQ || op == nme.NE) newFact(lhs, isEq = (op == nme.EQ), rhs) + else emptyFacts + // `eq` and `ne` are extension methods if the receiver is a nullable union, + // so `(x: String|Null) eq null` gets desugared to `eq(x)(null)`. + case Apply(Apply(Ident(op), List(lhs)), List(rhs)) => + if (op == nme.eq || op == nme.ne) newFact(lhs, isEq = (op == nme.eq), rhs) + else emptyFacts + // TODO(abeln): handle type test with argument that's not a subtype of `Null`. + // We could infer "non-null" in that case: e.g. `if (x.isInstanceOf[String]) { // x can't be null }` + // case TypeApply(Select(lhs, op), List(tArg)) if op == nme.isInstanceOf_ && tArg.tpe.isNullType => + // newFact(lhs, isEq = true, nullLit) + case Select(lhs, op) if op == nme.UNARY_! => recur(lhs).negate + case Block(_, expr) => recur(expr) + case inline: Inlined => recur(inline.expansion) + case typed: Typed => recur(typed.expr) // TODO(abeln): check that the type is `Boolean`? + case _ => emptyFacts + } + } + + /** Extract new facts from an expression `lhs = rhs` or `lhs != rhs` + * if either the lhs or rhs is the `null` literal. + */ + def newFact(lhs: Tree, isEq: Boolean, rhs: Tree): Inferred = { + def isNullLit(tree: Tree): Boolean = tree match { + case lit: Literal if lit.const.tag == Constants.NullTag => true + case _ => false + } + + def isStableTermRef(tree: Tree): Boolean = asStableTermRef(tree).isDefined + + def asStableTermRef(tree: Tree): Option[TermRef] = tree.tpe match { + case tref: TermRef if tref.isStable => Some(tref) + case _ => None + } + + val trefOpt = + if (isNullLit(lhs) && isStableTermRef(rhs)) asStableTermRef(rhs) + else if (isStableTermRef(lhs) && isNullLit(rhs)) asStableTermRef(lhs) + else None + + trefOpt match { + case Some(tref) => + // If `isEq`, then the condition is of the form `lhs == null`, + // in which case we know `lhs` is non-null if the condition is false. + Inferred(tref, ifTrue = !isEq) + case _ => emptyFacts + } + } + + recur(cond) + } + + /** Infer flow-sensitive type information inside a condition. + * + * Specifically, if `cond` is of the form `lhs &&` or `lhs ||`, where the lhs has already been typed + * (and the rhs hasn't been typed yet), compute the non-null facts that must hold so that the rhs can + * execute. These facts can then be soundly assumed when typing the rhs, because boolean operators are + * short-circuiting. + * + * This is useful in e.g. + * ``` + * val x: String|Null = ??? + * if (x != null && x.length > 0) ... + * ``` + */ + def inferWithinCond(cond: Tree)(implicit ctx: Context): FlowFacts = { + assert(ctx.explicitNulls) + cond match { + case Select(lhs, op) if op == nme.ZAND || op == nme.ZOR => + val Inferred(ifTrue, ifFalse) = inferFromCond(lhs) + if (op == nme.ZAND) ifTrue + else ifFalse + case _ => emptyFlowFacts + } + } + + /** Infer flow-sensitive type information within a block. + * + * More precisely, if `s1; s2` are consecutive statements in a block, this returns + * a context with nullability facts that hold once `s1` has executed. + * The new facts can then be used to type `s2`. + * + * This is useful for e.g. + * ``` + * val x: String|Null = ??? + * if (x == null) return "foo" + * val y = x.length // x: String inferred + * ``` + * + * How can we obtain additional facts just from the fact that `s1` executed? + * This can happen if `s1` is of the form `If(cond, then, else)`, where `then` or + * `else` have non-local control flow. + * + * The following qualify as non-local: + * 1) a return + * 2) an expression of type `Nothing` (in particular, usages of `throw`) + * 3) a block where the last expression is non-local + * + * So, for example, if we know that `x` must be non-null if `cond` is true, and `else` is non-local, + * then in order for `s2` to execute `cond` must be true. We can thus soundly add `x` to our + * flow facts. + */ + def inferWithinBlock(stat: Tree)(implicit ctx: Context): FlowFacts = { + def isNonLocal(s: Tree): Boolean = s match { + case _: Return => true + case Block(_, expr) => isNonLocal(expr) + case _ => + // If the type is bottom (like the result of a `throw`), then we assume the statement + // won't finish executing. + s.tpe.isBottomType + } + + assert(ctx.explicitNulls) + stat match { + case If(cond, thenExpr, elseExpr) => + val Inferred(ifTrue, ifFalse) = inferFromCond(cond) + if (isNonLocal(thenExpr) && isNonLocal(elseExpr)) ifTrue ++ ifFalse + else if (isNonLocal(thenExpr)) ifFalse + else if (isNonLocal(elseExpr)) ifTrue + else emptyFlowFacts + case _ => emptyFlowFacts + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala new file mode 100644 index 000000000000..1a02e596f40a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -0,0 +1,186 @@ +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.{Symbol, defn, _} +import dotty.tools.dotc.core.Types.{AndType, AppliedType, LambdaType, MethodType, OrType, PolyType, Type, TypeAlias, TypeMap, TypeParamRef, TypeRef} +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") + + // A list of "policies" that special-case certain members. + // The policies should be disjoint: we use the first one that is applicable. + val whitelist: Seq[NullifyPolicy] = Seq( + // The `TYPE` field in every class: don't nullify. + NoOpPolicy(_.name == nme.TYPE_), + // The `toString` method: don't nullify the return type. + paramsOnlyPolicy(_.name == nme.toString_), + // Constructors: params are nullified, but the result type isn't. + paramsOnlyPolicy(_.isConstructor), + // Java enum instances: don't nullify. + NoOpPolicy(_.isAllOf(Flags.JavaEnumValue)) + ) + + whitelist.find(_.isApplicable(sym)) match { + case Some(pol) => pol(tp) + case None => nullifyType(tp) // default case: nullify everything + } + } + + /** A policy that special cases the handling of some symbol. */ + private sealed trait NullifyPolicy { + /** Whether the policy applies to `sym`. */ + def isApplicable(sym: Symbol): Boolean + /** Nullifies `tp` according to the policy. Should call `isApplicable` first. */ + def apply(tp: Type): Type + } + + /** A policy that leaves the passed-in type unchanged. */ + private case class NoOpPolicy(trigger: Symbol => Boolean) extends NullifyPolicy { + override def isApplicable(sym: Symbol): Boolean = trigger(sym) + + override def apply(tp: Type): Type = tp + } + + /** A policy for handling a method or poly. + * @param trigger determines whether the policy applies to a given symbol. + * @param nnParams the indices of the method parameters that should be considered "non-null" (should not be nullified). + * @param nnRes whether the result type should be nullified. + * + * For the purposes of both `nnParams` and `nnRes`, when a parameter or return type is not nullified, + * this applies only at the top level. e.g. suppose we have a Java result type `Array[String]` and `nnRes` is set. + * Scala will see `Array[String|JavaNull]`; the array element type is still nullified. + */ + private case class MethodPolicy(trigger: Symbol => Boolean, + nnParams: Seq[Int], + nnRes: Boolean)(implicit ctx: Context) extends TypeMap with NullifyPolicy { + override def isApplicable(sym: Symbol): Boolean = trigger(sym) + + private def spare(tp: Type): Type = { + nullifyType(tp).stripNull + } + + override def apply(tp: Type): Type = { + tp match { + case ptp: PolyType => + derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) + case mtp: MethodType => + val paramTpes = mtp.paramInfos.zipWithIndex.map { + case (paramInfo, index) => + // TODO(abeln): the sequence lookup can be optimized, because the indices + // in it appear in increasing order. + if (nnParams.contains(index)) spare(paramInfo) else nullifyType(paramInfo) + } + val resTpe = if (nnRes) spare(mtp.resType) else nullifyType(mtp.resType) + derivedLambdaType(mtp)(paramTpes, resTpe) + } + } + } + + /** A policy that nullifies only method parameters (but not result types). */ + private def paramsOnlyPolicy(trigger: Symbol => Boolean)(implicit ctx: Context): MethodPolicy = { + MethodPolicy(trigger, nnParams = Seq.empty, nnRes = true) + } + + /** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ + private def nullifyType(tpe: Type)(implicit ctx: Context): Type = { + val nullMap = new JavaNullMap(alreadyNullable = false) + nullMap(tpe) + } + + /** A type map that adds `| JavaNull`. + * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). + * This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`, + * instead of `(A|JavaNull | B|JavaNull) | JavaNull`. + */ + private class JavaNullMap(var alreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { + /** Should we nullify `tp` at the outermost level? */ + def needsTopLevelNull(tp: Type): Boolean = { + !alreadyNullable && (tp match { + case tp: TypeRef => + // We don't modify value types because they're non-nullable even in Java. + // We don't modify `Any` because it's already nullable. + !tp.symbol.isValueClass && !tp.isRef(defn.AnyClass) + case _ => true + }) + } + + /** Should we nullify the type arguments to the given generic `tp`? + * We only nullify the inside of Scala-defined generics. + * This is because Java classes are _all_ nullified, so both `java.util.List[String]` and + * `java.util.List[String|Null]` contain nullable elements. + */ + def needsNullArgs(tp: AppliedType): Boolean = { + !tp.classSymbol.is(JavaDefined) + } + + override def apply(tp: Type): Type = { + tp match { + case tp: TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion + case appTp @ AppliedType(tycons, targs) => + val targs2 = if (needsNullArgs(appTp)) targs map this else targs + derivedAppliedType(appTp, tycons, targs2).toJavaNullableUnion + case tp: LambdaType => mapOver(tp) + case tp: TypeAlias => mapOver(tp) + case tp @ AndType(tp1, tp2) => + // 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`. + alreadyNullable = true + derivedAndType(tp, this(tp1), this(tp2)).toJavaNullableUnion + case tp @ OrType(tp1, tp2) if !tp.isJavaNullableUnion => + alreadyNullable = true + derivedOrType(tp, this(tp1), this(tp2)).toJavaNullableUnion + case tp: TypeParamRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion + case _ => tp + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala new file mode 100644 index 000000000000..e7764ae61250 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -0,0 +1,147 @@ +package dotty.tools.dotc.core + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Types.{AndType, ClassInfo, ConstantType, OrType, Type, TypeBounds, TypeMap, TypeProxy} + +/** Defines operations on nullable types. */ +object NullOpsDecorator { + + implicit class NullOps(val self: Type) { + /** Is this type a reference to `Null`, possibly after aliasing? */ + def isNullType(implicit ctx: Context): Boolean = self.isRef(defn.NullClass) + + /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ + def isNullableUnion(implicit ctx: Context): Boolean = { + assert(ctx.explicitNulls) + self.widenDealias.normNullableUnion match { + case OrType(_, right) => right.isNullType + case _ => false + } + } + + /** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */ + def isJavaNullType(implicit ctx: Context): Boolean = { + assert(ctx.explicitNulls) + // We can't do `self == defn.JavaNull` because when trees are unpickled new references + // to `JavaNull` could be created that are different from `defn.JavaNull`. + // Instead, we compare the symbol. + self.isDirectRef(defn.JavaNullAlias) + } + + /** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */ + def isJavaNullableUnion(implicit ctx: Context): Boolean = { + assert(ctx.explicitNulls) + def peelOff(tpe: Type): Boolean = tpe match { + case OrType(left, right) => + right.isJavaNullType || right.isNullType && peelOff(left) + case _ => false + } + val norm = self.widenDealias.normNullableUnion + // We can't just look at the r.h.s of the normalized union. + // The problem is that after normalizing we could get a type like `(String | JavaNull) | Null`, + // where the r.h.s of the union isn't a `JavaNull`, but if we keep peeling nulls off starting + // from the right we'll eventually get to the `JavaNull`. + peelOff(norm) + } + + /** Is this type guaranteed not to have `null` as a value? */ + final def isNotNull(implicit ctx: Context): Boolean = self match { + case tp: ConstantType => tp.value.value != null + case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass + case tp: TypeBounds => tp.lo.isNotNull + case tp: TypeProxy => tp.underlying.isNotNull + case AndType(tp1, tp2) => tp1.isNotNull || tp2.isNotNull + case OrType(tp1, tp2) => tp1.isNotNull && tp2.isNotNull + case _ => false + } + + /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of all other types. + * In the process, it also flattens the type so that there are no nested unions at the top level. + * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null | Null` + */ + def normNullableUnion(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + def split(tp: Type, nonNull: List[Type], nll: List[Type]): (List[Type], List[Type]) = { + tp match { + case OrType(left, right) => + // Recurse on the right first so we get the types in pre-order. + val (nonNull1, nll1) = split(right, nonNull, nll) + split(left, nonNull1, nll1) + case _ => + if (tp.isNullType) (nonNull, tp :: nll) else (tp :: nonNull, nll) + } + } + val (nonNull, nll) = split(self, Nil, Nil) + val all = nonNull ++ nll + assert(all.nonEmpty) + all.tail.foldLeft(all.head)(OrType.apply) + } + + /** Strips `Null` from type unions in this type. + * @param onlyJavaNull if true, we delete only `JavaNull` and not vanilla `Null`. + */ + private def stripNullImpl(onlyJavaNull: Boolean)(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + def strip(tp: Type, changed: Boolean): (Type, Boolean) = { + tp match { + case OrType(left, right) if right.isNullType => + if (!onlyJavaNull || right.isJavaNullType) strip(left, changed = true) + else { + val (tp1, changed1) = strip(left, changed) + (OrType(tp1, right), changed1) + } + case _ => (tp, changed) + } + } + // If there are no `Null`s to strip off, try to keep the method a no-op + // by keeping track of whether the result has changed. + // Otherwise, we would widen and dealias as a side effect. + val (tp, diff) = strip(self.widenDealias.normNullableUnion, changed = false) + if (diff) tp else self + } + + /** Syntactically strips the nullability from this type. If the normalized form (as per `normNullableUnion`) + * of this type is `T1 | ... | Tk | ... | Tn`, and all types in the range `Tk ... Tn` are references to `Null`, + * then return `T1 | ... | Tk-1`. + * If this type isn't (syntactically) nullable, then returns the type unchanged. + */ + def stripNull(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + stripNullImpl(onlyJavaNull = false) + } + + /** Like `stripNull`, but removes only the `JavaNull`s. */ + def stripJavaNull(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + stripNullImpl(onlyJavaNull = true) + } + + /** Collapses all `JavaNull` unions within this type, and not just the outermost ones (as `stripJavaNull` does). + * e.g. (Array[String|Null]|Null).stripNull => Array[String|Null] + * (Array[String|Null]|Null).stripInnerNulls => Array[String] + * If no `JavaNull` unions are found within the type, then returns the input type unchanged. + */ + def stripAllJavaNull(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + var diff = false + object RemoveNulls extends TypeMap { + override def apply(tp: Type): Type = tp match { + case tp: OrType if tp.isJavaNullableUnion => + diff = true + mapOver(tp.stripJavaNull) + case _ => mapOver(tp) + } + } + val rem = RemoveNulls(self.widenDealias) + if (diff) rem else self + } + + /** Injects this type into a union with `JavaNull`. */ + def toJavaNullableUnion(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + if (self.isJavaNullableUnion) self + else OrType(self, defn.JavaNullAliasType) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 0ba484b3e248..d14a28a8c8e5 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -196,6 +196,7 @@ object StdNames { final val Mirror: N = "Mirror" final val Nothing: N = "Nothing" final val Null: N = "Null" + final val JavaNull: N = "JavaNull" final val Object: N = "Object" final val Product: N = "Product" final val PartialFunction: N = "PartialFunction" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 9563015a09f3..a48427fd9277 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -785,9 +785,19 @@ object SymDenotations { // after Erasure and to avoid cyclic references caused by forcing denotations } - /** Is this symbol a class references to which that are supertypes of null? */ - final def isNullableClass(implicit ctx: Context): Boolean = + /** Is this symbol a class of which `null` is a value? */ + final def isNullableClass(implicit ctx: Context): Boolean = { + if (ctx.explicitNulls && !ctx.phase.erasedTypes) symbol == defn.NullClass || symbol == defn.AnyClass + else isNullableClassAfterErasure + } + + /** Is this symbol a class of which `null` is a value after erasure? + * For example, if `-Yexplicit-nulls` is set, `String` is not nullable before erasure, + * but it becomes nullable after erasure. + */ + final def isNullableClassAfterErasure(implicit ctx: Context): Boolean = { isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass + } /** Is this definition accessible as a member of tree with type `pre`? * @param pre The type of the tree from which the selection is made diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 70ecc04a6e22..7be49443a2d4 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -283,8 +283,8 @@ object TypeErasure { // We need to short-circuit this case here because the regular lub logic below // relies on the class hierarchy, which doesn't properly capture `Null`s subtyping // behaviour. - if (defn.isBottomType(tp1) && tp2.derivesFrom(defn.ObjectClass)) return tp2 - if (defn.isBottomType(tp2) && tp1.derivesFrom(defn.ObjectClass)) return tp1 + if (defn.isBottomTypeAfterErasure(tp1) && tp2.derivesFrom(defn.ObjectClass)) return tp2 + if (defn.isBottomTypeAfterErasure(tp2) && tp1.derivesFrom(defn.ObjectClass)) return tp1 tp1 match { case JavaArrayType(elem1) => import dotty.tools.dotc.transform.TypeUtils._ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d1aaf780ccdd..5651e6a7d7a5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -7,6 +7,7 @@ import Symbols._ import Flags._ import Names._ import StdNames._, NameOps._ +import NullOpsDecorator._ import NameKinds.SkolemName import Scopes._ import Constants._ @@ -272,17 +273,6 @@ object Types { loop(this) } - /** Is this type guaranteed not to have `null` as a value? */ - final def isNotNull(implicit ctx: Context): Boolean = this match { - case tp: ConstantType => tp.value.value != null - case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass - case tp: TypeBounds => tp.lo.isNotNull - case tp: TypeProxy => tp.underlying.isNotNull - case AndType(tp1, tp2) => tp1.isNotNull || tp2.isNotNull - case OrType(tp1, tp2) => tp1.isNotNull && tp2.isNotNull - case _ => false - } - /** Is this type produced as a repair for an error? */ final def isError(implicit ctx: Context): Boolean = stripTypeVar.isInstanceOf[ErrorType] @@ -432,7 +422,14 @@ object Types { else if (rsym isSubClass lsym) rsym else NoSymbol case tp: OrType => - tp.join.classSymbol + if (ctx.explicitNulls && this.isNullableUnion) { + val OrType(left, _) = this.normNullableUnion + // If `left` is a reference type, then the class LUB of `left | Null` is `Any`. + // This is another one-of case that keeps this method sound, but not complete. + if (left.classSymbol isSubClass defn.ObjectClass) defn.AnyClass + else NoSymbol + } + else tp.join.classSymbol case _ => NoSymbol } @@ -598,11 +595,19 @@ object Types { case AndType(l, r) => goAnd(l, r) case tp: OrType => - // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` - // achieved that by narrowing `pre` to each alternative, but it led to merge errors in - // lots of places. The present strategy is instead of widen `tp` using `join` to be a - // supertype of `pre`. - go(tp.join) + if (ctx.explicitNulls && tp.isJavaNullableUnion) { + // Selecting `name` from a type `T|JavaNull` is like selecting `name` from `T`. + // This can throw at runtime, but we trade soundness for usability. + // We need to strip `JavaNull` from both the type and the prefix so that + // `pre <: tp` continues to hold. + tp.stripJavaNull.findMember(name, pre.stripJavaNull, required, excluded) + } else { + // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` + // achieved that by narrowing `pre` to each alternative, but it led to merge errors in + // lots of places. The present strategy is instead of widen `tp` using `join` to be a + // supertype of `pre`. + go(tp.join) + } case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, required, excluded) case err: ErrorType => @@ -1067,12 +1072,21 @@ object Types { * * is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]` * instead of `ArrayBuffer[? >: Int | A <: Int & A]` + * + * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), + * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ def widenUnion(implicit ctx: Context): Type = widen match { - case OrType(tp1, tp2) => - ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { - case union: OrType => union.join - case res => res + case tp @ OrType(tp1, tp2) => + if (ctx.explicitNulls && tp.isNullableUnion) { + // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + val OrType(leftTpe, nullTpe) = tp.normNullableUnion + OrType(leftTpe.widenUnion, nullTpe) + } else { + ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { + case union: OrType => union.join + case res => res + } } case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnion, tp2.widenUnion) @@ -1795,6 +1809,9 @@ object Types { def isType: Boolean = isInstanceOf[TypeRef] def isTerm: Boolean = isInstanceOf[TermRef] + /** Is this type known to be non-null from flow typing (only applicable to TermRefs) ? */ + def isNonNull: Boolean = isInstanceOf[NonNullTermRef] + /** If designator is a name, this name. Otherwise, the original name * of the designator symbol. */ @@ -1890,15 +1907,21 @@ object Types { private def computeDenot(implicit ctx: Context): Denotation = { - def finish(d: Denotation) = { - if (d.exists) + def finish(d: Denotation): Denotation = { + if (d.exists) { + val d1 = if (ctx.explicitNulls && isNonNull) { + // If the denotation is computed for the first time, or if it's ever updated, make sure + // that the `info` is non-null. + d.mapInfo(_.stripNull) + } else d // Avoid storing NoDenotations in the cache - we will not be able to recover from // them. The situation might arise that a type has NoDenotation in some later // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type // is undefined after erasure.) We need to be able to do time travel back and // forth also in these cases. - setDenot(d) - d + setDenot(d1) + d1 + } else d } def fromDesignator = designator match { @@ -2199,7 +2222,7 @@ object Types { /** A reference like this one, but with the given symbol, if it exists */ final def withSym(sym: Symbol)(implicit ctx: Context): ThisType = - if ((designator ne sym) && sym.exists) NamedType(prefix, sym).asInstanceOf[ThisType] + if ((designator ne sym) && sym.exists) namedType(prefix, sym, isNonNull).asInstanceOf[ThisType] else this /** A reference like this one, but with the given denotation, if it exists. @@ -2246,10 +2269,10 @@ object Types { d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod else lastSymbol.asSeenFrom(prefix).signature) - NamedType(prefix, name, d) + namedType(prefix, name, d, isNonNull) } if (prefix eq this.prefix) this - else if (lastDenotation == null) NamedType(prefix, designator) + else if (lastDenotation == null) namedType(prefix, designator, isNonNull) else designator match { case sym: Symbol => if (infoDependsOnPrefix(sym, prefix) && !prefix.isArgPrefixOf(sym)) { @@ -2258,10 +2281,10 @@ object Types { // A false override happens if we rebind an inner class to another type with the same name // in an outer subclass. This is wrong, since classes do not override. We need to // return a type with the existing class info as seen from the new prefix instead. - if (falseOverride) NamedType(prefix, sym.name, denot.asSeenFrom(prefix)) + if (falseOverride) namedType(prefix, sym.name, denot.asSeenFrom(prefix), isNonNull) else candidate } - else NamedType(prefix, sym) + else namedType(prefix, sym, isNonNull) case name: Name => reload() } } @@ -2284,6 +2307,18 @@ object Types { } override def eql(that: Type): Boolean = this eq that // safe because named types are hash-consed separately + + /** Like `NamedType.apply`, but can create `NonNullTermRef`s. */ + private def namedType(prefix: Type, designator: Designator, isNonNull: Boolean)(implicit ctx: Context): NamedType = { + if (ctx.explicitNulls && isNonNull) NonNullTermRef.apply(prefix, designator) + else NamedType.apply(prefix, designator) + } + + /** Like `NamedType.apply`, but can create `NonNullTermRef`s. */ + private def namedType(prefix: Type, designator: Name, denot: Denotation, isNonNull: Boolean)(implicit ctx: Context): NamedType = { + if (ctx.explicitNulls && isNonNull) NonNullTermRef.apply(prefix, designator.asTermName, denot) + else NamedType.apply(prefix, designator, denot) + } } /** A reference to an implicit definition. This can be either a TermRef or a @@ -2300,7 +2335,7 @@ object Types { private var myDesignator: Designator) extends NamedType with SingletonType with ImplicitRef { - type ThisType = TermRef + type ThisType >: this.type <: TermRef type ThisName = TermName override def designator: Designator = myDesignator @@ -2355,6 +2390,21 @@ object Types { myHash = hc } + /** A `TermRef` that, through flow-sensitive type inference, we know is non-null. + * Accordingly, the `info` in its denotation won't be of the form `T|Null`. + * + * This class is cached differently from regular `TermRef`s. Regular `TermRef`s use the + * `uniqueNameTypes` map in the context, while these non-null `TermRef`s use + * the generic `uniques` map. This is so that regular `TermRef`s can continue to use + * a "fast path", since non-null `TermRef`s are not very common. + */ + final class NonNullTermRef(prefix: Type, designator: Designator) extends TermRef(prefix, designator) { + override type ThisType = NonNullTermRef + + // This class has no custom members: it's just used as a marker (via `isInstanceOf`) within + // `NamedType` to identify `TermRef`s that are non-nullable. + } + final class CachedTypeRef(prefix: Type, designator: Designator, hc: Int) extends TypeRef(prefix, designator) { assert((prefix ne NoPrefix) || designator.isInstanceOf[Symbol]) myHash = hc @@ -2386,9 +2436,11 @@ object Types { case sym: Symbol => sym.isType case name: Name => name.isTypeName } + def apply(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType = if (isType(designator)) TypeRef.apply(prefix, designator) else TermRef.apply(prefix, designator) + def apply(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType = if (designator.isTermName) TermRef.apply(prefix, designator.asTermName, denot) else TypeRef.apply(prefix, designator.asTypeName, denot) @@ -2407,6 +2459,23 @@ object Types { apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) } + object NonNullTermRef { + // Notice these TermRefs are cached in a different map than the one used for + // regular TermRefs. The non-null TermRefs use the "slow" map, since they're less common. + // If we used the same map, then we'd end up replacing a regular TermRef by a non-null + // one with a different denotation. + + /** Create a non-null term ref with the given designator. */ + def apply(prefix: Type, desig: Designator)(implicit ctx: Context): NonNullTermRef = + unique(new NonNullTermRef(prefix, desig)) + + /** Create a non-null term ref with given initial denotation. The name of the reference is taken + * from the denotation's symbol if the latter exists, or else it is the given name. + */ + def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): NonNullTermRef = + apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot) + } + object TypeRef { /** Create a type ref with given prefix and name */ diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 16c73f5df420..7eb6ae7cd054 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -287,6 +287,8 @@ class ClassfileParser( if (denot.is(Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0) denot.info = arrayToRepeated(denot.info) + if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info) + // seal java enums if (isEnum) { val enumClass = sym.owner.linkedClass diff --git a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala index 95e5e551d0d6..faeb7d83dbc2 100644 --- a/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala +++ b/compiler/src/dotty/tools/dotc/transform/CollectNullableFields.scala @@ -34,7 +34,7 @@ object CollectNullableFields { * - belongs to a non trait-class * - is private[this] * - is not lazy - * - its type is nullable + * - its type is nullable after erasure * - is only used in a lazy val initializer * - defined in the same class as the lazy val */ @@ -65,7 +65,9 @@ class CollectNullableFields extends MiniPhase { !sym.is(Lazy) && !sym.owner.is(Trait) && sym.initial.isAllOf(PrivateLocal) && - sym.info.widenDealias.typeSymbol.isNullableClass + // We need `isNullableClassAfterErasure` and not `isNullable` because + // we care about the values as present in the JVM. + sym.info.widenDealias.typeSymbol.isNullableClassAfterErasure if (isNullablePrivateField) nullability.get(sym) match { diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 2fc953cfdad2..3a2e4ca190e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -17,6 +17,7 @@ import DenotTransformers._ import NameOps._ import NameKinds.OuterSelectName import StdNames._ +import NullOpsDecorator._ object FirstTransform { val name: String = "firstTransform" @@ -50,10 +51,26 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => + val qualTpe = if (ctx.explicitNulls) { + // `JavaNull` is already special-cased in the Typer, but needs to be handled here as well. + // We need `stripAllJavaNull` and not `stripJavaNull` because of the following case: + // + // val s: (String|JavaNull)&(String|JavaNull) = "hello" + // val l = s.length + // + // The invariant below is that the type of `s`, which isn't a top-level JavaNull union, + // must derive from the type of the owner of `length`, which is `String`. Because we don't + // know which `JavaNull`s were used to find the `length` member, we conservatively remove + // all of them. + // TODO(abeln): is it too expensive to call `stripAllJavaNull` for all selections? + qual.tpe.stripAllJavaNull + } else { + qual.tpe + } assert( - qual.tpe.derivesFrom(tree.symbol.owner) || - tree.symbol.is(JavaStatic) && qual.tpe.derivesFrom(tree.symbol.enclosingClass), - i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe} in $tree") + qualTpe.derivesFrom(tree.symbol.owner) || + tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass), + i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree") case _: TypeTree => case _: Import | _: NamedArg | _: TypTree => assert(false, i"illegal tree: $tree") diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 2802bae99fb3..8dc774bcbc94 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -16,7 +16,7 @@ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.{Names, StdNames} import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.transform.SymUtils._ - +import dotty.tools.dotc.core.NullOpsDecorator._ import scala.collection.mutable class LazyVals extends MiniPhase with IdentityDenotTransformer { diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 589a9c523120..1539f6021e90 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -17,6 +17,7 @@ import config.Printers.patmatch import reporting.diagnostic.messages._ import dotty.tools.dotc.ast._ import util.Property._ +import NullOpsDecorator._ /** The pattern matching transform. * After this phase, the only Match nodes remaining in the code are simple switches diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 81a980167303..df05e05b602a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -14,6 +14,7 @@ import ValueClasses.isDerivedValueClass import SymUtils._ import util.Property import config.Printers.derive +import NullOpsDecorator._ object SyntheticMembers { @@ -188,7 +189,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) { val ioob = defn.IndexOutOfBoundsException.typeRef // Second constructor of ioob that takes a String argument def filterStringConstructor(s: Symbol): Boolean = s.info match { - case m: MethodType if s.isConstructor => m.paramInfos == List(defn.StringType) + case m: MethodType if s.isConstructor && m.paramInfos.size == 1 => + val pinfo = if (ctx.explicitNulls) m.paramInfos.head.stripJavaNull else m.paramInfos.head + pinfo == defn.StringType case _ => false } val constructor = ioob.typeSymbol.info.decls.find(filterStringConstructor _).asTerm diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 4b31f5ae28cf..7099c0b26292 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -14,6 +14,7 @@ import util.Spans._ import reporting.diagnostic.messages.TypeTestAlwaysSucceeds import reporting.trace import config.Printers.{ transforms => debug } +import NullOpsDecorator._ /** This transform normalizes type tests and type casts, * also replacing type tests with singleton argument type with reference equality check diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index e86cd9f172bf..990e1e48706a 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -22,6 +22,7 @@ import reporting.diagnostic.messages._ import reporting.trace import config.Printers.{exhaustivity => debug} import util.SourcePosition +import NullOpsDecorator._ /** Space logic for checking exhaustivity and unreachability of pattern matching * @@ -293,14 +294,28 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { private val scalaNilType = ctx.requiredModuleRef("scala.collection.immutable.Nil") private val scalaConsType = ctx.requiredClassRef("scala.collection.immutable.::") - private val nullType = ConstantType(Constant(null)) - private val nullSpace = Typ(nullType) + private val constantNullType = ConstantType(Constant(null)) + private val constantNullSpace = Typ(constantNullType) - override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Space = trace(s"atomic intersection: ${AndType(tp1, tp2).show}", debug) { - // Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1) + /** Does the given tree stand for the literal `null`? */ + def isNullLit(tree: Tree): Boolean = tree match { + case Literal(Constant(null)) => true + case _ => false + } - // Since projections of types don't include null, intersection with null is empty. - if (tp1 == nullType || tp2 == nullType) Empty + /** Does the given space contain just the value `null`? */ + def isNullSpace(space: Space): Boolean = space match { + case Typ(tpe, _) => tpe =:= constantNullType || tpe.isNullType + case Or(spaces) => spaces.forall(isNullSpace) + case _ => false + } + + override def intersectUnrelatedAtomicTypes(tp1: Type, tp2: Type): Space = trace(s"atomic intersection: ${AndType(tp1, tp2).show}", debug) { + // Precondition: !isSubType(tp1, tp2) && !isSubType(tp2, tp1). + if (!ctx.explicitNulls && (tp1.isNullType || tp2.isNullType)) { + // Since projections of types don't include null, intersection with null is empty. + return Empty + } else { val res = ctx.typeComparer.disjoint(tp1, tp2) @@ -320,7 +335,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { Typ(ConstantType(c), false) case pat: Ident if isBackquoted(pat) => Typ(pat.tpe, false) case Ident(nme.WILDCARD) => - Or(Typ(pat.tpe.stripAnnots, false) :: nullSpace :: Nil) + Or(Typ(pat.tpe.stripAnnots, false) :: constantNullSpace :: Nil) case Ident(_) | Select(_, _) => Typ(erase(pat.tpe.stripAnnots), false) case Alternative(trees) => Or(trees.map(project(_))) @@ -437,7 +452,11 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = { debug.println(TypeComparer.explained(tp1 <:< tp2)) - val res = (tp1 != nullType || tp2 == nullType) && tp1 <:< tp2 + val res = if (ctx.explicitNulls) { + tp1 <:< tp2 + } else { + (tp1 != constantNullType || tp2 == constantNullType) && tp1 <:< tp2 + } res } @@ -661,7 +680,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def doShow(s: Space, mergeList: Boolean = false): String = s match { case Empty => "" - case Typ(c: ConstantType, _) => "" + c.value.value + case Typ(c: ConstantType, _) => + val v = c.value.value + if (v == null) "null" else "" + v case Typ(tp: TermRef, _) => tp.symbol.showName case Typ(tp, decomposed) => val sym = tp.widen.classSymbol @@ -763,10 +784,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (!redundancyCheckable(sel)) return val targetSpace = - if (selTyp.classSymbol.isPrimitiveValueClass) - Typ(selTyp, true) - else - Or(Typ(selTyp, true) :: nullSpace :: Nil) + if (ctx.explicitNulls) Typ(selTyp, true) + else if (selTyp.classSymbol.isPrimitiveValueClass) Typ(selTyp, true) + else Or(Typ(selTyp, true) :: constantNullSpace :: Nil) // in redundancy check, take guard as false in order to soundly approximate def projectPrevCases(cases: List[CaseDef]): Space = @@ -775,11 +795,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { else Empty }.reduce((a, b) => Or(List(a, b))) - def isNull(tree: Tree): Boolean = tree match { - case Literal(Constant(null)) => true - case _ => false - } - (1 until cases.length).foreach { i => val prevs = projectPrevCases(cases.take(i)) @@ -796,16 +811,19 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { // `covered == Empty` may happen for primitive types with auto-conversion // see tests/patmat/reader.scala tests/patmat/byte.scala - if (covered == Empty) covered = curr + if (covered == Empty && !isNullLit(pat)) covered = curr if (isSubspace(covered, prevs)) { ctx.warning(MatchCaseUnreachable(), pat.sourcePos) } // if last case is `_` and only matches `null`, produce a warning - if (i == cases.length - 1 && !isNull(pat) ) { - simplify(minus(covered, prevs)) match { - case Typ(`nullType`, _) => + // If explicit nulls are enabled, this check isn't needed because most of the cases + // that would trigger it would also trigger unreachability warnings. + if (!ctx.explicitNulls && i == cases.length - 1 && !isNullLit(pat) ) { + val simpl = simplify(minus(covered, prevs)) + simpl match { + case Typ(`constantNullType`, _) => ctx.warning(MatchCaseOnlyNullWarning(), pat.sourcePos) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ec4c9543d7b9..912e7fe234f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -857,8 +857,19 @@ trait Applications extends Compatibility { typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") /** Type application where arguments come from prototype, and no implicits are inserted */ - def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = - methPart(fun1).tpe match { + def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = { + val ctx1 = if (ctx.explicitNulls) { + // TODO(abeln): we're re-doing work here by recomputing what's implies by the lhs of the comparison. + // e.g. in `A && B && C && D`, we'll recompute the facts implied by `A && B` twice. + // Find a more-efficient way to do this. + val newFacts = FlowTyper.inferWithinCond(fun1) + if (newFacts.isEmpty) ctx else ctx.fresh.addFlowFacts(newFacts) + } else { + ctx + } + + // Separate into a function so we can pass the updated context. + def proc(implicit ctx: Context): tpd.Tree = methPart(fun1).tpe match { case funRef: TermRef => val app = if (proto.allArgTypesAreCurrent()) @@ -870,6 +881,9 @@ trait Applications extends Compatibility { handleUnexpectedFunType(tree, fun1) } + proc(ctx1) + } + /** Try same application with an implicit inserted around the qualifier of the function * part. Return an optional value to indicate success. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index b2585dc7e591..39dc3479e0a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -22,9 +22,9 @@ import transform.TypeUtils._ import transform.SymUtils._ import reporting.diagnostic.messages._ -trait NamerContextOps { +trait NamerContextOps { this: Context => - + import NamerContextOps._ def typer: Typer = ctx.typeAssigner match { @@ -376,6 +376,17 @@ class Namer { typer: Typer => case tree: TypeDef => if (flags.is(Opaque) && ctx.owner.isClass) ctx.owner.setFlag(Opaque) new TypeDefCompleter(tree)(cctx) + case tree: ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !tree.mods.isOneOf(Lazy | Implicit) => + // Use a completer that completes itself with the completion text, so + // we can use flow typing for `ValDef`s. + // Don't use this special kind of completer for `lazy` or `implicit` symbols, + // because those could be completed through a forward reference: + // e.g. + // val x: String|Null = ??? + // y /* y is completed through a forward reference! */ + // if (x == null) throw new NullPointerException() + // lazy val y: Int = x.length + new ValDefInBlockCompleter(tree)(cctx) case _ => new Completer(tree)(cctx) } @@ -744,14 +755,19 @@ class Namer { typer: Typer => protected def localContext(owner: Symbol): FreshContext = ctx.fresh.setOwner(owner).setTree(original) + /** The context in which the completer should be typed: usually it's the creation context, + * but could also be the completion context. + */ + protected def typingContext(owner: Symbol, completionCtx: Context): FreshContext = localContext(owner) + /** The context with which this completer was created */ def creationContext: Context = ctx ctx.typerState.markShared() - protected def typeSig(sym: Symbol): Type = original match { + protected def typeSig(sym: Symbol, completionCtx: Context): Type = original match { case original: ValDef => if (sym.is(Module)) moduleValSig(sym) - else valOrDefDefSig(original, sym, Nil, Nil, identity)(localContext(sym).setNewScope) + else valOrDefDefSig(original, sym, Nil, Nil, identity)(typingContext(sym, completionCtx).setNewScope) case original: DefDef => val typer1 = ctx.typer.newLikeThis nestedTyper(sym) = typer1 @@ -781,7 +797,7 @@ class Namer { typer: Typer => denot.info = UnspecifiedErrorType } else { - completeInCreationContext(denot) + completeInContext(denot, ctx) if (denot.isCompleted) registerIfChild(denot) } } @@ -866,18 +882,47 @@ class Namer { typer: Typer => /** Intentionally left without `implicit ctx` parameter. We need * to pick up the context at the point where the completer was created. + * + * If -Yexplicit-nulls, is enabled, we sometimes use the completion context. + * See `ValDefInBlockCompleter`. */ - def completeInCreationContext(denot: SymDenotation): Unit = { + def completeInContext(denot: SymDenotation, completionContext: Context): Unit = { val sym = denot.symbol addAnnotations(sym) addInlineInfo(sym) - denot.info = typeSig(sym) + denot.info = typeSig(sym, completionContext) invalidateIfClashingSynthetic(denot) Checking.checkWellFormed(sym) denot.info = avoidPrivateLeaks(sym) } } + /** A completer that uses its completion context (as opposed to the creation context) + * to complete itself. This is used so that flow typing can handle `ValDef`s that appear within a block. + * + * Example: + * Suppose we have a block containing + * + * 1. val x: String|Null = ??? + * 2. if (x == null) throw NPE + * 3. val y = x + * + * We want to infer y: String on line 3, but if the completer for `y` uses its creation context, + * then we won't have the additional flow facts that say that `y` is not null. + * + * The solution is to use a completer that completes itself using the context at completion time, + * as opposed to creation time. Normally, we need to use the creation context, because a completer + * can be completed at any point in the future. However, for `ValDef`s within a block, we know they'll + * be completed immediately after the symbols are created, so it's safe to use this new kind of completer. + */ + class ValDefInBlockCompleter(original: ValDef)(implicit ctx: Context) extends Completer(original)(ctx) { + assert(ctx.explicitNulls) + + override def typingContext(owner: Symbol, completionCtx: Context): FreshContext = { + completionCtx.fresh.setOwner(owner).setTree(original) + } + } + class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter { private[this] var myTypeParams: List[TypeSymbol] = null private[this] var nestedCtx: Context = null @@ -906,7 +951,7 @@ class Namer { typer: Typer => myTypeParams } - override protected def typeSig(sym: Symbol): Type = + override protected def typeSig(sym: Symbol, completionContext: Context): Type = typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx) } @@ -1088,7 +1133,7 @@ class Namer { typer: Typer => } /** The type signature of a ClassDef with given symbol */ - override def completeInCreationContext(denot: SymDenotation): Unit = { + override def completeInContext(denot: SymDenotation, completionContext: Context): Unit = { val parents = impl.parents /* The type of a parent constructor. Types constructor arguments @@ -1385,7 +1430,12 @@ class Namer { typer: Typer => case _ => WildcardType } - paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + val memTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) + if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) { + JavaNullInterop.nullifyMember(sym, memTpe) + } else { + memTpe + } } /** The type signature of a DefDef with given symbol */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c19a53ef9d13..392721a5fdc3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -36,6 +36,7 @@ import util.Stats.record import config.Printers.{gadts, typr} import rewrites.Rewrites.patch import NavigateAST._ +import dotty.tools.dotc.core.FlowTyper.{FlowFacts, Inferred} import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages} import transform.SymUtils._ import transform.TypeUtils._ @@ -419,11 +420,13 @@ class Typer extends Namer else errorType(new MissingIdent(tree, kind, name.show), tree.sourcePos) - val tree1 = ownType match { - case ownType: NamedType if !prefixIsElidable(ownType) => - ref(ownType).withSpan(tree.span) + val ownType1 = if (ctx.explicitNulls) FlowTyper.refineType(ownType) else ownType + + val tree1 = ownType1 match { + case ownType1: NamedType if !prefixIsElidable(ownType1) => + ref(ownType1).withSpan(tree.span) case _ => - tree.withType(ownType) + tree.withType(ownType1) } checkStableIdentPattern(tree1, pt) @@ -451,8 +454,9 @@ class Typer extends Namer case qual => if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos) val select = assignType(cpy.Select(tree)(qual, tree.name), qual) - if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) - else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select + val select1 = if (ctx.explicitNulls) select.withType(FlowTyper.refineType(select.tpe)) else select + if (select1.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select1, pt)) + else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select1 else typedDynamicSelect(tree, Nil, pt) } @@ -762,8 +766,12 @@ class Typer extends Namer } } - def typedBlockStats(stats: List[untpd.Tree])(implicit ctx: Context): (Context, List[tpd.Tree]) = - (index(stats), typedStats(stats, ctx.owner)) + def typedBlockStats(stats: List[untpd.Tree])(implicit ctx: Context): (Context, List[tpd.Tree]) = { + val ctx1 = index(stats) + val (stats1, facts) = typedStatsAndGetFacts(stats, ctx.owner) + val ctx2 = if (ctx.explicitNulls && facts.nonEmpty) ctx1.fresh.addFlowFacts(facts) else ctx1 + (ctx2, stats1) + } def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context): Tree = { val localCtx = ctx.retractMode(Mode.Pattern) @@ -815,15 +823,25 @@ class Typer extends Namer if (tree.isInline) checkInInlineContext("inline if", tree.posd) val cond1 = typed(tree.cond, defn.BooleanType) + val (thenCtx, elseCtx) = if (ctx.explicitNulls) { + val Inferred(ifTrue, ifFalse) = FlowTyper.inferFromCond(cond1) + (ctx.fresh.addFlowFacts(ifTrue), ctx.fresh.addFlowFacts(ifFalse)) + } else { + (ctx, ctx) + } + if (tree.elsep.isEmpty) { - val thenp1 = typed(tree.thenp, defn.UnitType) + val thenp1 = typed(tree.thenp, defn.UnitType)(thenCtx) val elsep1 = tpd.unitLiteral.withSpan(tree.span.endPos) cpy.If(tree)(cond1, thenp1, elsep1).withType(defn.UnitType) } else { - val thenp1 :: elsep1 :: Nil = harmonic(harmonize, pt)( - (tree.thenp :: tree.elsep :: Nil).map(typed(_, pt.dropIfProto))) - assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1) + val thenp2 :: elsep2 :: Nil = harmonic(harmonize, pt) { + val thenp1 = typed(tree.thenp, pt.dropIfProto)(thenCtx) + val elsep1 = typed(tree.elsep, pt.dropIfProto)(elseCtx) + thenp1 :: elsep1 :: Nil + } + assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2) } } @@ -1261,7 +1279,13 @@ class Typer extends Namer } def typedThrow(tree: untpd.Throw)(implicit ctx: Context): Tree = { - val expr1 = typed(tree.expr, defn.ThrowableType) + val pt = if (ctx.explicitNulls) { + // `throw null` is valid Scala code + OrType(defn.ThrowableType, defn.NullType) + } else { + defn.ThrowableType + } + val expr1 = typed(tree.expr, pt) Throw(expr1).withSpan(tree.span) } @@ -2170,20 +2194,35 @@ class Typer extends Namer trees mapconserve (typed(_)) def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + val (stats1, _) = typedStatsAndGetFacts(stats, exprOwner) + stats1 + } + + def typedStatsAndGetFacts(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], FlowFacts) = { val buf = new mutable.ListBuffer[Tree] val enumContexts = new mutable.HashMap[Symbol, Context] // A map from `enum` symbols to the contexts enclosing their definitions - @tailrec def traverse(stats: List[untpd.Tree])(implicit ctx: Context): List[Tree] = stats match { + @tailrec def traverse(stats: List[untpd.Tree], facts: FlowFacts)(implicit ctx: Context): (List[Tree], FlowFacts) = stats match { case (imp: untpd.Import) :: rest => val imp1 = typed(imp) buf += imp1 - traverse(rest)(ctx.importContext(imp, imp1.symbol)) + traverse(rest, facts)(ctx.importContext(imp, imp1.symbol)) case (mdef: untpd.DefTree) :: rest => mdef.removeAttachment(ExpandedTree) match { case Some(xtree) => - traverse(xtree :: rest) + traverse(xtree :: rest, facts) case none => - typed(mdef) match { + import untpd.modsDeco + val ctx1 = if (ctx.explicitNulls) { + mdef match { + case mdef: untpd.ValDef if ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => + ctx.fresh.addFlowFacts(facts) + case _ => ctx + } + } else { + ctx + } + typed(mdef)(ctx1) match { case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) // replace body with expansion, because it will be used as inlined body @@ -2196,21 +2235,31 @@ class Typer extends Namer case mdef1 => buf += mdef1 } - traverse(rest) + traverse(rest, facts) } case Thicket(stats) :: rest => - traverse(stats ++ rest) + traverse(stats ++ rest, facts) case (stat: untpd.Export) :: rest => buf ++= stat.attachmentOrElse(ExportForwarders, Nil) // no attachment can happen in case of cyclic references - traverse(rest) + traverse(rest, facts) case stat :: rest => - val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner)) - checkStatementPurity(stat1)(stat, exprOwner) + val ctx1 = if (ctx.explicitNulls && facts.nonEmpty) { + ctx.fresh.addFlowFacts(facts) + } else { + ctx + } + val stat1 = typed(stat)(ctx1.exprContext(stat, exprOwner)) + checkStatementPurity(stat1)(stat, exprOwner)(ctx1) buf += stat1 - traverse(rest) + val newFacts = if (ctx.explicitNulls) { + facts ++ FlowTyper.inferWithinBlock(stat1)(ctx1) + } else { + facts + } + traverse(rest, newFacts)(ctx) case nil => - buf.toList + (buf.toList, facts) } val localCtx = { val exprOwnerOpt = if (exprOwner == ctx.owner) None else Some(exprOwner) @@ -2227,9 +2276,10 @@ class Typer extends Namer case _ => stat } - val stats1 = traverse(stats)(localCtx).mapConserve(finalize) - if (ctx.owner == exprOwner) checkNoAlphaConflict(stats1) - stats1 + val (stats1, ctx1) = traverse(stats, FlowTyper.emptyFlowFacts)(localCtx) + val stats2 = stats1.mapConserve(finalize) + if (ctx.owner == exprOwner) checkNoAlphaConflict(stats2) + (stats2, ctx1) } /** Given an inline method `mdef`, the method rewritten so that its body diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c68eff6ea7be..10cc87d8c888 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -248,6 +248,25 @@ class CompilationTests extends ParallelTesting { tests.foreach(_.delete()) } + + // Explicit nulls tests + @Test def explicitNullsNeg: Unit = { + implicit val testGroup: TestGroup = TestGroup("explicitNullsNeg") + aggregateTests( + compileFilesInDir("tests/explicit-nulls/neg", explicitNullsOptions), + compileFilesInDir("tests/explicit-nulls/neg-patmat", explicitNullsOptions and "-Xfatal-warnings") + ) + }.checkExpectedErrors() + + @Test def explicitNullsPos: Unit = { + implicit val testGroup: TestGroup = TestGroup("explicitNullsPos") + compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions) + }.checkCompile() + + @Test def explicitNullsRun: Unit = { + implicit val testGroup: TestGroup = TestGroup("explicitNullsRun") + compileFilesInDir("tests/explicit-nulls/run", explicitNullsOptions) + }.checkRuns() } object CompilationTests { diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 67f678be55eb..56f413d18359 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -66,4 +66,7 @@ object TestConfiguration { val scala2Mode = defaultOptions and "-language:Scala2" val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") + + /** Enables explicit nulls */ + val explicitNullsOptions = defaultOptions and "-Yexplicit-nulls" } diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md new file mode 100644 index 000000000000..4ad817ce5eeb --- /dev/null +++ b/docs/docs/internals/explicit-nulls.md @@ -0,0 +1,186 @@ +--- +layout: doc-page +title: "Explicit Nulls" +--- + +The "explicit nulls" feature (enabled via a flag) changes the Scala type hierarchy +so that reference types (e.g. `String`) are non-nullable. We can still express nullability +with union types: e.g. `val x: String|Null = null`. + +The implementation of the feature in dotty can be conceptually divided in several parts: + 1. changes to the type hierarchy so that `Null` is only a subtype of `Any` + 2. a "translation layer" for Java interop that exposes the nullability in Java APIs + 3. a "magic" `JavaNull` type (an alias for `Null`) that is recognized by the compiler and + allows unsound member selections (trading soundness for usability) + 4. a module for "flow typing", so we can work more naturally with nullable values + +Feature Flag +------------ +Explicit nulls are disabled by default. They can be enabled via `-Yexplicit-nulls` defined in +`ScalaSettings.scala`. All of the explicit-nulls-related changes should be gated behind the flag. + +Type Hierarchy +-------------- +We change the type hierarchy so that `Null` is only a subtype of `Any` by: + - modifying the notion of what is a nullable class (`isNullableClass`) in `SymDenotations` + to include _only_ `Null` and `Any` + - changing the parent of `Null` in `Definitions` to point to `Any` and not `AnyRef` + - changing `isBottomType` and `isBottomClass` in `Definitions` + +Java Interop +------------ +TODO(abeln): add support for recognizing nullability annotations a la +https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations + +The problem we're trying to solve here is: if we see a Java method `String foo(String)`, +what should that method look like to Scala? + - since we should be able to pass `null` into Java methods, the argument type should be `String|JavaNull` + - since Java methods might return `null`, the return type should be `String|JavaNull` + +`JavaNull` here is a type alias for `Null` with "magic" properties (see below). + +At a high-level: + - we track the loading of Java fields and methods as they're loaded by the compiler + - we do this in two places: `Namer` (for Java sources) and `ClassFileParser` (for bytecode) + - whenever we load a Java member, we "nullify" its argument and return types + +The nullification logic lives in `JavaNullInterop.scala`, a new file. + +The entry point is the function `def nullifyMember(sym: Symbol, tp: Type)(implicit ctx: Context): Type` +which, given a symbol and its "regular" type, produces what the type of the symbol should be in the +explicit nulls world. + +In order to nullify a member, we first pass it through a "whitelist" of symbols that need +special handling (e.g. `constructors`, which never return `null`). If none of the "policies" in the +whitelist apply, we then process the symbol with a `TypeMap` that implements the following nullification +function `n`: + 1. n(T) = T|JavaNull if T is a reference type + 2. n(T) = T if T is a value type + 3. n(T) = T|JavaNull if T is a type parameter + 4. n(C[T]) = C[T]|JavaNull if C is Java-defined + 5. n(C[T]) = C[n(T)]|JavaNull if C is Scala-defined + 6. n(A|B) = n(A)|n(B)|JavaNull + 7. n(A&B) = (n(A)&n(B))|JavaNull + 8. n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R + 9. n(T) = T otherwise + +JavaNull +-------- +`JavaNull` is just an alias for `Null`, but with magic power. `JavaNull`'s magic (anti-)power is that +it's unsound. + +```scala +val s: String|JavaNull = "hello" +s.length // allowed, but might throw NPE +``` + +`JavaNull` is defined as `JavaNullAlias` in `Definitions`. +The logic to allow member selections is defined in `findMember` in `Types.scala`: + - if we're finding a member in a type union + - and the union contains `JavaNull` on the r.h.s. after normalization (see below) + - then we can continue with `findMember` on the l.h.s of the union (as opposed to failing) + +Working with Nullable Unions +---------------------------- +Within `Types.scala`, we defined a few utility methods to work with nullable unions. All of these +are methods of the `Type` class, so call them with `this` as a receiver: + - `isNullableUnion` determines whether `this` is a nullable union. Here, what constitutes + a nullable union is determined purely syntactically: + 1. first we "normalize" `this` (see below) + 2. if the result is of the form `T | Null`, then the type is considered a nullable union. + Otherwise, it isn't. + - `isJavaNullableUnion` determines whether `this` is syntactically a union of the form `T|JavaNull` + - `normNullableUnion` normalizes `this` as follows: + 1. if `this` is not a nullable union, it's returned unchanged. + 2. if `this` is a union, then it's re-arranged so that all the `Null`s are to the right of all + the non-`Null`s. + - `stripNull` syntactically strips nullability from `this`: e.g. `String|Null => String`. Notice this + works only at the "top level": e.g. if we have an `Array[String|Null]|Null` and we call `stripNull` + we'll get `Array[String|Null]` (only the outermost nullable union was removed). + - `stripAllJavaNull` is like `stripNull` but removes _all_ nullable unions in the type (and only works + for `JavaNull`). This is needed when we want to "revert" the Java nullification function. + +Flow Typing +----------- +Flow typing is needed so we can work with nullable unions in a more natural way. +The following is a common idiom that should work without additional casts: +```scala +val x: String|Null = ??? +if (x != null && x.length < 10) +``` +This is implemented as a "must be null in the current scope" analysis on stable paths: + - we add additional state to the `Context` in `Contexts.scala`. + Specifically, we add a set of `FlowFacts` (right now just a set of `TermRef`s), which + are the paths known to be non-nullable in the current scope. + - the bulk of the flow typing logic lives in a new `FlowTyper.scala` file. + + There are four entry points to `FlowTyper`: + 1. `inferFromCond(cond: Tree): Inferred`: given a tree representing a condition such as + `x != null && x.length < 10`, return the `Inferred` facts. + + In turn, `Inferred` is defined as `case class Inferred(ifTrue: FlowFacts, ifFalse: FlowFacts)`. + That is, `Inferred` contains the paths that _must_ be non-null if the condition is true and, + separately, the paths that must be non-null if the condition is false. + + e.g. for `x != null` we'd get `Inferred({x}, {})`, but only if `x` is stable. + However, if we had `x == null` we'd get `Inferred({}, {x})`. + + 2. `inferWithinCond(cond: Tree): FlowFacts`: given a condition of the form `lhs && rhs` or + `lhs || rhs`, calculate the paths that must be non-null for the rhs to execute (given + that these operations) are short-circuiting. + + 3. `inferWithinBlock(stat: Tree): FlowFacts`: if `stat` is a statement with a block, calculate + which paths must be non-null when the statement that _follows_ `stat` in the block executes. + This is so we can handle things like + ```scala + val x: String|Null = ??? + if (x == null) return + val y = x.length + ``` + Here, `inferWithinBlock(if (x == null) return)` gives back `{x}`, because we can tell that + the next statement will execute only if `x` is non-null. + + 4. `refineType(tpe: Type): Type`: given a type, refine it if possible using flow-sensitive type + information. This uses a `NonNullTermRef` (see below). + + - Each of the public APIs in `FlowTyper` is used to do flow typing in a different scenario + (but all the use sites of `FlowTyper` are in `Typer.scala`): + * `refineType` is used in `typedIdent` and `typedSelect` + * `inferFromCond` is used for typing if statements + * `inferWithinCond` is used when typing "applications" (which is how "&&" and "||" are encoded) + * `inferWithinBlock` is used when typing blocks + + For example, to do FlowTyping on if expressions: + * we type the condition + * we give the typed condition to the FlowTyper and obtain a pair of sets of paths `(ifTrue, ifFalse)`. + We type the `then` branch with the `ifTrue` facts, and the else branch with the `ifFalse` facts. + * profit + +Flow typing also introduces two new abstractions: `NonNullTermRef` and `ValDefInBlockCompleter`. + +#### NonNullTermRef +This is a new type of `TermRef` (path-dependent type) that, whenever its denotation is updated, makes sure +that the underlying widened type is non-null. It's defined in `Types.scala`. A `NonNullTermRef` is identified by `computeDenot` whenever the denotation is updated, and then we call `stripNull` on the widened type. + +To use the flow-typing information, whenever we see a path that we know must be non-null (in `typedIdent` or +`typedSelect`), we replace its `TermRef` by a `NonNullTermRef`. + +#### ValDefInBlockCompleter +This a new type of completer defined in `Namer.scala` that completes itself using the completion context, asopposed to the creation context. + +The problem we're trying to solve here is the following: +```scala +val x: String|Null = ??? +if (x == null) return +val y = x.length +``` +The block is usually typed as follows: + 1. first, we scan the block to create symbols for the new definitions (`val x`, `val y`) + 2. then, we type statement by statement + 3. the completers for the symbols created in 1. are _all_ invoked in step 2. However, + regular completers use the _creation_ context, so that means that `val y` is completed + with a context that doesn't contain the new flow fact "x != null". + +To fix this, whenever we're inside a block and we create completers for `val`s, we use a +`ValDefInBlockCompleter` instead of a regular completer. This new completer uses the completion context, +which is aware of the new flow fact "x != null". diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md new file mode 100644 index 000000000000..f8ef015abf85 --- /dev/null +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -0,0 +1,382 @@ +--- +layout: doc-page +title: "Explicit Nulls" +--- + +This proposal describes 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: +``` +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 +``` + +Explicit nulls are enabled via a `-Yexplicit-nulls` flag, so they're an opt-in feature. + +Read on for details. + +## New Type Hierarchy + +When explicit nulls are enabled, the type hierarchy changes so that `Null` is subtype only of +`Any`, as opposed to every reference type. + +This is the new type hierarchy: +![](../../images/explicit-nulls/explicit-nulls-type-hierarchy.png "Type Hierarchy for Explicit Nulls") + +After erasure, `Null` remains a subtype of all reference types (as forced by the JVM). + +## 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 support this case by making the `eq` and `ne` methods in `AnyRef` take a `AnyRef|Null` +as argument. + +We also need to support +```scala +null.eq("hello") +val x: String|Null = null +x.eq(null) +``` + +We support this case via extension methods defined in the Predef: +```scala +def (x: AnyRef|Null) eq(y: AnyRef|Null): Boolean = + (x == null && y == null) || (x != null && x.eq(y)) +``` + +## 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`. + +## 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. + +Specifically, we patch + * the type of fields + * the argument type and return type of methods + +### Nullification Function + +We do the patching with a "nullification" function `n` on types: +```scala +1. n(T) = T|JavaNull if T is a reference type +2. n(T) = T if T is a value type +3. n(T) = T|JavaNull if T is a type parameter +4. n(C[T]) = C[T]|JavaNull if C is Java-defined +5. n(C[T]) = C[n(T)]|JavaNull if C is Scala-defined +6. n(A|B) = n(A)|n(B)|JavaNull +7. n(A&B) = (n(A)&n(B))|JavaNull +8. n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R +9. n(T) = T otherwise +``` + +`JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules for `nf` below with examples. + + * The first two rules are easy: we nullify reference types but not value types. + ```scala + class C { + String s; + int x; + } + ==> + class C { + val s: String|Null + val x: Int + } + ``` + + * In rule 3 we nullify type parameters because in Java a type parameter is always nullable, so the following code compiles. + ```scala + class C { T foo() { return null; } } + ==> + class C[T] { def foo(): T|Null } + ``` + + Notice this is rule is sometimes too conservative, as witnessed by + ```scala + class InScala { + val c: C[Bool] = ??? // C as above + val b: Bool = c.foo() // no longer typechecks, since foo now returns Bool|Null + } + ``` + + * Rule 4 reduces the number of redundant nullable types we need to add. Consider + ```scala + class Box { T get(); } + class BoxFactory { Box makeBox(); } + ==> + class Box[T] { def get(): T|JavaNull } + class BoxFactory[T] { def makeBox(): Box[T]|JavaNull } + ``` + + Suppose we have a `BoxFactory[String]`. Notice that calling `makeBox()` on it returns a `Box[String]|JavaNull`, not + a `Box[String|JavaNull]|JavaNull`, because of rule 4. This seems at first glance unsound ("What if the box itself + has `null` inside?"), but is sound because calling `get()` on a `Box[String]` returns a `String|JavaNull`, as per + rule 3. + + Notice that for rule 4 to be correct we need to patch _all_ Java-defined classes that transitively appear in the + argument or return type of a field or method accessible from the Scala code being compiled. Absent crazy reflection + magic, we think that all such Java classes _must_ be visible to the Typer in the first place, so they will be patched. + + * Rule 5 is needed because Java code might use a generic that's defined in Scala as opposed to Java. + ```scala + class BoxFactory { Box makeBox(); } // Box is Scala defined + ==> + class BoxFactory[T] { def makeBox(): Box[T|JavaNull]|JavaNull } + ``` + + In this case, since `Box` is Scala-defined, `nf` is applied to the type argument `T`, so rule 3 applies and we get + `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 handling of unions and intersections in the compiler is 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 +```scala +type JavaNull = Null +``` + +`JavaNull` behaves just like `Null`, except it allows (unsound) member selections: +```scala +// Assume someJavaMethod()'s original Java signature is +// String someJavaMethod() {} +val s2: String = someJavaMethod().trim().substring(2).toLowerCase() // unsound +``` + +Here, all of `trim`, `substring` and `toLowerCase` return a `String|JavaNull`. +The Typer notices the `JavaNull` and allows the member selection to go through. +However, if `someJavaMethod` were to return `null`, then the first member selection +would throw a `NPE`. + +Without `JavaNull`, the chaining becomes too cumbersome +```scala +val ret = someJavaMethod() +val s2 = if (ret != null) { + val tmp = ret.trim() + if (tmp != null) { + val tmp2 = tmp.substring(2) + if (tmp2 != null) { + tmp2.toLowerCase() + } + } +} +// Additionally, we need to handle the `else` branches. +``` + +## Binary Compatibility + +Our strategy for binary compatibility with Scala binaries that predate explicit nulls +is to leave the types unchanged and be compatible but unsound. + +Concretely, the problem is how to interpret the return type of `foo` below +```scala +// As compiled by e.g. Scala 2.12 +class Old { + def foo(): String = ??? +} +``` +There are two options: + - `def foo(): String` + - `def foo(): String|Null` + +The first option is unsound. The second option matches how we handle Java methods. + +However, this approach is too-conservative in the presence of generics +```scala +class Old[T] { + def id(x: T): T = x +} +==> +class Old[T] { + def id(x: T|Null): T|Null = x +} +``` + +If we instantiate `Old[T]` with a value type, then `id` now returns a nullable value, +even though it shouldn't: +```scala +val o: Old[Boolean] = ??? +val b = o.id(true) // b: Boolean|Null +``` + +So really the options are between being unsound and being too conservative. +The unsoundness only kicks in if the Scala code being used returns a `null` value. +We hypothesize that `null` is used infrequently in Scala libraries, so we go with +the first option. + +If a using an unported Scala library that _produces_ `null`, the user can wrap the +(hopefully rare) API in a type-safe wrapper: +```scala +// Unported library +class Old { + def foo(): String = null +} + +// User code in explicit-null world +def fooWrapper(o: Old): String|Null = o.foo() // ok: String <: String|Null + +val o: Old = ??? +val s = fooWrapper(o) +``` + +If the offending API _consumes_ `null`, then the user can cast the null literal to +the right type (the cast will succeed, since at runtime `Null` _is_ a subtype of +any reference type). +```scala +// Unported library +class Old() { + /** Pass a String, or null to signal a special case */ + def foo(s: String): Unit = ??? +} + +// User code in explicit-null world +val o: Old = ??? +o.foo(null.asInstanceOf[String]) // ok: cast will succeed at runtime +``` + +## Flow Typing + +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` + +### Non-Stable Paths +If `p` isn't stable, then inferring non-nullness is potentially unsound: +```scala +var s: String|Null = "hello" +if (s != null && {s = null; true}) { + // s == null +} +``` + +We _only_ infer non-nullness if `p` is stable (`val`s and not `var`s or `def`s). + +### Logical Operators +We also support logical operators (`&&`, `||`, and `!`): +```scala +val s: String|Null = ??? +val s2: String|Null = ??? +if (s != null && s2 != null) { + // s: String + // s2: String +} + +if (s == null || s2 == null) { + // s: String|Null + // s2: String|Null +} else { + // s: String + // s2: String +} +``` + +### Inside Conditions +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` + // s: String +} + +if (s == null || s.length > 0) // s: String in `s.length > 0` { + // s: String|Null +} else { + // s: String|Null +} +``` + +### Unsupported Idioms +We don't support + - reasoning about non-stable paths + - flow facts not related to nullability (`if (x == 0) { // x: 0.type not inferred }`) + - tracking aliasing between non-nullable paths + ```scala + val s: String|Null = ??? + val s2: String|Null = ??? + if (s != null && s == s2) { + // s: String inferred + // s2: String not inferred + } + ``` diff --git a/docs/images/explicit-nulls/explicit-nulls-type-hierarchy.png b/docs/images/explicit-nulls/explicit-nulls-type-hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..65179260c2462425a17122102d7c434195948b34 GIT binary patch literal 57752 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 literal 0 HcmV?d00001 diff --git a/library/src-bootstrapped/scala/ExplicitNulls.scala b/library/src-bootstrapped/scala/ExplicitNulls.scala new file mode 100644 index 000000000000..929db986d62b --- /dev/null +++ b/library/src-bootstrapped/scala/ExplicitNulls.scala @@ -0,0 +1,30 @@ +package scala + +/** This module defines extension methods for working with explicit nulls. */ +delegate ExplicitNullsOps { + + /** Strips away the nullability from a value. + * e.g. + * val s1: String|Null = "hello" + * val s: String = s1.nn + * + * Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE. + */ + def (x: T|Null) nn[T]: T = + if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") + else x.asInstanceOf[T] + + /** Reference equality where the receiver is a nullable union. + * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.eq` will invoke the + * `eq` method in `AnyRef`. + */ + def (x: AnyRef|Null) eq(y: AnyRef|Null): Boolean = + (x == null && y == null) || (x != null && x.eq(y)) + + /** Reference disequality where the receiver is a nullable union. + * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.ne` will invoke the + * `ne` method in `AnyRef`. + */ + def (x: AnyRef|Null) ne(y: AnyRef|Null): Boolean = + (x == null && y != null) || (x != null && x.ne(y)) +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg-patmat/patmat1.scala b/tests/explicit-nulls/neg-patmat/patmat1.scala new file mode 100644 index 000000000000..6e9710a56dec --- /dev/null +++ b/tests/explicit-nulls/neg-patmat/patmat1.scala @@ -0,0 +1,38 @@ + +class Foo { + val s: String = ??? + s match { + case s: String => 100 // warning: type test will always succeed + case _ => 200 // error: unreachable + } + + s match { + case s: String => 100 // warning: type test will always succeed + case _ => 200 // error: unreachable + } + + sealed trait Animal + case class Dog(name: String) extends Animal + case object Cat extends Animal + + val a: Animal = ??? + a match { + case Dog(name) => 100 + case Cat => 200 + case _ => 300 // error: unreachable + } + + val a2: Animal|Null = ??? + a2 match { + case Dog(_) => 100 + case Cat => 200 + case _ => 300 + } + + val a3: Animal|Null = ??? + a3 match { + case Dog(_) => 100 + case Cat => 200 + case null => 300 // ok + } +} diff --git a/tests/explicit-nulls/neg/alias.scala b/tests/explicit-nulls/neg/alias.scala new file mode 100644 index 000000000000..f8dea4864027 --- /dev/null +++ b/tests/explicit-nulls/neg/alias.scala @@ -0,0 +1,24 @@ + +// Test that nullability is correctly detected +// in the presence of a type alias. +class Base { + type T >: Null <: AnyRef|Null +} + +object foo { + class Foo { + val length: Int = 42 + def doFoo(): Unit = () + } +} + +class Derived extends Base { + type Nullable[X] = X|Null + type Foo = Nullable[foo.Foo] + + def fun(foo: Foo): Unit = { + foo.length // error: foo is nullable + foo.doFoo() // error: foo is nullable + } +} + diff --git a/tests/explicit-nulls/neg/basic.scala b/tests/explicit-nulls/neg/basic.scala new file mode 100644 index 000000000000..7c652887590b --- /dev/null +++ b/tests/explicit-nulls/neg/basic.scala @@ -0,0 +1,11 @@ +// Test that reference types are no longer nullable. + +class Foo { + val s: String = null // error + val s1: String|Null = null // ok + val b: Boolean = null // error + val ar: AnyRef = null // error + val a: Any = null // ok + val n: Null = null // ok +} + diff --git a/tests/explicit-nulls/neg/default.scala b/tests/explicit-nulls/neg/default.scala new file mode 100644 index 000000000000..fe115861e926 --- /dev/null +++ b/tests/explicit-nulls/neg/default.scala @@ -0,0 +1,13 @@ + +class Foo { + val x: String = null // error: String is non-nullable + + def foo(x: String): String = "x" + + val y = foo(null) // error: String argument is non-nullable + + val z: String = foo("hello") + + class Bar + val b: Bar = null // error: user-created classes are also non-nullable +} diff --git a/tests/explicit-nulls/neg/eq.scala b/tests/explicit-nulls/neg/eq.scala new file mode 100644 index 000000000000..970f4ec811a8 --- /dev/null +++ b/tests/explicit-nulls/neg/eq.scala @@ -0,0 +1,26 @@ +// Test what can be compared for equality against null. +class Foo { + // Null itself + val x0: Null = null + x0 == null + null == x0 + null == null + + // Nullable types: OK + val x1: String|Null = null + x1 == null + null == x1 + + // Reference types, even non-nullable ones: OK. + // Allowed as an escape hatch. + val x2: String = "hello" + x2 != null + x2 == null + null == x2 + + // Value types: not allowed. + 1 == null // error + null == 1 // error + true == null // error + null == true // error +} diff --git a/tests/explicit-nulls/neg/eq2.scala b/tests/explicit-nulls/neg/eq2.scala new file mode 100644 index 000000000000..13f3e4e7f46a --- /dev/null +++ b/tests/explicit-nulls/neg/eq2.scala @@ -0,0 +1,15 @@ + +// Test that we can't compare for equality `null` and +// classes that derive from AnyVal. +class Foo(x: Int) extends AnyVal + +class Bar { + val foo: Foo = new Foo(15) + if (foo == null) {} // error + if (null == foo) {} // error + + // To test against null, make the type nullable. + val foo2: Foo|Null = foo + if (foo2 == null) {} + if (null == foo2) {} +} diff --git a/tests/explicit-nulls/neg/erasure.scala b/tests/explicit-nulls/neg/erasure.scala new file mode 100644 index 000000000000..da896a0aa427 --- /dev/null +++ b/tests/explicit-nulls/neg/erasure.scala @@ -0,0 +1,6 @@ +// Check that T|Null is erased to T if T is a reference type. + +trait Foo { + def foo(s: String|Null): Unit + def foo(s: String): Unit // error: collision after erasure +} diff --git a/tests/explicit-nulls/neg/flow-conservative.scala b/tests/explicit-nulls/neg/flow-conservative.scala new file mode 100644 index 000000000000..4ed5713a08f8 --- /dev/null +++ b/tests/explicit-nulls/neg/flow-conservative.scala @@ -0,0 +1,55 @@ + +// Show that the static analysis behind flow typing is conservative. + +class Test { + + val x: String|Null = ??? + + // Why is the then branch ok, but the else problematic? + // The problem is that we're computing a "must not be null analysis". + // So we know that + // 1) if the condition x == null && x != null, then both sides of the + // and must be true. Then it must be the case that x != null, so we + // know that x cannot be null and x.length is allowed. + // Of course, the then branch will never execute, but the analysis doesn't + // know (so it's ok to say that x won't be null). + // 2) if the condition is false, then we only know that _one_ or more + // of the operands failed, but we don't know _which_. + // This means that we can only pick the flow facts that hold for _both_ + // operands. In particular, we look at x == null, and see that if the condition + // is false, then x must _not_ be null. But then we look at what happens if + // x != null is false, and we can't conclude that any variables must be non-null. + // When we intersect the two sets {x} and \empty, we get the empty set, which + // correctly approximates reality, which is that we can get to the else branch + // regardless of whether x is null. + + if (x == null && x != null) { + val y = x.length // ok + } else { + val y = x.length // error + } + + // Next we show how strengthening the condition can backfire in an + // unintuitive way. + if (x != null && 1 == 1) { + val y = x.length // ok + } + + if (x == null) { + } else { + val y = x.length // ok + } + + // But + if (x == null && 1 == 1) { // logically equivalent to `x == null`, but the + // analysis doesn't known + } else { + val y = x.length // error + } + + // The problem here is the same. If the condition is false + // then we know the l.h.s implies that x must not be null. + // But the r.h.s doesn't tell us anything about x, so we can't + // assume that x is non-null. Then the fact that x is non-null can't + // be propagated to the else branch. +} diff --git a/tests/explicit-nulls/neg/flow-implicitly.scala b/tests/explicit-nulls/neg/flow-implicitly.scala new file mode 100644 index 000000000000..33934cf1f70f --- /dev/null +++ b/tests/explicit-nulls/neg/flow-implicitly.scala @@ -0,0 +1,10 @@ + +// Test that flow typing works well with implicit resolution. +class Test { + implicit val x: String | Null = ??? + implicitly[x.type <:< String] // error: x.type is widened String|Null + + if (x != null) { + implicitly[x.type <:< String] // ok: x.type is widened to String + } +} diff --git a/tests/explicit-nulls/neg/flow.scala b/tests/explicit-nulls/neg/flow.scala new file mode 100644 index 000000000000..013b56d3f1c2 --- /dev/null +++ b/tests/explicit-nulls/neg/flow.scala @@ -0,0 +1,182 @@ + +// Flow-sensitive type inference +class Foo { + + def basic() = { + class Bar { + val s: String = ??? + } + + // Basic + val b: Bar|Null = ??? + if (b != null) { + val s = b.s // ok: type of `b` inferred as `Bar` + val s2: Bar = b + } else { + val s = b.s // error: `b` is `Bar|Null` + } + val s = b.s // error: `b` is `Bar|Null` + } + + def notStable() = { + class Bar { + var s: String = ??? + } + + var b2: Bar|Null = ??? + if (b2 != null) { + val s = b.s // error: type of `b2` isn't refined because `b2` is not stable + } + } + + def nested() = { + class Bar2 { + val x: Bar2|Null = ??? + } + + val bar2: Bar2|Null = ??? + if (bar2 != null) { + if (bar2.x != null) { + if (bar2.x.x != null) { + if (bar2.x.x.x != null) { + val b2: Bar2 = bar2.x.x.x + } + val b2: Bar2 = bar2.x.x + val b2err: Bar2 = bar2.x.x.x // error: expected Bar2 but got Bar2|Null + } + val b2: Bar2 = bar2.x + } + val b2: Bar2 = bar2 + } + } + + def ifThenElse() = { + val s: String|Null = ??? + if (s == null) { + } else { + val len: Int = s.length + val len2 = s.length + } + } + + def elseIf() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + if (s1 != null) { + val len = s1.length + val err1 = s2.length // error + val err2 = s3.length // error + } else if (s2 != null) { + val len = s2.length + val err1 = s1.length // error + val err2 = s3.length // error + } else if (s3 != null) { + val len = s3.length + val err1 = s1.length // error + val err2 = s2.length // error + } + + // Accumulation in elseif + if (s1 == null) { + } else if (s2 == null) { + val len = s1.length + } else if (s3 == null) { + val len1 = s1.length + val len2 = s2.length + } else { + val len1 = s1.length + val len2 = s2.length + val len3 = s3.length + } + } + + def commonIdioms() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + + if (s1 == null || s2 == null || s3 == null) { + } else { + val len1: Int = s1.length + val len2: Int = s2.length + val len3: Int = s3.length + } + + if (s1 != null && s2 != null && s3 != null) { + val len1: Int = s1.length + val len2: Int = s2.length + val len3: Int = s3.length + } + } + + def basicNegation() = { + val s1: String|Null = ??? + if (!(s1 != null)) { + val len = s1.length // error + } else { + val len = s1.length + } + + if (!(!(!(!(s1 != null))))) { + val len1 = s1.length + } + } + + def parens() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + if ((((s1 == null))) || s2 == null) { + } else { + val len1 = s1.length + val len2 = s2.length + } + } + + def operatorPrec() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + + if (s1 != null || s2 != null && s3 != null) { + val len = s3.length // error + } + + if (s1 != null && s2 != null || s3 != null) { + val len1 = s1.length // error + val len2 = s2.length // error + val len3 = s3.length // error + } + + if (s1 != null && (s2 != null || s3 != null)) { + val len1 = s1.length + val len2 = s2.length // error + val len3 = s3.length // error + } + } + + def insideCond() = { + val x: String|Null = ??? + if (x != null && x.length > 0) { + val len = x.length + } else { + val len = x.length // error + } + + if (x == null || x.length > 0) { + val len = x.length // error + } else { + val len = x.length + } + + class Rec { + val r: Rec|Null = ??? + } + + val r: Rec|Null = ??? + if (r != null && r.r != null && (r.r.r == null || r.r.r.r == r)) { + val err = r.r.r.r // error + } + } +} + diff --git a/tests/explicit-nulls/neg/flow2.scala b/tests/explicit-nulls/neg/flow2.scala new file mode 100644 index 000000000000..7ac243f7fd36 --- /dev/null +++ b/tests/explicit-nulls/neg/flow2.scala @@ -0,0 +1,18 @@ + +// Test that flow inference can handle blocks. +class Foo { + val x: String|Null = "hello" + if ({val z = 10; {1 + 1 == 2; x != null}}) { + val l = x.length + } + + if ({x != null; true}) { + val l = x.length // error + } + + val x2: String|Null = "world" + if ({{{{1 + 1 == 2; x != null}}}} && x2 != null) { + val l = x.length + val l2 = x2.length + } +} diff --git a/tests/explicit-nulls/neg/flow3.scala b/tests/explicit-nulls/neg/flow3.scala new file mode 100644 index 000000000000..0c3b8b47e18b --- /dev/null +++ b/tests/explicit-nulls/neg/flow3.scala @@ -0,0 +1,23 @@ + +// Test flow typing in the presence of `eq` and `ne`. + +class Test { + val x: String|Null = ??? + + if (x eq null) { + val y = x.length // error + } else { + val y = x.length // ok + } + + if (x ne null) { + val y = x.length // ok + } else { + val y = x.length // error + } + + if ((x ne null) && x.length > 10) { // ok + } else { + val y = x.length // error + } +} diff --git a/tests/explicit-nulls/neg/flow4.scala b/tests/explicit-nulls/neg/flow4.scala new file mode 100644 index 000000000000..079f8c97dd1b --- /dev/null +++ b/tests/explicit-nulls/neg/flow4.scala @@ -0,0 +1,18 @@ + +// Test that flow inference handles `eq/ne` checks. +class Foo { + val x: String|Null = "" + + if (x eq null) { + val y = x.length // error + } else { + val y = x.length + } + + + if (x ne null) { + val y = x.length + } else { + val y = x.length // error + } +} diff --git a/tests/explicit-nulls/neg/flow5.scala b/tests/explicit-nulls/neg/flow5.scala new file mode 100644 index 000000000000..0d11e45c6d54 --- /dev/null +++ b/tests/explicit-nulls/neg/flow5.scala @@ -0,0 +1,66 @@ + +// Test that flow-sensitive type inference handles +// early exists from blocks. +class Foo(x: String|Null) { + + // Test within constructor + if (x == null) throw new NullPointerException() + val x2: String = x // error: flow inference for blocks doesn't work inside constructors + + def foo(): Unit = { + val y: String|Null = ??? + if (y == null) return () + val y2: String = y // ok + } + + def bar(): Unit = { + val y: String|Null = ??? + if (y != null) { + } else { + return () + } + val y2: String = y // ok + } + + def fooInExprPos(): String = { + val y: String|Null = ??? + if (y == null) return "foo" + y // ok + } + + def nonLocalInBlock(): String = { + val y: String|Null = ??? + if (y == null) { println("foo"); return "foo" } + y + } + + def barWrong(): Unit = { + val y: String|Null = ??? + if (y != null) { + return () + } else { + } + val y2: String = y // error: can't infer that y is non-null (actually, it's the opposite) + } + + def err(msg: String): Nothing = { + throw new RuntimeException(msg) + } + + def retTypeNothing(): String = { + val y: String|Null = ??? + if (y == null) err("y is null!") + y + } + + def errRetUnit(msg: String): Unit = { + throw new RuntimeException(msg) + () + } + + def retTypeUnit(): String = { + val y: String|Null = ??? + if (y == null) errRetUnit("y is null!") + y // error: previous statement returned unit so can't infer non-nullability + } +} diff --git a/tests/explicit-nulls/neg/flow6.scala b/tests/explicit-nulls/neg/flow6.scala new file mode 100644 index 000000000000..6d722984e8f2 --- /dev/null +++ b/tests/explicit-nulls/neg/flow6.scala @@ -0,0 +1,70 @@ + +// Test that flow inference behaves soundly within blocks. +// This means that flow facts are propagated to ValDefs, but not +// to DefDefs or lazy vals or implicit vals. +// The reason is that forward references are allowed for `defs`, +// so when we see +// ``` +// f +// if (x == null) return "foo" +// def f = x.length +// ``` +// we can't assume that f is executed _after_ the if statement +// is executed. +class Foo { + + def test1(): Unit = { + val x: String|Null = ??? + if (x == null) return () + val y = x.length // ok: x: String inferred + () + } + + def test2(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y = x.length // error: x: String|Null inferred + () + } + + def test3(): Unit = { + val x: String|Null = ??? + if (x == null) return () + lazy val y = x.length // error: x: String|Null inferred + () + } + + def test4(): Unit = { + val x: String|Null = ??? + if (x == null) return () + implicit val y: Int = x.length // error: x: String|Null inferred + } + + // This case is different from #3 because the type of y doesn't need + // to be inferred, which triggers a different codepath within the completer. + def test5(): Unit = { + val x: String|Null = ??? + if (x == null) return () + lazy val y: Int = x.length // error: x: String|Null inferred + () + } + + def test6(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y: Int = x.length // error: x: String|Null inferred + () + } + + // This test checks that flow facts are forgotten for defs, but only + // the facts gathered within the current block are forgotten. + // Other facts from outer blocks are remembered. + def test7(): Unit = { + val x: String|Null = ??? + if (x == null) { + } else { + def f = x.length // ok + def f2: Int = x.length // ok + } + } +} diff --git a/tests/explicit-nulls/neg/flow7.scala b/tests/explicit-nulls/neg/flow7.scala new file mode 100644 index 000000000000..e0fa5b79464c --- /dev/null +++ b/tests/explicit-nulls/neg/flow7.scala @@ -0,0 +1,11 @@ + +class Foo(x: String|Null) { + if (x == null) throw new NullPointerException("x is null") + val y: String = x // error: flow inference for blocks only works inside methods + + def foo(x: String|Null): Unit = { + if (x == null) throw new NullPointerException("x is null") + val y: String = x + () + } +} diff --git a/tests/explicit-nulls/neg/interop-array-src/J.java b/tests/explicit-nulls/neg/interop-array-src/J.java new file mode 100644 index 000000000000..80fda83e89d7 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-array-src/J.java @@ -0,0 +1,3 @@ +class J { + void foo(String[] ss) {} +} diff --git a/tests/explicit-nulls/neg/interop-array-src/S.scala b/tests/explicit-nulls/neg/interop-array-src/S.scala new file mode 100644 index 000000000000..3796bab79970 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-array-src/S.scala @@ -0,0 +1,10 @@ +class S { + + val j = new J() + val x: Array[String] = ??? + j.foo(x) // error: expected Array[String|Null] but got Array[String] + + val x2: Array[String|Null] = ??? + j.foo(x2) // ok + j.foo(null) // ok +} diff --git a/tests/explicit-nulls/neg/interop-java-enum-src/Planet.java b/tests/explicit-nulls/neg/interop-java-enum-src/Planet.java new file mode 100644 index 000000000000..7a6cb097f565 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-java-enum-src/Planet.java @@ -0,0 +1,27 @@ +public enum Planet { + MERCURY (3.303e+23, 2.4397e6), + VENUS (4.869e+24, 6.0518e6), + EARTH (5.976e+24, 6.37814e6), + MARS (6.421e+23, 3.3972e6), + JUPITER (1.9e+27, 7.1492e7), + SATURN (5.688e+26, 6.0268e7), + URANUS (8.686e+25, 2.5559e7), + NEPTUNE (1.024e+26, 2.4746e7); + + private final double mass; // in kilograms + private final double radius; // in meters + Planet(double mass, double radius) { + this.mass = mass; + this.radius = radius; + } + private double mass() { return mass; } + private double radius() { return radius; } + + // This method returns a `Planet`, but since `null` is a valid + // return value, the return type should be nullified. + // Contrast with accessing the static member corresponding to the enum + // _instance_ (e.g. Planet.MERCURY) which shouldn't be nullified. + Planet next() { + return null; + } +} diff --git a/tests/explicit-nulls/neg/interop-java-enum-src/S.scala b/tests/explicit-nulls/neg/interop-java-enum-src/S.scala new file mode 100644 index 000000000000..8e4e228a5e76 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-java-enum-src/S.scala @@ -0,0 +1,6 @@ + +// Verify that enum values aren't nullified. +class S { + val p: Planet = Planet.MARS // ok: accessing static member + val p2: Planet = p.next() // error: expected Planet but got Planet|Null +} diff --git a/tests/explicit-nulls/neg/interop-javanull.scala b/tests/explicit-nulls/neg/interop-javanull.scala new file mode 100644 index 000000000000..1a1924016491 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-javanull.scala @@ -0,0 +1,8 @@ + +// Test that JavaNull can be assigned to Null. +class Foo { + import java.util.ArrayList + val l = new ArrayList[String]() + val s: String = l.get(0) // error: return type is nullable + val s2: String|Null = l.get(0) // ok +} diff --git a/tests/explicit-nulls/neg/interop-method-src/J.java b/tests/explicit-nulls/neg/interop-method-src/J.java new file mode 100644 index 000000000000..1b7ea514e4b2 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-method-src/J.java @@ -0,0 +1,5 @@ + +class J { + String foo(String x) { return null; } + static String fooStatic(String x) { return null; } +} diff --git a/tests/explicit-nulls/neg/interop-method-src/S.scala b/tests/explicit-nulls/neg/interop-method-src/S.scala new file mode 100644 index 000000000000..403c86bc4c06 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-method-src/S.scala @@ -0,0 +1,10 @@ + +class S { + + val j = new J() + j.foo(null) // ok: argument is nullable + val s: String = j.foo("hello") // error: return type is nullable + + J.fooStatic(null) // ok: argument is nullable + val s2: String = J.fooStatic("hello") // error: return type is nullable +} diff --git a/tests/explicit-nulls/neg/interop-polytypes.scala b/tests/explicit-nulls/neg/interop-polytypes.scala new file mode 100644 index 000000000000..5718e0fc564d --- /dev/null +++ b/tests/explicit-nulls/neg/interop-polytypes.scala @@ -0,0 +1,7 @@ +class Foo { + import java.util.ArrayList + // Test that return values in PolyTypes are marked as nullable. + val lstring = new ArrayList[String]() + val res: String = java.util.Collections.max(lstring) // error: missing |Null + val res2: String|Null = java.util.Collections.max(lstring) // ok +} diff --git a/tests/explicit-nulls/neg/interop-propagate.scala b/tests/explicit-nulls/neg/interop-propagate.scala new file mode 100644 index 000000000000..c21728fb7395 --- /dev/null +++ b/tests/explicit-nulls/neg/interop-propagate.scala @@ -0,0 +1,11 @@ + class Foo { + import java.util.ArrayList + + // Test that as we extract return values, we're missing the |JavaNull in the return type. + // i.e. test that the nullability is propagated to nested containers. + val ll = new ArrayList[ArrayList[ArrayList[String]]] + val level1: ArrayList[ArrayList[String]] = ll.get(0) // error + val level2: ArrayList[String] = ll.get(0).get(0) // error + val level3: String = ll.get(0).get(0).get(0) // error + val ok: String = ll.get(0).get(0).get(0) // error +} diff --git a/tests/explicit-nulls/neg/interop-return.scala b/tests/explicit-nulls/neg/interop-return.scala new file mode 100644 index 000000000000..677d9528e6fa --- /dev/null +++ b/tests/explicit-nulls/neg/interop-return.scala @@ -0,0 +1,14 @@ + +// Test that the return type of Java methods as well as the type of Java fields is marked as nullable. +class Foo { + + def foo = { + import java.util.ArrayList + val x = new ArrayList[String]() + val r: String = x.get(0) // error: got String|JavaNull instead of String + + val x2 = new ArrayList[Int]() + val r2: Int = x2.get(0) // error: even though Int is non-nullable in Scala, its counterpart + // (for purposes of generics) in Java (Integer) is. So we're missing |JavaNull + } +} diff --git a/tests/explicit-nulls/neg/java-null.scala b/tests/explicit-nulls/neg/java-null.scala new file mode 100644 index 000000000000..884cd43745db --- /dev/null +++ b/tests/explicit-nulls/neg/java-null.scala @@ -0,0 +1,10 @@ +// Test that `JavaNull` is see-through, but `Null` isn't. + +class Test { + val s: String|Null = "hello" + val l = s.length // error: `Null` isn't "see-through" + + val s2: String|JavaNull = "world" + val l2 = s2.length // ok +} + diff --git a/tests/explicit-nulls/neg/null-subtype-any.scala b/tests/explicit-nulls/neg/null-subtype-any.scala new file mode 100644 index 000000000000..aa1ff441a601 --- /dev/null +++ b/tests/explicit-nulls/neg/null-subtype-any.scala @@ -0,0 +1,17 @@ +// Test that Null is a subtype of Any, but not of AnyRef. + +class Foo { + + val x1: Any = null + val x2: AnyRef = null // error + val x3: AnyRef|Null = null + val x4: Any|Null = null // Any|Null == Any + + { + def bar(a: Any): Unit = () + val s: String|Null = ??? + bar(s) + val s2: Int|Null = ??? + bar(s2) + } +} diff --git a/tests/explicit-nulls/neg/strip.scala b/tests/explicit-nulls/neg/strip.scala new file mode 100644 index 000000000000..1dbe1646005d --- /dev/null +++ b/tests/explicit-nulls/neg/strip.scala @@ -0,0 +1,16 @@ + +class Foo { + + class B1 + class B2 + + val x: (Null | String) | Null | (B1 | (Null | B2)) = ??? + if (x != null) { + val x2: String | B1 | B2 = x // ok: can remove all nullable unions + } + + val x2: (Null | String) & (Null | B1) = ??? + if (x2 != null) { + val x3: String & B1 = x2 // error: can't remove null from embedded intersection + } +} diff --git a/tests/explicit-nulls/neg/type-arg.scala b/tests/explicit-nulls/neg/type-arg.scala new file mode 100644 index 000000000000..c145ce562e6e --- /dev/null +++ b/tests/explicit-nulls/neg/type-arg.scala @@ -0,0 +1,13 @@ + +// Test that reference types being non-nullable +// is checked when lower bound of a type argument +// is Null. +object Test { + type Untyped = Null + class TreeInstances[T >: Untyped] + class Type + + object untpd extends TreeInstances[Null] + // There are two errors reported for the line below (don't know why). + object tpd extends TreeInstances[Type] // error // error +} diff --git a/tests/explicit-nulls/pos/array.scala b/tests/explicit-nulls/pos/array.scala new file mode 100644 index 000000000000..f3146c8e8e2b --- /dev/null +++ b/tests/explicit-nulls/pos/array.scala @@ -0,0 +1,5 @@ +// Test that array contents are non-nullable. +class Foo { + val x: Array[String] = Array("hello") + val s: String = x(0) +} diff --git a/tests/explicit-nulls/pos/dont-widen-singleton.scala b/tests/explicit-nulls/pos/dont-widen-singleton.scala new file mode 100644 index 000000000000..bcd3df969e23 --- /dev/null +++ b/tests/explicit-nulls/pos/dont-widen-singleton.scala @@ -0,0 +1,9 @@ + +// Test that we correctly handle nullable unions when widening +// (we don't widen them). +class Test { + def foo(): Unit = { + val x: String|Null = ??? + val y = x // this used to crash the compiler + } +} diff --git a/tests/explicit-nulls/pos/dont-widen-src/J.java b/tests/explicit-nulls/pos/dont-widen-src/J.java new file mode 100644 index 000000000000..c957a1f307b6 --- /dev/null +++ b/tests/explicit-nulls/pos/dont-widen-src/J.java @@ -0,0 +1,3 @@ +class J { + String foo() { return "hello"; } +} diff --git a/tests/explicit-nulls/pos/dont-widen-src/S.scala b/tests/explicit-nulls/pos/dont-widen-src/S.scala new file mode 100644 index 000000000000..0fbca30fac0a --- /dev/null +++ b/tests/explicit-nulls/pos/dont-widen-src/S.scala @@ -0,0 +1,7 @@ +class S { + val j = new J() + val x = j.foo() + // Check that the type of `x` is inferred to be `String|Null`. + // i.e. the union isn't collapsed. + val y: String|Null = x +} diff --git a/tests/explicit-nulls/pos/dont-widen.scala b/tests/explicit-nulls/pos/dont-widen.scala new file mode 100644 index 000000000000..e35615f7079a --- /dev/null +++ b/tests/explicit-nulls/pos/dont-widen.scala @@ -0,0 +1,8 @@ + +class S { + def foo[T](x: T): T = x + // Check that the type argument to `foo` is inferred to be + // `String|Null`: i.e. it isn't collapsed. + val x = foo(if (1 == 2) "hello" else null) + val y: String|Null = x +} diff --git a/tests/explicit-nulls/pos/flow-singleton.scala b/tests/explicit-nulls/pos/flow-singleton.scala new file mode 100644 index 000000000000..b329a25370b0 --- /dev/null +++ b/tests/explicit-nulls/pos/flow-singleton.scala @@ -0,0 +1,9 @@ +// Test that flow typing works well with singleton types. + +class Test { + val x : String | Null = ??? + if (x != null) { + val y: x.type = x + y.toLowerCase // ok: y should have type `String` in this branch + } +} diff --git a/tests/explicit-nulls/pos/flow.scala b/tests/explicit-nulls/pos/flow.scala new file mode 100644 index 000000000000..bbe42a923b4d --- /dev/null +++ b/tests/explicit-nulls/pos/flow.scala @@ -0,0 +1,160 @@ + +// Flow-sensitive type inference +class Foo { + + def basic() = { + class Bar { + val s: String = ??? + } + + val b: Bar|Null = ??? + if (b != null) { + val s = b.s // ok: type of `b` inferred as `Bar` + val s2: Bar = b + } else { + } + } + + def nestedAndSelection() = { + class Bar2 { + val x: Bar2|Null = ??? + } + + val bar2: Bar2|Null = ??? + if (bar2 != null) { + if (bar2.x != null) { + if (bar2.x.x != null) { + if (bar2.x.x.x != null) { + val b2: Bar2 = bar2.x.x.x + } + val b2: Bar2 = bar2.x.x + } + val b2: Bar2 = bar2.x + } + val b2: Bar2 = bar2 + } + } + + def ifThenElse() = { + val s: String|Null = ??? + if (s == null) { + } else { + val len: Int = s.length + val len2 = s.length + } + } + + + def elseIf() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + if (s1 != null) { + val len = s1.length + } else if (s2 != null) { + val len = s2.length + } else if (s3 != null) { + val len = s3.length + } + + // Accumulation in elseif + if (s1 == null) { + } else if (s2 == null) { + val len = s1.length + } else if (s3 == null) { + val len1 = s1.length + val len2 = s2.length + } else { + val len1 = s1.length + val len2 = s2.length + val len3 = s3.length + } + } + + def commonIdioms() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + + if (s1 == null || s2 == null || s3 == null) { + } else { + val len1: Int = s1.length + val len2: Int = s2.length + val len3: Int = s3.length + } + + if (s1 != null && s2 != null && s3 != null) { + val len1: Int = s1.length + val len2: Int = s2.length + val len3: Int = s3.length + } + } + + def basicNegation() = { + val s1: String|Null = ??? + + if (!(s1 != null)) { + } else { + val len = s1.length + } + + if (!(!(!(!(s1 != null))))) { + val len1 = s1.length + } + } + + def parens() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + + if ((((s1 == null))) || s2 == null) { + } else { + val len1 = s1.length + val len2 = s2.length + } + } + + def operatorPrecedence() = { + val s1: String|Null = ??? + val s2: String|Null = ??? + val s3: String|Null = ??? + + if (s1 != null && (s2 != null || s3 != null)) { + val len1 = s1.length + } + } + + def propInsideCond() = { + val s: String|Null = ??? + if (s != null && s.length > 0) { + val len: Int = s.length + } + + if (s == null || s.length == 0) { + } else { + val len: Int = s.length + } + + class Rec { + val r: Rec|Null = ??? + } + + val r: Rec|Null = ??? + if (r != null && r.r != null && r.r.r != null && (r.r.r.r != null) && r.r.r.r.r != null) { + val r6: Rec|Null = r.r.r.r.r.r + } + + if (r == null || r.r == null || r.r.r == null || (r.r.r.r == null) || r.r.r.r.r == null) { + } else { + val r6: Rec|Null = r.r.r.r.r.r + } + + if (!(r == null) && r.r != null) { + val r3: Rec|Null = r.r.r + } + } + + def interactWithTypeInference() = { + val f: String|Null => Int = (x) => if (x != null) x.length else 0 + } +} diff --git a/tests/explicit-nulls/pos/flow2.scala b/tests/explicit-nulls/pos/flow2.scala new file mode 100644 index 000000000000..2391da60b3be --- /dev/null +++ b/tests/explicit-nulls/pos/flow2.scala @@ -0,0 +1,11 @@ + +class Foo { + + val x: String|Null = ??? + val y: String|Null = ??? + val z: String|Null = ??? + + if ((x != null && z != null) || (y != null && z != null)) { + val z2: String = z + } +} diff --git a/tests/explicit-nulls/pos/flow3.scala b/tests/explicit-nulls/pos/flow3.scala new file mode 100644 index 000000000000..21198e09a748 --- /dev/null +++ b/tests/explicit-nulls/pos/flow3.scala @@ -0,0 +1,9 @@ + +// Test that flow inference can look inside type ascriptions. +// This is useful in combination with inlining (because inlined methods have an ascribed return type). +class Foo { + val x: String|Null = "hello" + if ((x != null): Boolean) { + val y = x.length + } +} diff --git a/tests/explicit-nulls/pos/flow4.scala b/tests/explicit-nulls/pos/flow4.scala new file mode 100644 index 000000000000..ef1c8b0c7ab9 --- /dev/null +++ b/tests/explicit-nulls/pos/flow4.scala @@ -0,0 +1,24 @@ + +// This test is based on tests/pos/rbtree.scala +// and it tests that we can use an inline method to "abstract" a more complicated +// isInstanceOf check, while at the same time getting the flow inference to know +// that `isRedTree(tree) => tree ne null`. +class TreeOps { + abstract class Tree[A, B](val key: A, val value: B) + class RedTree[A, B](override val key: A, override val value: B) extends Tree[A, B](key, value) + + private[this] inline def isRedTree(tree: Tree[_, _]) = (tree ne null) && tree.isInstanceOf[RedTree[_, _]] + + def foo[A, B](tree: Tree[A, B]): Unit = { + if (isRedTree(tree)) { + val key = tree.key + val value = tree.value + } + + if (!isRedTree(tree)) { + } else { + val key = tree.key + val value = tree.value + } + } +} diff --git a/tests/explicit-nulls/pos/flow5.scala b/tests/explicit-nulls/pos/flow5.scala new file mode 100644 index 000000000000..7fe74fee7a76 --- /dev/null +++ b/tests/explicit-nulls/pos/flow5.scala @@ -0,0 +1,19 @@ +// Test that flow inference handles `eq/ne`. + +class Test { + val x: String|Null = ??? + + if (!(x eq null)) { + val y = x.length + } + + if (x eq null) { + } else { + val y = x.length + } + + if (x ne null) { + val y = x.length + } +} + diff --git a/tests/explicit-nulls/pos/interop-constructor-src/J.java b/tests/explicit-nulls/pos/interop-constructor-src/J.java new file mode 100644 index 000000000000..b1590d50023e --- /dev/null +++ b/tests/explicit-nulls/pos/interop-constructor-src/J.java @@ -0,0 +1,6 @@ +class J { + private String s; + + J(String x) { this.s = x; } + J(String x, String y, String z) {} +} diff --git a/tests/explicit-nulls/pos/interop-constructor-src/S.scala b/tests/explicit-nulls/pos/interop-constructor-src/S.scala new file mode 100644 index 000000000000..6cbfea9b57b1 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-constructor-src/S.scala @@ -0,0 +1,6 @@ + +class S { + val x: J = new J("hello") + val x2: J = new J(null) + val x3: J = new J(null, null, null) +} diff --git a/tests/explicit-nulls/pos/interop-constructor.scala b/tests/explicit-nulls/pos/interop-constructor.scala new file mode 100644 index 000000000000..1f631e6efff6 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-constructor.scala @@ -0,0 +1,7 @@ + +// Test that constructors have a non-nullab.e return type. +class Foo { + val x: java.lang.String = new java.lang.String() + val y: java.util.Date = new java.util.Date() + val v = new java.util.Vector[String](null /*stands for Collection*/) +} diff --git a/tests/explicit-nulls/pos/interop-enum-src/Day.java b/tests/explicit-nulls/pos/interop-enum-src/Day.java new file mode 100644 index 000000000000..55dca0783931 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-enum-src/Day.java @@ -0,0 +1,6 @@ + +public enum Day { + SUN, + MON, + TUE +} diff --git a/tests/explicit-nulls/pos/interop-enum-src/Planet.java b/tests/explicit-nulls/pos/interop-enum-src/Planet.java new file mode 100644 index 000000000000..287aed6aecc5 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-enum-src/Planet.java @@ -0,0 +1,19 @@ +public enum Planet { + MERCURY (3.303e+23, 2.4397e6), + VENUS (4.869e+24, 6.0518e6), + EARTH (5.976e+24, 6.37814e6), + MARS (6.421e+23, 3.3972e6), + JUPITER (1.9e+27, 7.1492e7), + SATURN (5.688e+26, 6.0268e7), + URANUS (8.686e+25, 2.5559e7), + NEPTUNE (1.024e+26, 2.4746e7); + + private final double mass; // in kilograms + private final double radius; // in meters + Planet(double mass, double radius) { + this.mass = mass; + this.radius = radius; + } + private double mass() { return mass; } + private double radius() { return radius; } +} diff --git a/tests/explicit-nulls/pos/interop-enum-src/S.scala b/tests/explicit-nulls/pos/interop-enum-src/S.scala new file mode 100644 index 000000000000..75e4654869a4 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-enum-src/S.scala @@ -0,0 +1,6 @@ + +// Verify that enum values aren't nullified. +class S { + val d: Day = Day.MON + val p: Planet = Planet.MARS +} diff --git a/tests/explicit-nulls/pos/interop-generics/J.java b/tests/explicit-nulls/pos/interop-generics/J.java new file mode 100644 index 000000000000..b8eab374844b --- /dev/null +++ b/tests/explicit-nulls/pos/interop-generics/J.java @@ -0,0 +1,9 @@ + +class I {} + +class J { + I foo(T x) { + return new I(); + } + // TODO(abeln): test returning a Scala generic from Java +} diff --git a/tests/explicit-nulls/pos/interop-generics/S.scala b/tests/explicit-nulls/pos/interop-generics/S.scala new file mode 100644 index 000000000000..8c33ba3f0368 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-generics/S.scala @@ -0,0 +1,7 @@ +class ReturnedFromJava[T] {} + +class S { + val j = new J() + // Check that the inside of a Java generic isn't nullified + val i: I[String]|Null = j.foo("hello") +} diff --git a/tests/explicit-nulls/pos/interop-javanull-src/J.java b/tests/explicit-nulls/pos/interop-javanull-src/J.java new file mode 100644 index 000000000000..a85afa17c859 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-javanull-src/J.java @@ -0,0 +1,8 @@ + +class J1 { + J2 getJ2() { return new J2(); } +} + +class J2 { + J1 getJ1() { return new J1(); } +} diff --git a/tests/explicit-nulls/pos/interop-javanull-src/S.scala b/tests/explicit-nulls/pos/interop-javanull-src/S.scala new file mode 100644 index 000000000000..0f5c51a18ccc --- /dev/null +++ b/tests/explicit-nulls/pos/interop-javanull-src/S.scala @@ -0,0 +1,6 @@ + +// Test that JavaNull is "see through" +class S { + val j: J2 = new J2() + j.getJ1().getJ2().getJ1().getJ2().getJ1().getJ2() +} diff --git a/tests/explicit-nulls/pos/interop-javanull.scala b/tests/explicit-nulls/pos/interop-javanull.scala new file mode 100644 index 000000000000..636475166cbf --- /dev/null +++ b/tests/explicit-nulls/pos/interop-javanull.scala @@ -0,0 +1,10 @@ + +// Tests that the "JavaNull" type added to Java types is "see through" w.r.t member selections. +class Foo { + import java.util.ArrayList + import java.util.Iterator + + // Test that we can select through "|JavaNull" (unsoundly). + val x3 = new ArrayList[ArrayList[ArrayList[String]]]() + val x4: Int = x3.get(0).get(0).get(0).length() +} diff --git a/tests/explicit-nulls/pos/interop-nn-src/J.java b/tests/explicit-nulls/pos/interop-nn-src/J.java new file mode 100644 index 000000000000..96ac77a528f5 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-nn-src/J.java @@ -0,0 +1,4 @@ +class J { + String foo() { return "hello"; } + String[] bar() { return null; } +} diff --git a/tests/explicit-nulls/pos/interop-nn-src/S.scala b/tests/explicit-nulls/pos/interop-nn-src/S.scala new file mode 100644 index 000000000000..819f080eab0c --- /dev/null +++ b/tests/explicit-nulls/pos/interop-nn-src/S.scala @@ -0,0 +1,15 @@ +class S { + val j = new J() + // Test that the `nn` extension method can be used to strip away + // nullability from a type. + val s: String = j.foo.nn + val a: Array[String|Null] = j.bar.nn + + // We can also call .nn on non-nullable types. + val x: String = ??? + val y: String = x.nn + + // And on other Scala code. + val x2: String|Null = null + val y2: String = x2.nn +} diff --git a/tests/explicit-nulls/pos/interop-poly-src/J.java b/tests/explicit-nulls/pos/interop-poly-src/J.java new file mode 100644 index 000000000000..03e146942f58 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-poly-src/J.java @@ -0,0 +1,14 @@ + +class JavaCat { + T prop; +} + +class J { + static ScalaCat getScalaCat() { + return null; + } + + static JavaCat getJavaCat() { + return null; + } +} diff --git a/tests/explicit-nulls/pos/interop-poly-src/S.scala b/tests/explicit-nulls/pos/interop-poly-src/S.scala new file mode 100644 index 000000000000..868ddce0cc98 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-poly-src/S.scala @@ -0,0 +1,14 @@ +// Test the handling of generics by the nullability transform. +// There are two classes here: JavaCat is Java-defined, and ScalaCat +// is Scala-defined. + +class ScalaCat[T] {} + +class Test { + // It's safe to return a JavaCat[String]|Null (no inner |Null), + // because JavaCat, being a Java class, _already_ nullifies its + // fields. + val jc: JavaCat[String]|Null = J.getJavaCat[String]() + // ScalaCat is Java-defined, so we need the inner |Null. + val sc: ScalaCat[String|Null]|Null = J.getScalaCat[String]() +} diff --git a/tests/explicit-nulls/pos/interop-static-src/J.java b/tests/explicit-nulls/pos/interop-static-src/J.java new file mode 100644 index 000000000000..10965aa9ef4c --- /dev/null +++ b/tests/explicit-nulls/pos/interop-static-src/J.java @@ -0,0 +1,4 @@ + +class J { + static int foo(String s) { return 42; } +} diff --git a/tests/explicit-nulls/pos/interop-static-src/S.scala b/tests/explicit-nulls/pos/interop-static-src/S.scala new file mode 100644 index 000000000000..e54a33cd175b --- /dev/null +++ b/tests/explicit-nulls/pos/interop-static-src/S.scala @@ -0,0 +1,6 @@ + +class S { + + J.foo(null) // Java static methods are also nullified + +} diff --git a/tests/explicit-nulls/pos/interop-tostring.scala b/tests/explicit-nulls/pos/interop-tostring.scala new file mode 100644 index 000000000000..75c90150dd05 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-tostring.scala @@ -0,0 +1,9 @@ +// Test that `toString` has been special-cased to +// return a non-nullable value. + +class Foo { + val x: java.lang.Integer = 42 + val y: String = x.toString // would fail if toString returns nullable value + val y2 = x.toString // test interaction with type inference + val z: String = y2 +} diff --git a/tests/explicit-nulls/pos/interop-valuetypes.scala b/tests/explicit-nulls/pos/interop-valuetypes.scala new file mode 100644 index 000000000000..595a7de8917a --- /dev/null +++ b/tests/explicit-nulls/pos/interop-valuetypes.scala @@ -0,0 +1,6 @@ + +// Tests that value (non-reference) types aren't nullified by the Java transform. +class Foo { + val x: java.lang.String = "" + val len: Int = x.length() // type is Int and not Int|JavaNull +} diff --git a/tests/explicit-nulls/pos/java-null.scala b/tests/explicit-nulls/pos/java-null.scala new file mode 100644 index 000000000000..3739ddc138a1 --- /dev/null +++ b/tests/explicit-nulls/pos/java-null.scala @@ -0,0 +1,16 @@ +// Test that `JavaNull`able unions are transparent +// w.r.t member selections. + +class Test { + val s: String|JavaNull = "hello" + val l: Int = s.length // ok: `JavaNull` allows (unsound) member selections. + + val s2: JavaNull|String = "world" + val l2: Int = s2.length + + val s3: JavaNull|String|JavaNull = "hello" + val l3: Int = s3.length + + val s4: (String|JavaNull)&(JavaNull|String) = "hello" + val l4 = s4.length +} diff --git a/tests/explicit-nulls/pos/nn.scala b/tests/explicit-nulls/pos/nn.scala new file mode 100644 index 000000000000..9682999f9fdc --- /dev/null +++ b/tests/explicit-nulls/pos/nn.scala @@ -0,0 +1,20 @@ +// Check that the `.nn` extension method strips away nullability. + +class Test { + val s1: String|Null = ??? + val s2: String = s1.nn + + type NString = String|Null + val s3: NString = ??? + val s4: String = s3.nn + + // `.nn` is a no-op when called on value types + val b1: Boolean = true + val b2: Boolean = b1.nn + + // Check that `.nn` interacts well with type inference. + def foo(s: String): String = s + val s5: String|Null = "hello" + val s6 = s5.nn + foo(s6) +} diff --git a/tests/explicit-nulls/pos/nn2.scala b/tests/explicit-nulls/pos/nn2.scala new file mode 100644 index 000000000000..417d8855e405 --- /dev/null +++ b/tests/explicit-nulls/pos/nn2.scala @@ -0,0 +1,10 @@ + +// Test that is fixed when explicit nulls are enabled. +// https://github.com/lampepfl/dotty/issues/6247 + +class Foo { + val x1: String|Null = null + x1.nn.length + val x2: String = x1.nn + x1.nn.length +} diff --git a/tests/explicit-nulls/pos/nullable-union.scala b/tests/explicit-nulls/pos/nullable-union.scala new file mode 100644 index 000000000000..5e63b5adef45 --- /dev/null +++ b/tests/explicit-nulls/pos/nullable-union.scala @@ -0,0 +1,14 @@ +// Test that nullable types can be represented via unions. + +class Bar + +class Foo { + val x: String|Null = null + val y: Array[String]|Null = null + val b: Null|Bar = null + + def foo(p: Bar|Null): String|Null = null + + foo(null) + foo(b) +} diff --git a/tests/explicit-nulls/pos/pattern-matching.scala b/tests/explicit-nulls/pos/pattern-matching.scala new file mode 100644 index 000000000000..7e84fb8cd513 --- /dev/null +++ b/tests/explicit-nulls/pos/pattern-matching.scala @@ -0,0 +1,38 @@ + +class Foo { + val s: String = ??? + s match { + case s: String => 100 // warning: type test will always succeed + case _ => 200 // warning: unreachable + } + + s match { + case s: String => 100 // warning: type test will always succeed + case _ => 200 // warning: unreachable + } + + sealed trait Animal + case class Dog(name: String) extends Animal + case object Cat extends Animal + + val a: Animal = ??? + a match { + case Dog(name) => 100 + case Cat => 200 + case _ => 300 // warning: unreachable + } + + val a2: Animal|Null = ??? + a2 match { + case Dog(_) => 100 + case Cat => 200 + case _ => 300 // warning: only matches null + } + + val a3: Animal|Null = ??? + a3 match { + case Dog(_) => 100 + case Cat => 200 + case null => 300 // ok + } +} diff --git a/tests/explicit-nulls/pos/ref-eq.scala b/tests/explicit-nulls/pos/ref-eq.scala new file mode 100644 index 000000000000..340c687c7d70 --- /dev/null +++ b/tests/explicit-nulls/pos/ref-eq.scala @@ -0,0 +1,31 @@ +// Test reference equality. + +class Test { + val x1: String = "hello" + val x2: String|Null = null + + // Methods in AnyRef + x1.eq(x1) + x1.ne(1) + x1.eq(null) + x1.ne(null) + x1.eq(1) // ok: implicit conversion from int to Integer + x1.ne(1) // ok: ditto + x1.eq(x2) + x1.ne(x2) + + // Extension methods + null.eq("hello") + null.ne("hello") + null.eq(null) + null.ne(null) + null.eq(x2) + null.ne(x2) + + x2.eq(null) + x2.ne(null) + x2.eq(x1) + x2.ne(x1) + x2.eq(x2) + x2.ne(x2) +} diff --git a/tests/explicit-nulls/pos/throw-null.scala b/tests/explicit-nulls/pos/throw-null.scala new file mode 100644 index 000000000000..54def30492d2 --- /dev/null +++ b/tests/explicit-nulls/pos/throw-null.scala @@ -0,0 +1,8 @@ +// Check that `throws null` still typechecks. +// https://stackoverflow.com/questions/17576922/why-can-i-throw-null-in-java + +class Foo { + throw null // throws an npe + val npe: NullPointerException|Null = ??? + throw npe +} diff --git a/tests/explicit-nulls/pos/tref-caching.scala b/tests/explicit-nulls/pos/tref-caching.scala new file mode 100644 index 000000000000..7a4c3bc412ea --- /dev/null +++ b/tests/explicit-nulls/pos/tref-caching.scala @@ -0,0 +1,19 @@ + +// Exercise code paths for different types of cached term refs. +// Specifically, `NonNullTermRef`s are cached separately from regular `TermRefs`. +// If the two kinds of trefs weren't cached separately, then the code below would +// error out, because every time `x` is accessed the nullable or non-null denotation +// would replace the other one, causing errors during -Ychecks. +class Test { + def foo(): Unit = { + val x: String|Null = ??? // regular tref `x` + if (x != null) { + val y = x.length // non-null tref `x` + x.length // 2nd access to non-null tref `x` + val z = x.length // 3rd access to non-null tref `x` + } else { + val y = x // regular tref `x` + } + val x2 = x // regular tref `x` + } +} diff --git a/tests/explicit-nulls/run/flow-extension-methods.scala b/tests/explicit-nulls/run/flow-extension-methods.scala new file mode 100644 index 000000000000..840402e78577 --- /dev/null +++ b/tests/explicit-nulls/run/flow-extension-methods.scala @@ -0,0 +1,44 @@ +// Test the `eq` and `ne` extension methods. +object Test { + def main(args: Array[String]): Unit = { + val x: String|Null = "hello" + if (x ne null) { + assert(x.length == 5) + } else { + assert(false) + } + + val y: String|Null = null + if (y eq null) { + assert(true) + } else { + assert(y.length == 5) + } + + class Foo { + val sz: Int = 42 + } + + // Now test the `AnyRef` methods. + val s: Foo = null.asInstanceOf[Foo] + if (s eq null) { + assert(true) + } else { + assert(false) + } + + val s2 = new Foo + if (s2 ne null) { + assert(true) + } else { + assert(false) + } + + // Now test the extension methods on null itself + assert(null eq null) + assert(!(null ne null)) + assert(null ne "hello") + assert(null eq y) + assert(null ne x) + } +} diff --git a/tests/explicit-nulls/run/flow.check b/tests/explicit-nulls/run/flow.check new file mode 100644 index 000000000000..02a42f1b5dd8 --- /dev/null +++ b/tests/explicit-nulls/run/flow.check @@ -0,0 +1 @@ +npe diff --git a/tests/explicit-nulls/run/flow.scala b/tests/explicit-nulls/run/flow.scala new file mode 100644 index 000000000000..7fa56e046c5a --- /dev/null +++ b/tests/explicit-nulls/run/flow.scala @@ -0,0 +1,30 @@ +// Test that flow-sensitive type inference handles +// early exists from blocks. +object Test { + def main(args: Array[String]): Unit = { + check("hello") + check("world") + check2("blocks") + try { + check(null) + } catch { + case npe: NullPointerException => + println("npe") + } + } + + def err(msg: String) = throw new NullPointerException(msg) + + def check(s: String|Null): String = { + if (s == null) err("null argument!") + s + } + + // Test that flow info is propagated to vals, but not to defs. + def check2(s: String|Null): String = { + if (s == null) err("null argument") + val s2 = s + def s3 = s.nn // need the "nn" + s2 ++ s3 + } +} diff --git a/tests/explicit-nulls/run/generic-java-array-src/JA.java b/tests/explicit-nulls/run/generic-java-array-src/JA.java new file mode 100644 index 000000000000..ccca309d4f49 --- /dev/null +++ b/tests/explicit-nulls/run/generic-java-array-src/JA.java @@ -0,0 +1,13 @@ +class JA { + public static T get(T[] arr) { + return arr[0]; + } + + public static int getInt(int[] arr) { + return arr[0]; + } + + public static boolean getBool(boolean[] arr) { + return arr[0]; + } +} diff --git a/tests/explicit-nulls/run/generic-java-array-src/Test.scala b/tests/explicit-nulls/run/generic-java-array-src/Test.scala new file mode 100644 index 000000000000..22cc5ea1eb91 --- /dev/null +++ b/tests/explicit-nulls/run/generic-java-array-src/Test.scala @@ -0,0 +1,21 @@ +object Test { + def main(args: Array[String]): Unit = { + // This test shows that if we have a Java method that takes a generic array, + // then on the Scala side we'll need to pass a nullable array. + // i.e. with explicit nulls the previously-implicit cast becomes an explicit + // type annotation. + val x = new Array[Int|Null](1) + x(0) = 10 + println(JA.get(x)) + + // However, if the Java method takes an array that's explicitly of a value type, + // then we can pass a non-nullable array from the Scala side. + val intArr = new Array[Int](1) + intArr(0) = 20 + println(JA.getInt(intArr)) + + val boolArr = new Array[Boolean](1) + boolArr(0) = true + println(JA.getBool(boolArr)) + } +} diff --git a/tests/explicit-nulls/run/instanceof-nothing.scala b/tests/explicit-nulls/run/instanceof-nothing.scala new file mode 100644 index 000000000000..e51aabc7fe00 --- /dev/null +++ b/tests/explicit-nulls/run/instanceof-nothing.scala @@ -0,0 +1,25 @@ +// Check that calling `asInstanceOf[Nothing]` throws a ClassCastException. +// In particular, the compiler needs access to the right method to throw +// the exception, and identifying the method uses some explicit nulls related +// logic (see ClassCastExceptionClass in Definitions.scala). +object Test { + def main(args: Array[String]): Unit = { + val x: String = "hello" + try { + val y: Nothing = x.asInstanceOf[Nothing] + assert(false) + } catch { + case e: ClassCastException => + // ok + } + + val n: Null = null + try { + val y: Nothing = n.asInstanceOf[Nothing] + assert(false) + } catch { + case e: ClassCastException => + // ok + } + } +} diff --git a/tests/explicit-nulls/run/interop-unsound-src/J.java b/tests/explicit-nulls/run/interop-unsound-src/J.java new file mode 100644 index 000000000000..e06b22c3bae2 --- /dev/null +++ b/tests/explicit-nulls/run/interop-unsound-src/J.java @@ -0,0 +1,17 @@ + +class JavaBox { + T contents; + + JavaBox(T contents) { this.contents = contents; } +} + +class Forwarder { + + static > void putInJavaBox(T box, String s) { + box.contents = s; + } + + static > void putInScalaBox(T box, String s) { + box.setContents(s); + } +} diff --git a/tests/explicit-nulls/run/interop-unsound-src/S.scala b/tests/explicit-nulls/run/interop-unsound-src/S.scala new file mode 100644 index 000000000000..2e5eca0c1e5b --- /dev/null +++ b/tests/explicit-nulls/run/interop-unsound-src/S.scala @@ -0,0 +1,33 @@ +// An example that shows that the nullability transform is unsound. + +class ScalaBox[T](init: T) { + var contents: T = init + + def setContents(c: T): Unit = { + contents = c + } +} + +object Test { + + def main(args: Array[String]): Unit = { + val jb: JavaBox[String] = new JavaBox("hello") + val sb: ScalaBox[String] = ScalaBox("world") + + Forwarder.putInJavaBox(jb, null) // not unsound, becase JavaBox is java-defined + // so the contents fields will have a nullable + // type + + Forwarder.putInScalaBox(sb, null) // this is unsound, because ScalaBox + // should contain only Strings, but we added + // a null + + try { + sb.contents.length + assert(false) + } catch { + case ex: NullPointerException => + // expected + } + } +} diff --git a/tests/explicit-nulls/run/java-null.scala b/tests/explicit-nulls/run/java-null.scala new file mode 100644 index 000000000000..39eb1668d9d5 --- /dev/null +++ b/tests/explicit-nulls/run/java-null.scala @@ -0,0 +1,17 @@ +// Check that selecting a member from a `JavaNull`able union is unsound. + +object Test { + def main(args: Array[String]): Unit = { + val s: String|JavaNull = "hello" + assert(s.length == 5) + + val s2: String|JavaNull = null + try { + s2.length // should throw + assert(false) + } catch { + case e: NullPointerException => + // ok: selecting on a JavaNull can throw + } + } +} diff --git a/tests/explicit-nulls/run/nn.scala b/tests/explicit-nulls/run/nn.scala new file mode 100644 index 000000000000..3ffff69649cf --- /dev/null +++ b/tests/explicit-nulls/run/nn.scala @@ -0,0 +1,21 @@ +// Test the `nn` extension method for removing nullability. +object Test { + def len(x: Array[String]|Null): Unit = x.nn.length + def load(x: Array[String]|Null): Unit = x.nn(0) + + def assertThrowsNPE(x: => Any) = try { + x; + assert(false) // failed to throw NPE + } catch { case _: NullPointerException => } + + def main(args: Array[String]): Unit = { + assert(42.nn == 42) + val x: String|Null = "hello" + assert(x.nn == "hello") + val y: String|Null = null + assertThrowsNPE(y.nn) + assertThrowsNPE(null.nn) + assertThrowsNPE(len(null)) + assertThrowsNPE(load(null)) + } +} diff --git a/tests/explicit-nulls/run/subtype-any.scala b/tests/explicit-nulls/run/subtype-any.scala new file mode 100644 index 000000000000..31a5bb66f092 --- /dev/null +++ b/tests/explicit-nulls/run/subtype-any.scala @@ -0,0 +1,28 @@ + +object Test { + + def main(args: Array[String]): Unit = { + assert(null.eq(null)) + assert(!null.ne(null)) + + assert(!null.eq("hello")) + assert(null.ne("hello")) + + assert(!null.eq(4)) + assert(null.ne(4)) + + assert(!"hello".eq(null)) + assert("hello".ne(null)) + + assert(!4.eq(null)) + assert(4.ne(null)) + + val x: String|Null = null + assert(x.eq(null)) + assert(!x.ne(null)) + + val x2: AnyRef|Null = "world" + assert(!x2.eq(null)) + assert(x2.ne(null)) + } +} diff --git a/tests/pos/interop-tostring.scala b/tests/pos/interop-tostring.scala new file mode 100644 index 000000000000..6d4798badfa2 --- /dev/null +++ b/tests/pos/interop-tostring.scala @@ -0,0 +1,8 @@ + +// Check that the return type of toString() isn't nullable. +class Foo { + + val x: java.lang.Integer = 42 + val s: String = x.toString() + +} diff --git a/tests/pos/interop-type-field.scala b/tests/pos/interop-type-field.scala new file mode 100644 index 000000000000..69c5fadef819 --- /dev/null +++ b/tests/pos/interop-type-field.scala @@ -0,0 +1,5 @@ + +class S { + // verify that the special TYPE field is non-nullable + val x: Class[Integer] = java.lang.Integer.TYPE +} From 07d18820e9758b9d70d5ec9873086f3b227e4065 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 19 Sep 2019 13:42:07 -0400 Subject: [PATCH 02/54] remove ValDefInBlockCompleter --- .../src/dotty/tools/dotc/typer/Namer.scala | 53 +++---------------- .../src/dotty/tools/dotc/typer/Typer.scala | 22 ++++---- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 39dc3479e0a4..74fe92cd3305 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -376,17 +376,6 @@ class Namer { typer: Typer => case tree: TypeDef => if (flags.is(Opaque) && ctx.owner.isClass) ctx.owner.setFlag(Opaque) new TypeDefCompleter(tree)(cctx) - case tree: ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !tree.mods.isOneOf(Lazy | Implicit) => - // Use a completer that completes itself with the completion text, so - // we can use flow typing for `ValDef`s. - // Don't use this special kind of completer for `lazy` or `implicit` symbols, - // because those could be completed through a forward reference: - // e.g. - // val x: String|Null = ??? - // y /* y is completed through a forward reference! */ - // if (x == null) throw new NullPointerException() - // lazy val y: Int = x.length - new ValDefInBlockCompleter(tree)(cctx) case _ => new Completer(tree)(cctx) } @@ -758,18 +747,19 @@ class Namer { typer: Typer => /** The context in which the completer should be typed: usually it's the creation context, * but could also be the completion context. */ - protected def typingContext(owner: Symbol, completionCtx: Context): FreshContext = localContext(owner) + protected def typingContext(owner: Symbol, ctx: Context): FreshContext = + ctx.fresh.setOwner(owner).setTree(original) /** The context with which this completer was created */ def creationContext: Context = ctx ctx.typerState.markShared() - protected def typeSig(sym: Symbol, completionCtx: Context): Type = original match { + protected def typeSig(sym: Symbol, ctx: Context): Type = original match { case original: ValDef => if (sym.is(Module)) moduleValSig(sym) - else valOrDefDefSig(original, sym, Nil, Nil, identity)(typingContext(sym, completionCtx).setNewScope) + else valOrDefDefSig(original, sym, Nil, Nil, identity)(typingContext(sym, ctx).setNewScope) case original: DefDef => - val typer1 = ctx.typer.newLikeThis + val typer1 = this.ctx.typer.newLikeThis nestedTyper(sym) = typer1 typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1)) case imp: Import => @@ -797,7 +787,7 @@ class Namer { typer: Typer => denot.info = UnspecifiedErrorType } else { - completeInContext(denot, ctx) + completeInContext(denot, this.ctx) if (denot.isCompleted) registerIfChild(denot) } } @@ -884,45 +874,18 @@ class Namer { typer: Typer => * to pick up the context at the point where the completer was created. * * If -Yexplicit-nulls, is enabled, we sometimes use the completion context. - * See `ValDefInBlockCompleter`. */ - def completeInContext(denot: SymDenotation, completionContext: Context): Unit = { + def completeInContext(denot: SymDenotation, ctx: Context): Unit = { val sym = denot.symbol addAnnotations(sym) addInlineInfo(sym) - denot.info = typeSig(sym, completionContext) + denot.info = typeSig(sym, ctx) invalidateIfClashingSynthetic(denot) Checking.checkWellFormed(sym) denot.info = avoidPrivateLeaks(sym) } } - /** A completer that uses its completion context (as opposed to the creation context) - * to complete itself. This is used so that flow typing can handle `ValDef`s that appear within a block. - * - * Example: - * Suppose we have a block containing - * - * 1. val x: String|Null = ??? - * 2. if (x == null) throw NPE - * 3. val y = x - * - * We want to infer y: String on line 3, but if the completer for `y` uses its creation context, - * then we won't have the additional flow facts that say that `y` is not null. - * - * The solution is to use a completer that completes itself using the context at completion time, - * as opposed to creation time. Normally, we need to use the creation context, because a completer - * can be completed at any point in the future. However, for `ValDef`s within a block, we know they'll - * be completed immediately after the symbols are created, so it's safe to use this new kind of completer. - */ - class ValDefInBlockCompleter(original: ValDef)(implicit ctx: Context) extends Completer(original)(ctx) { - assert(ctx.explicitNulls) - - override def typingContext(owner: Symbol, completionCtx: Context): FreshContext = { - completionCtx.fresh.setOwner(owner).setTree(original) - } - } - class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter { private[this] var myTypeParams: List[TypeSymbol] = null private[this] var nestedCtx: Context = null diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 392721a5fdc3..af1faedc6455 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2213,16 +2213,20 @@ class Typer extends Namer traverse(xtree :: rest, facts) case none => import untpd.modsDeco - val ctx1 = if (ctx.explicitNulls) { - mdef match { - case mdef: untpd.ValDef if ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => - ctx.fresh.addFlowFacts(facts) - case _ => ctx - } - } else { - ctx + val ctx2 = mdef match { + case mdef: untpd.ValDef + if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => + val ctx1 = ctx.fresh.addFlowFacts(facts) + val sym = ctx1.effectiveScope.lookup(mdef.name) + sym.infoOrCompleter match { + case completer: Namer#Completer => + completer.completeInContext(sym, ctx1) + case _ => + } + ctx1 + case _ => ctx } - typed(mdef)(ctx1) match { + typed(mdef)(ctx2) match { case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) // replace body with expansion, because it will be used as inlined body From 9912940e4954c6c28d11bda98dc021d32e0981bd Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 20 Sep 2019 15:47:06 -0400 Subject: [PATCH 03/54] add comments --- .../src/dotty/tools/dotc/typer/Namer.scala | 11 +++--- .../src/dotty/tools/dotc/typer/Typer.scala | 34 +++++++++++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 74fe92cd3305..e71c646915e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -744,12 +744,6 @@ class Namer { typer: Typer => protected def localContext(owner: Symbol): FreshContext = ctx.fresh.setOwner(owner).setTree(original) - /** The context in which the completer should be typed: usually it's the creation context, - * but could also be the completion context. - */ - protected def typingContext(owner: Symbol, ctx: Context): FreshContext = - ctx.fresh.setOwner(owner).setTree(original) - /** The context with which this completer was created */ def creationContext: Context = ctx ctx.typerState.markShared() @@ -757,7 +751,10 @@ class Namer { typer: Typer => protected def typeSig(sym: Symbol, ctx: Context): Type = original match { case original: ValDef => if (sym.is(Module)) moduleValSig(sym) - else valOrDefDefSig(original, sym, Nil, Nil, identity)(typingContext(sym, ctx).setNewScope) + else { + val newScope = ctx.fresh.setOwner(sym).setTree(original).setNewScope + valOrDefDefSig(original, sym, Nil, Nil, identity)(newScope) + } case original: DefDef => val typer1 = this.ctx.typer.newLikeThis nestedTyper(sym) = typer1 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index af1faedc6455..88bef43eb413 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2213,14 +2213,36 @@ class Typer extends Namer traverse(xtree :: rest, facts) case none => import untpd.modsDeco + // Force completer to use calling context (as opposed to the creation context) + // to complete itself. This approach is used so that flow typing can handle `ValDef`s + // that appear within a block. + // + // Example: + // Suppose we have a block containing + // 1. val x: String|Null = ??? + // 2. if (x == null) throw NPE + // 3. val y = x + // + // We want to infer y: String on line 3, but if the completer for `y` uses its creation context, + // then we won't have the additional flow facts that say that `y` is not null. + // + // The solution is to use the context containing more information about the statements above. + // We use creation context whenever we need to force a completer from some different context, + // but use calling context if we get to a definition in a statement sequence that has not been + // completed yet. For `ValDef`s within a block, we know they'll be completed immediately after + // the symbols are created, so it's safe to complete it using calling context. val ctx2 = mdef match { - case mdef: untpd.ValDef - if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => + case mdef: untpd.ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => val ctx1 = ctx.fresh.addFlowFacts(facts) - val sym = ctx1.effectiveScope.lookup(mdef.name) - sym.infoOrCompleter match { - case completer: Namer#Completer => - completer.completeInContext(sym, ctx1) + // we cannot use mdef.symbol to get the symbol of the tree here + // since the tree has not been completed and doesn't have a denotation + mdef.getAttachment(SymOfTree) match { + case Some(sym) => + sym.infoOrCompleter match { + case completer: Namer#Completer => + completer.completeInContext(sym, ctx1) + case _ => + } case _ => } ctx1 From 36aca3596035db047e3d5eda4c93d7903e9a91a0 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 23 Sep 2019 14:07:16 -0400 Subject: [PATCH 04/54] remove implicit check for ValDef --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/explicit-nulls/neg/flow6.scala | 2 +- tests/explicit-nulls/neg/flow8.scala | 11 +++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 tests/explicit-nulls/neg/flow8.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 88bef43eb413..4ee40df58f84 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2232,7 +2232,8 @@ class Typer extends Namer // completed yet. For `ValDef`s within a block, we know they'll be completed immediately after // the symbols are created, so it's safe to complete it using calling context. val ctx2 = mdef match { - case mdef: untpd.ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) => + // Lazy is checked here because lazy ValDef is allowed between forward references + case mdef: untpd.ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy) => val ctx1 = ctx.fresh.addFlowFacts(facts) // we cannot use mdef.symbol to get the symbol of the tree here // since the tree has not been completed and doesn't have a denotation diff --git a/tests/explicit-nulls/neg/flow6.scala b/tests/explicit-nulls/neg/flow6.scala index 6d722984e8f2..242e4069842c 100644 --- a/tests/explicit-nulls/neg/flow6.scala +++ b/tests/explicit-nulls/neg/flow6.scala @@ -37,7 +37,7 @@ class Foo { def test4(): Unit = { val x: String|Null = ??? if (x == null) return () - implicit val y: Int = x.length // error: x: String|Null inferred + implicit val y: Int = x.length // ok: x: String inferred } // This case is different from #3 because the type of y doesn't need diff --git a/tests/explicit-nulls/neg/flow8.scala b/tests/explicit-nulls/neg/flow8.scala new file mode 100644 index 000000000000..75533c6962ec --- /dev/null +++ b/tests/explicit-nulls/neg/flow8.scala @@ -0,0 +1,11 @@ +class Foo { + + // This case is not valid but #4 in flow6 is valid, because non-lazy value definations + // exist between forward references + def fr(): Unit = { + val z = implicitly[Int] // error: forward reference is not allowed here + val x: String|Null = ??? + if (x == null) return () + implicit val y: Int = x.length + } +} From 46c3e16b1cb9610261d7f1fc8a900e566531cf71 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 24 Sep 2019 11:11:56 -0400 Subject: [PATCH 05/54] refine comments --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ---- tests/explicit-nulls/neg/flow8.scala | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4ee40df58f84..04dc6e2fdf2e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2227,10 +2227,6 @@ class Typer extends Namer // then we won't have the additional flow facts that say that `y` is not null. // // The solution is to use the context containing more information about the statements above. - // We use creation context whenever we need to force a completer from some different context, - // but use calling context if we get to a definition in a statement sequence that has not been - // completed yet. For `ValDef`s within a block, we know they'll be completed immediately after - // the symbols are created, so it's safe to complete it using calling context. val ctx2 = mdef match { // Lazy is checked here because lazy ValDef is allowed between forward references case mdef: untpd.ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy) => diff --git a/tests/explicit-nulls/neg/flow8.scala b/tests/explicit-nulls/neg/flow8.scala index 75533c6962ec..98a748f64fdc 100644 --- a/tests/explicit-nulls/neg/flow8.scala +++ b/tests/explicit-nulls/neg/flow8.scala @@ -1,6 +1,6 @@ class Foo { - // This case is not valid but #4 in flow6 is valid, because non-lazy value definations + // This case is not valid but #4 in flow6 is valid, because non-lazy value definitions // exist between forward references def fr(): Unit = { val z = implicitly[Int] // error: forward reference is not allowed here From e43963b211ad338093720e10ead7714ffe3947e6 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 25 Sep 2019 14:55:26 -0400 Subject: [PATCH 06/54] modify flow typing for all defs --- .../src/dotty/tools/dotc/typer/Namer.scala | 5 +- .../src/dotty/tools/dotc/typer/Typer.scala | 33 +++++---- tests/explicit-nulls/neg/flow6.scala | 60 +++------------- tests/explicit-nulls/neg/flow8.scala | 10 ++- tests/explicit-nulls/pos/flow6.scala | 72 +++++++++++++++++++ 5 files changed, 108 insertions(+), 72 deletions(-) create mode 100644 tests/explicit-nulls/pos/flow6.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e71c646915e3..cb4c017d1356 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -756,9 +756,10 @@ class Namer { typer: Typer => valOrDefDefSig(original, sym, Nil, Nil, identity)(newScope) } case original: DefDef => - val typer1 = this.ctx.typer.newLikeThis + val typer1 = ctx.typer.newLikeThis nestedTyper(sym) = typer1 - typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1)) + val withTyper = ctx.fresh.setOwner(sym).setTree(original).setTyper(typer1) + typer1.defDefSig(original, sym)(withTyper) case imp: Import => try { val expr1 = typedAheadExpr(imp.expr, AnySelectionProto) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 04dc6e2fdf2e..a0bd8913f073 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2227,24 +2227,23 @@ class Typer extends Namer // then we won't have the additional flow facts that say that `y` is not null. // // The solution is to use the context containing more information about the statements above. - val ctx2 = mdef match { - // Lazy is checked here because lazy ValDef is allowed between forward references - case mdef: untpd.ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy) => - val ctx1 = ctx.fresh.addFlowFacts(facts) - // we cannot use mdef.symbol to get the symbol of the tree here - // since the tree has not been completed and doesn't have a denotation - mdef.getAttachment(SymOfTree) match { - case Some(sym) => - sym.infoOrCompleter match { - case completer: Namer#Completer => - completer.completeInContext(sym, ctx1) - case _ => - } - case _ => - } - ctx1 - case _ => ctx + val ctx2 = if (ctx.explicitNulls && ctx.owner.is(Method)) { + val ctx1 = ctx.fresh.addFlowFacts(facts) + // we cannot use mdef.symbol to get the symbol of the tree here + // since the tree has not been completed and doesn't have a denotation + mdef.getAttachment(SymOfTree) match { + case Some(sym) => + sym.infoOrCompleter match { + case completer: Namer#Completer => + completer.completeInContext(sym, ctx1) + case _ => + } + case _ => + } + ctx1 } + else ctx + typed(mdef)(ctx2) match { case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) diff --git a/tests/explicit-nulls/neg/flow6.scala b/tests/explicit-nulls/neg/flow6.scala index 242e4069842c..1ebb1674238a 100644 --- a/tests/explicit-nulls/neg/flow6.scala +++ b/tests/explicit-nulls/neg/flow6.scala @@ -1,70 +1,28 @@ - -// Test that flow inference behaves soundly within blocks. -// This means that flow facts are propagated to ValDefs, but not -// to DefDefs or lazy vals or implicit vals. -// The reason is that forward references are allowed for `defs`, -// so when we see -// ``` -// f -// if (x == null) return "foo" -// def f = x.length -// ``` -// we can't assume that f is executed _after_ the if statement -// is executed. class Foo { def test1(): Unit = { val x: String|Null = ??? if (x == null) return () - val y = x.length // ok: x: String inferred + def y = x.length // ok: x: String inferred () } + // This test is similar to test1, but a forward DefDef referring + // to y is added after x. y is completed before knowing the + // fact "x != null", hence, the type x: String|Null is used. def test2(): Unit = { val x: String|Null = ??? + def z = y if (x == null) return () - def y = x.length // error: x: String|Null inferred + def y = x.length // error: x: String|Null is inferred () } def test3(): Unit = { val x: String|Null = ??? + lazy val z = y if (x == null) return () - lazy val y = x.length // error: x: String|Null inferred - () - } - - def test4(): Unit = { - val x: String|Null = ??? - if (x == null) return () - implicit val y: Int = x.length // ok: x: String inferred - } - - // This case is different from #3 because the type of y doesn't need - // to be inferred, which triggers a different codepath within the completer. - def test5(): Unit = { - val x: String|Null = ??? - if (x == null) return () - lazy val y: Int = x.length // error: x: String|Null inferred + lazy val y = x.length // error: x: String|Null is inferred () } - - def test6(): Unit = { - val x: String|Null = ??? - if (x == null) return () - def y: Int = x.length // error: x: String|Null inferred - () - } - - // This test checks that flow facts are forgotten for defs, but only - // the facts gathered within the current block are forgotten. - // Other facts from outer blocks are remembered. - def test7(): Unit = { - val x: String|Null = ??? - if (x == null) { - } else { - def f = x.length // ok - def f2: Int = x.length // ok - } - } -} +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg/flow8.scala b/tests/explicit-nulls/neg/flow8.scala index 98a748f64fdc..234a9d58c8b7 100644 --- a/tests/explicit-nulls/neg/flow8.scala +++ b/tests/explicit-nulls/neg/flow8.scala @@ -1,7 +1,13 @@ class Foo { - // This case is not valid but #4 in flow6 is valid, because non-lazy value definitions - // exist between forward references + def foo(): Unit = { + val x: String|Null = ??? + if (x == null) return () + implicit val y: Int = x.length + } + + // This case is not valid but foo() above is valid, because + // non-lazy value definitions exist between forward references def fr(): Unit = { val z = implicitly[Int] // error: forward reference is not allowed here val x: String|Null = ??? diff --git a/tests/explicit-nulls/pos/flow6.scala b/tests/explicit-nulls/pos/flow6.scala new file mode 100644 index 000000000000..a1be65575225 --- /dev/null +++ b/tests/explicit-nulls/pos/flow6.scala @@ -0,0 +1,72 @@ + +// Test that flow inference behaves soundly within blocks. +// This means that flow facts are propagated to all ValDef and DefDef. +class Foo { + + def test1(): Unit = { + val x: String|Null = ??? + if (x == null) return () + val y = x.length // ok: x: String inferred + () + } + + def test2(): Unit = { + val x: String|Null = ??? + if (x == null) return () + lazy val y = x.length // ok: x: String inferred + () + } + + def test3(): Unit = { + val x: String|Null = ??? + if (x == null) return () + implicit val y = x.length // ok: x: String inferred + () + } + + def test4(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y = x.length // ok: x: String inferred + () + } + + // This case is different from #3 because the type of y doesn't need + // to be inferred, which triggers a different codepath within the completer. + def test5(): Unit = { + val x: String|Null = ??? + if (x == null) return () + implicit val y: Int = x.length // ok: x: String inferred + } + + def test6(): Unit = { + val x: String|Null = ??? + if (x == null) return () + lazy val y: Int = x.length // ok: x: String inferred + () + } + + def test7(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y: Int = x.length // ok: x: String inferred + () + } + + def test8(): Unit = { + lazy val x: String|Null = ??? + if (x == null) return () + val y = x.length // ok: x: String inferred + () + } + + // This test checks facts from outer blocks are remembered. + def test9(): Unit = { + val x: String|Null = ??? + if (x == null) { + } else { + def f = x.length // ok + def f2: Int = x.length // ok + } + } +} From 4f9a437302a1bcdfd0b40aa806d753df4c12888b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 26 Sep 2019 16:01:17 -0400 Subject: [PATCH 07/54] fix flow typing when forward referred --- .../src/dotty/tools/dotc/typer/Namer.scala | 27 +++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 29 +++++++++---------- tests/explicit-nulls/neg/flow8.scala | 22 ++++++++++---- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index cb4c017d1356..766e2780fe22 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -752,8 +752,8 @@ class Namer { typer: Typer => case original: ValDef => if (sym.is(Module)) moduleValSig(sym) else { - val newScope = ctx.fresh.setOwner(sym).setTree(original).setNewScope - valOrDefDefSig(original, sym, Nil, Nil, identity)(newScope) + val withNewScope = ctx.fresh.setOwner(sym).setTree(original).setNewScope + valOrDefDefSig(original, sym, Nil, Nil, identity)(withNewScope) } case original: DefDef => val typer1 = ctx.typer.newLikeThis @@ -785,6 +785,7 @@ class Namer { typer: Typer => denot.info = UnspecifiedErrorType } else { + // the default behaviour is to complete using creation context completeInContext(denot, this.ctx) if (denot.isCompleted) registerIfChild(denot) } @@ -868,10 +869,12 @@ class Namer { typer: Typer => } } - /** Intentionally left without `implicit ctx` parameter. We need - * to pick up the context at the point where the completer was created. + /** Intentionally left without `implicit ctx` parameter. + * We use the given ctx to complete this Completer. * - * If -Yexplicit-nulls, is enabled, we sometimes use the completion context. + * Normally, the creation context is passed, as passed in the complete method. + * However, if -Yexplicit-nulls, is enabled, we pass in the current context + * so that flow typing works with blocks. */ def completeInContext(denot: SymDenotation, ctx: Context): Unit = { val sym = denot.symbol @@ -912,7 +915,7 @@ class Namer { typer: Typer => myTypeParams } - override protected def typeSig(sym: Symbol, completionContext: Context): Type = + override protected def typeSig(sym: Symbol, ctx: Context): Type = typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx) } @@ -1094,7 +1097,7 @@ class Namer { typer: Typer => } /** The type signature of a ClassDef with given symbol */ - override def completeInContext(denot: SymDenotation, completionContext: Context): Unit = { + override def completeInContext(denot: SymDenotation, ctx: Context): Unit = { val parents = impl.parents /* The type of a parent constructor. Types constructor arguments @@ -1130,26 +1133,26 @@ class Namer { typer: Typer => * (4) If the class is sealed, it is defined in the same compilation unit as the current class */ def checkedParentType(parent: untpd.Tree): Type = { - val ptype = parentType(parent)(ctx.superCallContext).dealiasKeepAnnots + val ptype = parentType(parent)(this.ctx.superCallContext).dealiasKeepAnnots if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.sourcePos, traitReq = parent ne parents.head, stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { - case Select(qual: Super, _) if ctx.scala2Mode => + case Select(qual: Super, _) if this.ctx.scala2Mode => "\n(Note that inheriting a class of the same name is no longer allowed)" case _ => "" } - ctx.error(CyclicInheritance(cls, addendum), parent.sourcePos) + this.ctx.error(CyclicInheritance(cls, addendum), parent.sourcePos) defn.ObjectType } else { val pclazz = pt.typeSymbol if (pclazz.is(Final)) - ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos) + this.ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos) if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile) - ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos) + this.ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos) pt } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a0bd8913f073..ed16d6f7a6ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2214,8 +2214,8 @@ class Typer extends Namer case none => import untpd.modsDeco // Force completer to use calling context (as opposed to the creation context) - // to complete itself. This approach is used so that flow typing can handle `ValDef`s - // that appear within a block. + // to complete itself. This approach is used so that flow typing can handle definitions + // appearing within a block. // // Example: // Suppose we have a block containing @@ -2223,24 +2223,23 @@ class Typer extends Namer // 2. if (x == null) throw NPE // 3. val y = x // - // We want to infer y: String on line 3, but if the completer for `y` uses its creation context, - // then we won't have the additional flow facts that say that `y` is not null. + // We want to infer y: String on line 3, but if the completer for `y` uses its creation + // context, then we won't have the additional flow facts that say that `y` is not null. // // The solution is to use the context containing more information about the statements above. val ctx2 = if (ctx.explicitNulls && ctx.owner.is(Method)) { - val ctx1 = ctx.fresh.addFlowFacts(facts) - // we cannot use mdef.symbol to get the symbol of the tree here - // since the tree has not been completed and doesn't have a denotation - mdef.getAttachment(SymOfTree) match { - case Some(sym) => - sym.infoOrCompleter match { - case completer: Namer#Completer => - completer.completeInContext(sym, ctx1) - case _ => - } + // We cannot use mdef.symbol to get the symbol of the tree here, + // since the tree has not been completed and doesn't have a denotation. + mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match { + case Some((sym, completer: Namer#Completer)) => + val ctx1 = ctx.fresh.addFlowFacts(facts) + completer.completeInContext(sym, ctx1) + ctx1 case _ => + // If it has been completed, we use the default context + // The flow typing will not be applied to definitions forwardly referred. + ctx } - ctx1 } else ctx diff --git a/tests/explicit-nulls/neg/flow8.scala b/tests/explicit-nulls/neg/flow8.scala index 234a9d58c8b7..91dc624aeebf 100644 --- a/tests/explicit-nulls/neg/flow8.scala +++ b/tests/explicit-nulls/neg/flow8.scala @@ -1,3 +1,5 @@ +// Test forward references handled with flow typing +// Currently, the flow typing will not be applied to definitions forwardly referred. class Foo { def foo(): Unit = { @@ -6,12 +8,22 @@ class Foo { implicit val y: Int = x.length } - // This case is not valid but foo() above is valid, because - // non-lazy value definitions exist between forward references - def fr(): Unit = { - val z = implicitly[Int] // error: forward reference is not allowed here + // This case is not valid but the foo() above is valid, because + // non-lazy value definitions exist between forward references. + // Since y is referred (by z) before definition, flow typing is not used here. + // Only the typing error is shown because reference check is after typing. + def fr1(): Unit = { + val z = implicitly[Int] val x: String|Null = ??? if (x == null) return () - implicit val y: Int = x.length + implicit val y: Int = x.length // error: x: String|Null inferred + } + + // Since z is referred before definition, flow typing is not used here. + def fr2(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y = z + def z = x.length // error: x: String|Null inferred } } From 19c5e40909fc090fed3f22e8762a9b0d7a3e247b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 27 Sep 2019 12:08:21 -0400 Subject: [PATCH 08/54] adjust tests; add comments --- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++-- tests/explicit-nulls/neg/flow6.scala | 22 ++++++++++++++ tests/explicit-nulls/neg/flow8.scala | 29 ------------------- 3 files changed, 25 insertions(+), 31 deletions(-) delete mode 100644 tests/explicit-nulls/neg/flow8.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ed16d6f7a6ca..8e3ca8da57b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2236,8 +2236,9 @@ class Typer extends Namer completer.completeInContext(sym, ctx1) ctx1 case _ => - // If it has been completed, we use the default context - // The flow typing will not be applied to definitions forwardly referred. + // If it has been completed, then it must be because there is a forward reference + // to the definition in the program. We use the default (creation) context, and flow + // typing will not be applied. ctx } } diff --git a/tests/explicit-nulls/neg/flow6.scala b/tests/explicit-nulls/neg/flow6.scala index 1ebb1674238a..56b672c786bf 100644 --- a/tests/explicit-nulls/neg/flow6.scala +++ b/tests/explicit-nulls/neg/flow6.scala @@ -1,3 +1,5 @@ +// Test forward references handled with flow typing +// Currently, the flow typing will not be applied to definitions forwardly referred. class Foo { def test1(): Unit = { @@ -18,6 +20,7 @@ class Foo { () } + // Since y is referred before definition, flow typing is not used here. def test3(): Unit = { val x: String|Null = ??? lazy val z = y @@ -25,4 +28,23 @@ class Foo { lazy val y = x.length // error: x: String|Null is inferred () } + + // This case is not valid but the test1 above is, because + // non-lazy value definitions exist between forward references. + // Since y is referred (by z) before definition, flow typing is not used here. + // Only the typing error is shown because reference check is after typing. + def test4(): Unit = { + val z = implicitly[Int] + val x: String|Null = ??? + if (x == null) return () + implicit val y: Int = x.length // error: x: String|Null inferred + } + + // Since z is referred before definition, flow typing is not used here. + def test5(): Unit = { + val x: String|Null = ??? + if (x == null) return () + def y = z + def z = x.length // error: x: String|Null inferred + } } \ No newline at end of file diff --git a/tests/explicit-nulls/neg/flow8.scala b/tests/explicit-nulls/neg/flow8.scala deleted file mode 100644 index 91dc624aeebf..000000000000 --- a/tests/explicit-nulls/neg/flow8.scala +++ /dev/null @@ -1,29 +0,0 @@ -// Test forward references handled with flow typing -// Currently, the flow typing will not be applied to definitions forwardly referred. -class Foo { - - def foo(): Unit = { - val x: String|Null = ??? - if (x == null) return () - implicit val y: Int = x.length - } - - // This case is not valid but the foo() above is valid, because - // non-lazy value definitions exist between forward references. - // Since y is referred (by z) before definition, flow typing is not used here. - // Only the typing error is shown because reference check is after typing. - def fr1(): Unit = { - val z = implicitly[Int] - val x: String|Null = ??? - if (x == null) return () - implicit val y: Int = x.length // error: x: String|Null inferred - } - - // Since z is referred before definition, flow typing is not used here. - def fr2(): Unit = { - val x: String|Null = ??? - if (x == null) return () - def y = z - def z = x.length // error: x: String|Null inferred - } -} From b3c85e398c6db98b24b494beceb1cfc23eabf546 Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Fri, 27 Sep 2019 14:04:47 -0400 Subject: [PATCH 09/54] Handle Java arguments with 'Object' type in override checks --- .../dotty/tools/dotc/core/TypeComparer.scala | 14 +++++++-- .../neg/override-java-object-arg.scala | 26 ++++++++++++++++ .../neg/override-java-object-arg2.scala | 13 ++++++++ .../pos/override-java-object-arg-src/J.java | 10 +++++++ .../pos/override-java-object-arg-src/S.scala | 20 +++++++++++++ .../pos/override-java-object-arg.scala | 30 +++++++++++++++++++ 6 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 tests/explicit-nulls/neg/override-java-object-arg.scala create mode 100644 tests/explicit-nulls/neg/override-java-object-arg2.scala create mode 100644 tests/explicit-nulls/pos/override-java-object-arg-src/J.java create mode 100644 tests/explicit-nulls/pos/override-java-object-arg-src/S.scala create mode 100644 tests/explicit-nulls/pos/override-java-object-arg.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 89bb068edff3..922192abdb93 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -17,6 +17,7 @@ import transform.SymUtils._ import scala.util.control.NonFatal import typer.ProtoTypes.constrained import reporting.trace +import NullOpsDecorator.NullOps final class AbsentContext object AbsentContext { @@ -1605,9 +1606,18 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w formals2 match { case formal2 :: rest2 => val formal2a = if (tp2.isParamDependent) formal2.subst(tp2, tp1) else formal2 + // The next two definitions handle the special case mentioned above, where + // the Java argument has type 'Any', and the Scala argument has type 'Object' or + // 'Object|Null', depending on whether explicit nulls are enabled. + lazy val formal2IsObject = + if (ctx.explicitNulls) formal2.isNullableUnion && formal2.stripNull(ctx).isRef(ObjectClass) + else formal2.isRef(ObjectClass) + lazy val formal1IsObject = + if (ctx.explicitNulls) formal1.isNullableUnion && formal1.stripNull(ctx).isRef(ObjectClass) + else formal1.isRef(ObjectClass) (isSameTypeWhenFrozen(formal1, formal2a) - || tp1.isJavaMethod && (formal2 isRef ObjectClass) && (formal1 isRef AnyClass) - || tp2.isJavaMethod && (formal1 isRef ObjectClass) && (formal2 isRef AnyClass)) && + || tp1.isJavaMethod && formal2IsObject && (formal1 isRef AnyClass) + || tp2.isJavaMethod && formal1IsObject && (formal2 isRef AnyClass)) && loop(rest1, rest2) case nil => false diff --git a/tests/explicit-nulls/neg/override-java-object-arg.scala b/tests/explicit-nulls/neg/override-java-object-arg.scala new file mode 100644 index 000000000000..7a45cb1d6199 --- /dev/null +++ b/tests/explicit-nulls/neg/override-java-object-arg.scala @@ -0,0 +1,26 @@ + +// Test that we can properly override Java methods where an argument has type 'Object'. +// See pos/override-java-object-arg.scala for context. + +import javax.management.{Notification, NotificationEmitter, NotificationListener} + +class Foo { + + def bar(): Unit = { + val listener = new NotificationListener() { + override def handleNotification(n: Notification|Null, emitter: Object): Unit = { // error: method handleNotification overrides nothing + } + } + + val listener2 = new NotificationListener() { + override def handleNotification(n: Notification|Null, emitter: Object|Null): Unit = { // ok + } + } + + val listener3 = new NotificationListener() { + override def handleNotification(n: Notification, emitter: Object|Null): Unit = { // error: method handleNotification overrides nothing + } + } + } +} + diff --git a/tests/explicit-nulls/neg/override-java-object-arg2.scala b/tests/explicit-nulls/neg/override-java-object-arg2.scala new file mode 100644 index 000000000000..5ef7373d0868 --- /dev/null +++ b/tests/explicit-nulls/neg/override-java-object-arg2.scala @@ -0,0 +1,13 @@ + +import javax.management.{Notification, NotificationEmitter, NotificationListener} + +class Foo { + + def bar(): Unit = { + val listener4 = new NotificationListener() { // error: duplicate symbol error + def handleNotification(n: Notification|Null, emitter: Object): Unit = { + } + } + } + +} diff --git a/tests/explicit-nulls/pos/override-java-object-arg-src/J.java b/tests/explicit-nulls/pos/override-java-object-arg-src/J.java new file mode 100644 index 000000000000..efcb630b7b6c --- /dev/null +++ b/tests/explicit-nulls/pos/override-java-object-arg-src/J.java @@ -0,0 +1,10 @@ + +// Copy of https://docs.oracle.com/javase/7/docs/api/javax/management/NotificationListener.html + +class Notification {}; + +interface NotificationListener { + + void handleNotification(Notification notification, Object handback); + +} diff --git a/tests/explicit-nulls/pos/override-java-object-arg-src/S.scala b/tests/explicit-nulls/pos/override-java-object-arg-src/S.scala new file mode 100644 index 000000000000..333e6e710d57 --- /dev/null +++ b/tests/explicit-nulls/pos/override-java-object-arg-src/S.scala @@ -0,0 +1,20 @@ + +// This test is like tests/pos/override-java-object-arg.scala, except that +// here we load the Java code from source, as opposed to a class file. +// In this case, the Java 'Object' type is turned into 'AnyRef', not 'Any'. + +class S { + + def bar(): Unit = { + val listener = new NotificationListener() { + override def handleNotification(n: Notification|Null, emitter: Object|Null): Unit = { + } + } + + val listener2 = new NotificationListener() { + override def handleNotification(n: Notification|Null, emitter: AnyRef|Null): Unit = { + } + } + } + +} diff --git a/tests/explicit-nulls/pos/override-java-object-arg.scala b/tests/explicit-nulls/pos/override-java-object-arg.scala new file mode 100644 index 000000000000..7ab8a77a8b0f --- /dev/null +++ b/tests/explicit-nulls/pos/override-java-object-arg.scala @@ -0,0 +1,30 @@ + +// When we load a Java class file, if a java method has an argument with type +// 'Object', it (the method argument) gets loaded by Dotty as 'Any' (as opposed to 'AnyRef'). +// This is pre-explicit-nulls behaviour. +// There is special logic in the type comparer that allows that method to be overriden +// with a corresponding argument with type 'AnyRef'. +// This test verifies that we can continue to override such methods, except that in +// the explicit nulls world we override with 'AnyRef|Null'. + +import javax.management.{Notification, NotificationEmitter, NotificationListener} + +class Foo { + + def bar(): Unit = { + val listener = new NotificationListener() { + // The second argument in the base interface is loaded with type 'Any', but we override + // it with 'AnyRef|Null'. + override def handleNotification(n: Notification|Null, emitter: Object|Null): Unit = { + } + } + + val listener2 = new NotificationListener() { + // The second argument in the base interface is loaded with type 'Any', but we override + // it with 'AnyRef|Null'. + override def handleNotification(n: Notification|Null, emitter: AnyRef|Null): Unit = { + } + } + } + +} From 3f2e8d1471c3c241266a12f2290875b42523e01f Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Fri, 27 Sep 2019 14:23:10 -0400 Subject: [PATCH 10/54] Add test for unboxed option type --- .../explicit-nulls/pos/opaque-nullable.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/explicit-nulls/pos/opaque-nullable.scala diff --git a/tests/explicit-nulls/pos/opaque-nullable.scala b/tests/explicit-nulls/pos/opaque-nullable.scala new file mode 100644 index 000000000000..eaf7a9198e64 --- /dev/null +++ b/tests/explicit-nulls/pos/opaque-nullable.scala @@ -0,0 +1,22 @@ +// Unboxed option type using unions + null + opaque. +// Relies on the fact that Null is not a subtype of AnyRef. +// Test suggested by Sébastien Doeraene. + +opaque type Nullable[+A <: AnyRef] = A | Null // disjoint by construction! + +object Nullable { + def apply[A <: AnyRef](x: A | Null): Nullable[A] = x + + def some[A <: AnyRef](x: A): Nullable[A] = x + def none: Nullable[Nothing] = null + + implicit class NullableOps[A <: AnyRef](x: Nullable[A]) { + def isEmpty: Boolean = x == null + def flatMap[B <: AnyRef](f: A => Nullable[B]): Nullable[B] = + if (x == null) null + else f(x) + } + + val s1: Nullable[String] = "hello" + val s2: Nullable[String] = null +} From 4fd2085c4de7ab8a12482d47b1b1cfa4d70eae36 Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Fri, 27 Sep 2019 15:44:49 -0400 Subject: [PATCH 11/54] Use flatMap in opaque option test --- tests/explicit-nulls/pos/opaque-nullable.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/explicit-nulls/pos/opaque-nullable.scala b/tests/explicit-nulls/pos/opaque-nullable.scala index eaf7a9198e64..4b6f4f3f88aa 100644 --- a/tests/explicit-nulls/pos/opaque-nullable.scala +++ b/tests/explicit-nulls/pos/opaque-nullable.scala @@ -19,4 +19,7 @@ object Nullable { val s1: Nullable[String] = "hello" val s2: Nullable[String] = null + + s1.isEmpty + s1.flatMap((x) => true) } From a56557f805942942429685f3b50978327dbfef4b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 27 Sep 2019 15:59:23 -0400 Subject: [PATCH 12/54] refine comments --- tests/explicit-nulls/neg/flow6.scala | 4 ++-- tests/explicit-nulls/pos/flow6.scala | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/explicit-nulls/neg/flow6.scala b/tests/explicit-nulls/neg/flow6.scala index 56b672c786bf..d4252e5a0412 100644 --- a/tests/explicit-nulls/neg/flow6.scala +++ b/tests/explicit-nulls/neg/flow6.scala @@ -29,8 +29,8 @@ class Foo { () } - // This case is not valid but the test1 above is, because - // non-lazy value definitions exist between forward references. + // This case is invalid because z has an implicit forward reference to y, + // but x, y and z aren't lazy (only forward references to lazy vals are allowed). // Since y is referred (by z) before definition, flow typing is not used here. // Only the typing error is shown because reference check is after typing. def test4(): Unit = { diff --git a/tests/explicit-nulls/pos/flow6.scala b/tests/explicit-nulls/pos/flow6.scala index a1be65575225..555e24335c26 100644 --- a/tests/explicit-nulls/pos/flow6.scala +++ b/tests/explicit-nulls/pos/flow6.scala @@ -60,7 +60,9 @@ class Foo { () } - // This test checks facts from outer blocks are remembered. + // This test checks that flow facts are forgotten for defs, but only + // the facts gathered within the current block are forgotten. + // Other facts from outer blocks are remembered. def test9(): Unit = { val x: String|Null = ??? if (x == null) { From 902dc4a5b143286a6b53d11fc09090d4d8b5fdf9 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 27 Sep 2019 16:41:28 -0400 Subject: [PATCH 13/54] Small changes according to comments --- .../dotty/tools/dotc/core/Definitions.scala | 43 ++++------- .../src/dotty/tools/dotc/core/FlowTyper.scala | 15 +--- .../src/dotty/tools/dotc/core/Types.scala | 74 +++++++++---------- .../tools/dotc/transform/patmat/Space.scala | 14 ++-- .../scala/ExplicitNulls.scala | 30 -------- library/src/dotty/DottyPredef.scala | 28 +++++++ 6 files changed, 90 insertions(+), 114 deletions(-) delete mode 100644 library/src-bootstrapped/scala/ExplicitNulls.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 30ffe502df05..1c5347feb543 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -300,14 +300,12 @@ class Definitions { @tu lazy val Object_eq: TermSymbol = { // If explicit nulls is enabled, then we want to allow `(x: String).eq(null)`, so we need // to adjust the signature of `eq` accordingly. - val tpe = if (ctx.explicitNulls) methOfAnyRefOrNull(BooleanType) else methOfAnyRef(BooleanType) - enterMethod(ObjectClass, nme.eq, tpe, Final) + enterMethod(ObjectClass, nme.eq, methOfAnyRefOrNull(BooleanType), Final) } @tu lazy val Object_ne: TermSymbol = { // If explicit nulls is enabled, then we want to allow `(x: String).ne(null)`, so we need // to adjust the signature of `ne` accordingly. - val tpe = if (ctx.explicitNulls) methOfAnyRefOrNull(BooleanType) else methOfAnyRef(BooleanType) - enterMethod(ObjectClass, nme.ne, tpe, Final) + enterMethod(ObjectClass, nme.ne, methOfAnyRefOrNull(BooleanType), Final) } @tu lazy val Object_synchronized: TermSymbol = enterPolyMethod(ObjectClass, nme.synchronized_, 1, pt => MethodType(List(pt.paramRefs(0)), pt.paramRefs(0)), Final) @@ -342,17 +340,19 @@ class Definitions { /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = { - val argTpe = if (ctx.explicitNulls) OrType(ThrowableType, NullType) else ThrowableType - enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(argTpe), NothingType)) + enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(mayNull(ThrowableType)), NothingType)) } + // TODO: where ? + def mayNull(typ: Type): Type = if (ctx.explicitNulls) OrType(typ, NullType) else typ + @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) def NothingType: TypeRef = NothingClass.typeRef @tu lazy val RuntimeNothingModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Nothing") @tu lazy val NullClass: ClassSymbol = { - val parents = List(if (ctx.explicitNulls) AnyType else ObjectType) - enterCompleteClassSymbol(ScalaPackageClass, tpnme.Null, AbstractFinal, parents) + val parent = if (ctx.explicitNulls) AnyType else ObjectType + enterCompleteClassSymbol(ScalaPackageClass, tpnme.Null, AbstractFinal, parent :: Nil) } def NullType: TypeRef = NullClass.typeRef @tu lazy val RuntimeNullModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Null") @@ -371,10 +371,7 @@ class Definitions { assert(ctx.explicitNulls) enterAliasType(tpnme.JavaNull, NullType) } - def JavaNullAliasType: TypeRef = { - assert(ctx.explicitNulls) - JavaNullAlias.typeRef - } + def JavaNullAliasType: TypeRef = JavaNullAlias.typeRef @tu lazy val ImplicitScrutineeTypeSym = newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered @@ -814,8 +811,8 @@ class Definitions { // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) - def methOfAnyRef(tp: Type): MethodType = MethodType(List(ObjectType), tp) - def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(OrType(ObjectType, NullType)), tp) + def methOfAnyRefOrNull(tp: Type): MethodType = + if (ctx.explicitNulls) MethodType(List(OrType(ObjectType, NullType)), tp) else MethodType(List(ObjectType), tp) // Derived types @@ -975,23 +972,18 @@ class Definitions { name.length > prefix.length && name.drop(prefix.length).forall(_.isDigit)) - def isBottomClass(cls: Symbol): Boolean = { + def isBottomClass(cls: Symbol): Boolean = if (ctx.explicitNulls && !ctx.phase.erasedTypes) cls == NothingClass else isBottomClassAfterErasure(cls) - } - def isBottomClassAfterErasure(cls: Symbol): Boolean = { - cls == NothingClass || cls == NullClass - } + def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass - def isBottomType(tp: Type): Boolean = { + def isBottomType(tp: Type): Boolean = if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.derivesFrom(NothingClass) else isBottomTypeAfterErasure(tp) - } - def isBottomTypeAfterErasure(tp: Type): Boolean = { + def isBottomTypeAfterErasure(tp: Type): Boolean = tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) - } /** Is a function class. * - FunctionXXL @@ -1087,10 +1079,7 @@ class Definitions { val PredefImportFns: List[RootRef] = List[RootRef]( (() => ScalaPredefModule.termRef, true), - (() => DottyPredefModule.termRef, false), - // TODO(abeln): is this in the right place? - // And is it ok to import this unconditionally? - (() => ctx.requiredModuleRef("scala.ExplicitNullsOps"), false) + (() => DottyPredefModule.termRef, false) ) @tu lazy val RootImportFns: List[RootRef] = diff --git a/compiler/src/dotty/tools/dotc/core/FlowTyper.scala b/compiler/src/dotty/tools/dotc/core/FlowTyper.scala index 0d81efedc9e7..bfd698fff643 100644 --- a/compiler/src/dotty/tools/dotc/core/FlowTyper.scala +++ b/compiler/src/dotty/tools/dotc/core/FlowTyper.scala @@ -18,7 +18,7 @@ object FlowTyper { type FlowFacts = Set[TermRef] /** The initial state where no `TermRef`s are known to be non-null */ - @sharable val emptyFlowFacts = Set.empty[TermRef] + val emptyFlowFacts = Set.empty[TermRef] /** Tries to improve the precision of `tpe` using flow-sensitive type information. * For nullability, is `tpe` is a `TermRef` declared as nullable but known to be non-nullable because of the @@ -28,7 +28,7 @@ object FlowTyper { def refineType(tpe: Type)(implicit ctx: Context): Type = { assert(ctx.explicitNulls) tpe match { - case tref: TermRef if ctx.flowFacts.contains(tref) => NonNullTermRef.apply(tref.prefix, tref.designator) + case tref: TermRef if ctx.flowFacts.contains(tref) => NonNullTermRef(tref.prefix, tref.designator) case _ => tpe } } @@ -57,14 +57,6 @@ object FlowTyper { def negate: Inferred = Inferred(ifFalse, ifTrue) } - object Inferred { - /** Create a singleton inferred fact containing `tref`. */ - def apply(tref: TermRef, ifTrue: Boolean): Inferred = { - if (ifTrue) Inferred(Set(tref), emptyFlowFacts) - else Inferred(emptyFlowFacts, Set(tref)) - } - } - /** Analyze the tree for a condition `cond` to learn new flow facts. * Supports ands, ors, and unary negation. * @@ -160,7 +152,8 @@ object FlowTyper { case Some(tref) => // If `isEq`, then the condition is of the form `lhs == null`, // in which case we know `lhs` is non-null if the condition is false. - Inferred(tref, ifTrue = !isEq) + if (!isEq) Inferred(Set(tref), emptyFlowFacts) + else Inferred(emptyFlowFacts, Set(tref)) case _ => emptyFacts } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 29d5f5a15ae7..fdd5abaf1bb6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -426,7 +426,7 @@ object Types { val OrType(left, _) = this.normNullableUnion // If `left` is a reference type, then the class LUB of `left | Null` is `Any`. // This is another one-of case that keeps this method sound, but not complete. - if (left.classSymbol isSubClass defn.ObjectClass) defn.AnyClass + if (left.classSymbol derivesFrom defn.ObjectClass) defn.AnyClass else NoSymbol } else tp.join.classSymbol @@ -1807,9 +1807,6 @@ object Types { def isType: Boolean = isInstanceOf[TypeRef] def isTerm: Boolean = isInstanceOf[TermRef] - /** Is this type known to be non-null from flow typing (only applicable to TermRefs) ? */ - def isNonNull: Boolean = isInstanceOf[NonNullTermRef] - /** If designator is a name, this name. Otherwise, the original name * of the designator symbol. */ @@ -1903,24 +1900,18 @@ object Types { else computeDenot } - private def computeDenot(implicit ctx: Context): Denotation = { + protected def finish(d: Denotation)(implicit ctx: Context): Denotation = { + if (d.exists) + // Avoid storing NoDenotations in the cache - we will not be able to recover from + // them. The situation might arise that a type has NoDenotation in some later + // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type + // is undefined after erasure.) We need to be able to do time travel back and + // forth also in these cases. + setDenot(d) + d + } - def finish(d: Denotation): Denotation = { - if (d.exists) { - val d1 = if (ctx.explicitNulls && isNonNull) { - // If the denotation is computed for the first time, or if it's ever updated, make sure - // that the `info` is non-null. - d.mapInfo(_.stripNull) - } else d - // Avoid storing NoDenotations in the cache - we will not be able to recover from - // them. The situation might arise that a type has NoDenotation in some later - // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type - // is undefined after erasure.) We need to be able to do time travel back and - // forth also in these cases. - setDenot(d1) - d1 - } else d - } + private def computeDenot(implicit ctx: Context): Denotation = { def fromDesignator = designator match { case name: Name => @@ -2220,7 +2211,7 @@ object Types { /** A reference like this one, but with the given symbol, if it exists */ final def withSym(sym: Symbol)(implicit ctx: Context): ThisType = - if ((designator ne sym) && sym.exists) namedType(prefix, sym, isNonNull).asInstanceOf[ThisType] + if ((designator ne sym) && sym.exists) newLikeThis(prefix, sym).asInstanceOf[ThisType] else this /** A reference like this one, but with the given denotation, if it exists. @@ -2267,10 +2258,10 @@ object Types { d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod else lastSymbol.asSeenFrom(prefix).signature) - namedType(prefix, name, d, isNonNull) + newLikeThis(prefix, name, d) } if (prefix eq this.prefix) this - else if (lastDenotation == null) namedType(prefix, designator, isNonNull) + else if (lastDenotation == null) newLikeThis(prefix, designator) else designator match { case sym: Symbol => if (infoDependsOnPrefix(sym, prefix) && !prefix.isArgPrefixOf(sym)) { @@ -2279,10 +2270,10 @@ object Types { // A false override happens if we rebind an inner class to another type with the same name // in an outer subclass. This is wrong, since classes do not override. We need to // return a type with the existing class info as seen from the new prefix instead. - if (falseOverride) namedType(prefix, sym.name, denot.asSeenFrom(prefix), isNonNull) + if (falseOverride) newLikeThis(prefix, sym.name, denot.asSeenFrom(prefix)) else candidate } - else namedType(prefix, sym, isNonNull) + else newLikeThis(prefix, sym) case name: Name => reload() } } @@ -2306,17 +2297,11 @@ object Types { override def eql(that: Type): Boolean = this eq that // safe because named types are hash-consed separately - /** Like `NamedType.apply`, but can create `NonNullTermRef`s. */ - private def namedType(prefix: Type, designator: Designator, isNonNull: Boolean)(implicit ctx: Context): NamedType = { - if (ctx.explicitNulls && isNonNull) NonNullTermRef.apply(prefix, designator) - else NamedType.apply(prefix, designator) - } + protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType = + NamedType(prefix, designator) - /** Like `NamedType.apply`, but can create `NonNullTermRef`s. */ - private def namedType(prefix: Type, designator: Name, denot: Denotation, isNonNull: Boolean)(implicit ctx: Context): NamedType = { - if (ctx.explicitNulls && isNonNull) NonNullTermRef.apply(prefix, designator.asTermName, denot) - else NamedType.apply(prefix, designator, denot) - } + protected def newLikeThis(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType = + NamedType(prefix, designator, denot) } /** A reference to an implicit definition. This can be either a TermRef or a @@ -2399,8 +2384,21 @@ object Types { final class NonNullTermRef(prefix: Type, designator: Designator) extends TermRef(prefix, designator) { override type ThisType = NonNullTermRef - // This class has no custom members: it's just used as a marker (via `isInstanceOf`) within - // `NamedType` to identify `TermRef`s that are non-nullable. + override protected def finish(d: Denotation)(implicit ctx: Context): Denotation = + // If the denotation is computed for the first time, or if it's ever updated, make sure + // that the `info` is non-null. + super.finish(d.mapInfo(_.stripNull)) + + override protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType = + NonNullTermRef(prefix, designator) + + override protected def newLikeThis(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType = + NonNullTermRef(prefix, designator.asTermName, denot) + + override def eql(that: Type): Boolean = that match { + case that: NonNullTermRef => (this.prefix eq that.prefix) && (this.designator eq that.designator) + case _ => false + } } final class CachedTypeRef(prefix: Type, designator: Designator, hc: Int) extends TypeRef(prefix, designator) { diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 990e1e48706a..3a05d2f790d1 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -680,9 +680,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def doShow(s: Space, mergeList: Boolean = false): String = s match { case Empty => "" - case Typ(c: ConstantType, _) => - val v = c.value.value - if (v == null) "null" else "" + v + case Typ(c: ConstantType, _) => String.valueOf(c.value.value) case Typ(tp: TermRef, _) => tp.symbol.showName case Typ(tp, decomposed) => val sym = tp.widen.classSymbol @@ -784,9 +782,10 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (!redundancyCheckable(sel)) return val targetSpace = - if (ctx.explicitNulls) Typ(selTyp, true) - else if (selTyp.classSymbol.isPrimitiveValueClass) Typ(selTyp, true) - else Or(Typ(selTyp, true) :: constantNullSpace :: Nil) + if (ctx.explicitNulls || selTyp.classSymbol.isPrimitiveValueClass) + Typ(selTyp, true) + else + Or(Typ(selTyp, true) :: constantNullSpace :: Nil) // in redundancy check, take guard as false in order to soundly approximate def projectPrevCases(cases: List[CaseDef]): Space = @@ -821,8 +820,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { // If explicit nulls are enabled, this check isn't needed because most of the cases // that would trigger it would also trigger unreachability warnings. if (!ctx.explicitNulls && i == cases.length - 1 && !isNullLit(pat) ) { - val simpl = simplify(minus(covered, prevs)) - simpl match { + simplify(minus(covered, prevs)) match { case Typ(`constantNullType`, _) => ctx.warning(MatchCaseOnlyNullWarning(), pat.sourcePos) case _ => diff --git a/library/src-bootstrapped/scala/ExplicitNulls.scala b/library/src-bootstrapped/scala/ExplicitNulls.scala deleted file mode 100644 index 7aa01ee277fe..000000000000 --- a/library/src-bootstrapped/scala/ExplicitNulls.scala +++ /dev/null @@ -1,30 +0,0 @@ -package scala - -/** This module defines extension methods for working with explicit nulls. */ -given ExplicitNullsOps: { - - /** Strips away the nullability from a value. - * e.g. - * val s1: String|Null = "hello" - * val s: String = s1.nn - * - * Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE. - */ - def (x: T|Null) nn[T]: T = - if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") - else x.asInstanceOf[T] - - /** Reference equality where the receiver is a nullable union. - * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.eq` will invoke the - * `eq` method in `AnyRef`. - */ - def (x: AnyRef|Null) eq(y: AnyRef|Null): Boolean = - (x == null && y == null) || (x != null && x.eq(y)) - - /** Reference disequality where the receiver is a nullable union. - * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.ne` will invoke the - * `ne` method in `AnyRef`. - */ - def (x: AnyRef|Null) ne(y: AnyRef|Null): Boolean = - (x == null && y != null) || (x != null && x.ne(y)) -} diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 18b16a0283e6..c1a8aa5aa51c 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -38,4 +38,32 @@ object DottyPredef { } inline def summon[T](given x: T): x.type = x + + // extension methods for working with explicit nulls + + /** Strips away the nullability from a value. + * e.g. + * val s1: String|Null = "hello" + * val s: String = s1.nn + * + * Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE. + */ + def (x: T|Null) nn[T]: T = + if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") + else x.asInstanceOf[T] + + /** Reference equality where the receiver is a nullable union. + * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.eq` will invoke the + * `eq` method in `AnyRef`. + */ + def (x: AnyRef|Null) eq(y: AnyRef|Null): Boolean = + (x == null && y == null) || (x != null && x.eq(y)) + + /** Reference disequality where the receiver is a nullable union. + * Note that if the receiver `r` is a reference type (e.g. `String`), then `r.ne` will invoke the + * `ne` method in `AnyRef`. + */ + def (x: AnyRef|Null) ne(y: AnyRef|Null): Boolean = + (x == null && y != null) || (x != null && x.ne(y)) + } From 116885601cdf37be9554e41283c284e4d8519e96 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 30 Sep 2019 15:12:42 -0400 Subject: [PATCH 14/54] cache flow facts on trees --- .../dotty/tools/dotc/transform/patmat/Space.scala | 2 +- .../src/dotty/tools/dotc/typer/Applications.scala | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 3a05d2f790d1..4098b44e59e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -680,7 +680,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { def doShow(s: Space, mergeList: Boolean = false): String = s match { case Empty => "" - case Typ(c: ConstantType, _) => String.valueOf(c.value.value) + case Typ(c: ConstantType, _) => "" + c.value.value case Typ(tp: TermRef, _) => tp.symbol.showName case Typ(tp, decomposed) => val sym = tp.widen.classSymbol diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index d621a78c9ede..2c200f68c170 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -4,6 +4,7 @@ package typer import core._ import ast.{Trees, tpd, untpd} +import util.Property import util.Spans._ import util.Stats.record import util.{SourcePosition, NoSourcePosition, SourceFile} @@ -37,6 +38,8 @@ import annotation.{constructorOnly, threadUnsafe} object Applications { import tpd._ + private val FlowFactsOnTree = new Property.Key[FlowTyper.FlowFacts] + def extractorMember(tp: Type, name: Name)(implicit ctx: Context): SingleDenotation = tp.member(name).suchThat(_.info.isParameterless) @@ -862,8 +865,14 @@ trait Applications extends Compatibility { // TODO(abeln): we're re-doing work here by recomputing what's implies by the lhs of the comparison. // e.g. in `A && B && C && D`, we'll recompute the facts implied by `A && B` twice. // Find a more-efficient way to do this. - val newFacts = FlowTyper.inferWithinCond(fun1) - if (newFacts.isEmpty) ctx else ctx.fresh.addFlowFacts(newFacts) + val facts = fun1.getAttachment(FlowFactsOnTree) match { + case Some(fs) => fs + case None => + val fs = FlowTyper.inferWithinCond(fun1) + fun1.putAttachment(FlowFactsOnTree, fs) + fs + } + if (facts.isEmpty) ctx else ctx.fresh.addFlowFacts(facts) } else { ctx } From 2d5d7844057d61d5c6181f53d5ab847eb986f9f1 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 1 Oct 2019 13:37:38 -0400 Subject: [PATCH 15/54] move FlowFactsOnTree to Typer; refine comments --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 +++----- compiler/src/dotty/tools/dotc/core/Types.scala | 9 +-------- .../src/dotty/tools/dotc/typer/Applications.scala | 11 +++-------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 +++++ library/src/dotty/DottyPredef.scala | 2 +- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1c5347feb543..4475cc829f8e 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -340,11 +340,10 @@ class Definitions { /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = { - enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(mayNull(ThrowableType)), NothingType)) + enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(maybeNull(ThrowableType)), NothingType)) } - // TODO: where ? - def mayNull(typ: Type): Type = if (ctx.explicitNulls) OrType(typ, NullType) else typ + def maybeNull(typ: Type): Type = if (ctx.explicitNulls) OrType(typ, NullType) else typ @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) @@ -811,8 +810,7 @@ class Definitions { // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) - def methOfAnyRefOrNull(tp: Type): MethodType = - if (ctx.explicitNulls) MethodType(List(OrType(ObjectType, NullType)), tp) else MethodType(List(ObjectType), tp) + def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(maybeNull(ObjectType)), tp) // Derived types diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index fdd5abaf1bb6..29538e42d74a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -422,14 +422,7 @@ object Types { else if (rsym isSubClass lsym) rsym else NoSymbol case tp: OrType => - if (ctx.explicitNulls && this.isNullableUnion) { - val OrType(left, _) = this.normNullableUnion - // If `left` is a reference type, then the class LUB of `left | Null` is `Any`. - // This is another one-of case that keeps this method sound, but not complete. - if (left.classSymbol derivesFrom defn.ObjectClass) defn.AnyClass - else NoSymbol - } - else tp.join.classSymbol + tp.join.classSymbol case _ => NoSymbol } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2c200f68c170..b54cd7e98cbb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -4,7 +4,6 @@ package typer import core._ import ast.{Trees, tpd, untpd} -import util.Property import util.Spans._ import util.Stats.record import util.{SourcePosition, NoSourcePosition, SourceFile} @@ -38,8 +37,6 @@ import annotation.{constructorOnly, threadUnsafe} object Applications { import tpd._ - private val FlowFactsOnTree = new Property.Key[FlowTyper.FlowFacts] - def extractorMember(tp: Type, name: Name)(implicit ctx: Context): SingleDenotation = tp.member(name).suchThat(_.info.isParameterless) @@ -862,14 +859,12 @@ trait Applications extends Compatibility { /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = { val ctx1 = if (ctx.explicitNulls) { - // TODO(abeln): we're re-doing work here by recomputing what's implies by the lhs of the comparison. - // e.g. in `A && B && C && D`, we'll recompute the facts implied by `A && B` twice. - // Find a more-efficient way to do this. - val facts = fun1.getAttachment(FlowFactsOnTree) match { + // The flow facts of lhs are cached in the FlowFactsOnTree attachment + val facts = fun1.getAttachment(Typer.FlowFactsOnTree) match { case Some(fs) => fs case None => val fs = FlowTyper.inferWithinCond(fun1) - fun1.putAttachment(FlowFactsOnTree, fs) + fun1.putAttachment(Typer.FlowFactsOnTree, fs) fs } if (facts.isEmpty) ctx else ctx.fresh.addFlowFacts(facts) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7e03c023c23f..93a66f03495f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -78,6 +78,11 @@ object Typer { * search was tried on a tree. This will in some cases be reported in error messages */ private[typer] val HiddenSearchFailure = new Property.Key[SearchFailure] + + /** An attachment that indicates the flow-sensitive type information + * inside a condition. + */ + private[typer] val FlowFactsOnTree = new Property.Key[FlowFacts] } diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index c1a8aa5aa51c..755f41ea64d7 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -39,7 +39,7 @@ object DottyPredef { inline def summon[T](given x: T): x.type = x - // extension methods for working with explicit nulls + // Extension methods for working with explicit nulls /** Strips away the nullability from a value. * e.g. From f72f2013eaf28c6994fa3578c3a0c175408a1e82 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 2 Oct 2019 12:15:41 -0400 Subject: [PATCH 16/54] move maybeNull --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 6 ++---- compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4475cc829f8e..5f8f05b530be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -340,11 +340,9 @@ class Definitions { /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = { - enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(maybeNull(ThrowableType)), NothingType)) + enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType.maybeNull), NothingType)) } - def maybeNull(typ: Type): Type = if (ctx.explicitNulls) OrType(typ, NullType) else typ - @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) def NothingType: TypeRef = NothingClass.typeRef @@ -810,7 +808,7 @@ class Definitions { // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) - def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(maybeNull(ObjectType)), tp) + def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(ObjectType.maybeNull), tp) // Derived types diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index e7764ae61250..98379e8482f4 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -56,6 +56,9 @@ object NullOpsDecorator { case _ => false } + def maybeNull(implicit ctx: Context): Type = + if (ctx.explicitNulls) OrType(self, defn.NullType) else self + /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of all other types. * In the process, it also flattens the type so that there are no nested unions at the top level. * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null | Null` From de5241bf9da7bf863a8ffe2f3503d7f412f3095f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 2 Oct 2019 15:37:38 -0400 Subject: [PATCH 17/54] rename to maybeNullable --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5f8f05b530be..c092d357c0de 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -340,7 +340,7 @@ class Definitions { /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = { - enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType.maybeNull), NothingType)) + enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType.maybeNullable), NothingType)) } @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( @@ -808,7 +808,7 @@ class Definitions { // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) - def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(ObjectType.maybeNull), tp) + def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(ObjectType.maybeNullable), tp) // Derived types diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 98379e8482f4..aea72502ae34 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -56,7 +56,7 @@ object NullOpsDecorator { case _ => false } - def maybeNull(implicit ctx: Context): Type = + def maybeNullable(implicit ctx: Context): Type = if (ctx.explicitNulls) OrType(self, defn.NullType) else self /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of all other types. From 92b6c5c5c596794a986f40e330d44b45c4c18407 Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Mon, 7 Oct 2019 12:27:59 -0400 Subject: [PATCH 18/54] Handle Java varargs during nullification If we see a Java varargs like `void foo(String... s)`, translate it into `def foo(s: (String|JavaNull)*)`. That is, nullify the element type, but not the top-level vararg. This is because `foo(null)` really means `foo(Array(null))`. --- .../tools/dotc/core/JavaNullInterop.scala | 12 ++++++++-- .../pos/java-varargs-src/Names.java | 4 ++++ .../pos/java-varargs-src/S.scala | 19 ++++++++++++++++ tests/explicit-nulls/pos/java-varargs.scala | 22 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/explicit-nulls/pos/java-varargs-src/Names.java create mode 100644 tests/explicit-nulls/pos/java-varargs-src/S.scala create mode 100644 tests/explicit-nulls/pos/java-varargs.scala diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 1a02e596f40a..3be62a45af1d 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -147,8 +147,15 @@ object JavaNullInterop { !alreadyNullable && (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.symbol.isValueClass && !tp.isRef(defn.AnyClass) + !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 }) } @@ -167,7 +174,8 @@ object JavaNullInterop { case tp: TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion case appTp @ AppliedType(tycons, targs) => val targs2 = if (needsNullArgs(appTp)) targs map this else targs - derivedAppliedType(appTp, tycons, targs2).toJavaNullableUnion + val appTp2 = derivedAppliedType(appTp, tycons, targs2) + if (needsTopLevelNull(tycons)) appTp2.toJavaNullableUnion else appTp2 case tp: LambdaType => mapOver(tp) case tp: TypeAlias => mapOver(tp) case tp @ AndType(tp1, tp2) => diff --git a/tests/explicit-nulls/pos/java-varargs-src/Names.java b/tests/explicit-nulls/pos/java-varargs-src/Names.java new file mode 100644 index 000000000000..e46b406749ce --- /dev/null +++ b/tests/explicit-nulls/pos/java-varargs-src/Names.java @@ -0,0 +1,4 @@ + +class Names { + static void setNames(String... names) {} +} diff --git a/tests/explicit-nulls/pos/java-varargs-src/S.scala b/tests/explicit-nulls/pos/java-varargs-src/S.scala new file mode 100644 index 000000000000..5c180fcca400 --- /dev/null +++ b/tests/explicit-nulls/pos/java-varargs-src/S.scala @@ -0,0 +1,19 @@ + +// Test that nullification can handle Java varargs. +// For varargs, the element type is nullified, but the top level argument isn't. +class S { + // Pass an empty array. + Names.setNames() + + // Pass a singleton array with null as an element. + Names.setNames(null) + + // Pass a singleton array. + Names.setNames("name1") + + // Multiple arguments. + Names.setNames("name1", "name2", "name3", "name4") + + // Multiple arguments, some null. + Names.setNames(null, null, "hello", "world", null) +} diff --git a/tests/explicit-nulls/pos/java-varargs.scala b/tests/explicit-nulls/pos/java-varargs.scala new file mode 100644 index 000000000000..79d0bcb7cbfa --- /dev/null +++ b/tests/explicit-nulls/pos/java-varargs.scala @@ -0,0 +1,22 @@ + +import java.nio.file._ +import java.nio.file.Paths + + +class S { + + // Paths.get is a Java method with two arguments, where the second one + // is a varargs: https://docs.oracle.com/javase/8/docs/api/java/nio/file/Paths.html + // static Path get(String first, String... more) + // The Scala compiler converts this signature into + // def get(first: String|JavaNUll, more: (String|JavaNull)*) + + // Test that we can avoid providing the varargs argument altogether. + Paths.get("out").toAbsolutePath + + // Test with one argument in the varargs. + Paths.get("home", "src") + + // Test multiple arguments in the varargs. + Paths.get("home", "src", "compiler", "src") +} From feb11b0b0063f263e2a7e45e10524193372bfba5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 2 Oct 2019 12:02:01 -0400 Subject: [PATCH 19/54] modify methods in NullOpsDecorator --- .../tools/dotc/core/NullOpsDecorator.scala | 120 ++++++++++-------- .../src/dotty/tools/dotc/core/Types.scala | 18 ++- 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index aea72502ae34..2d55c1991ccc 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -8,16 +8,35 @@ import dotty.tools.dotc.core.Types.{AndType, ClassInfo, ConstantType, OrType, Ty object NullOpsDecorator { implicit class NullOps(val self: Type) { + def findAndStripNull(implicit ctx: Context): (Type, Boolean, Boolean) = { + var hasNull = false + var hasJavaNull = false + def strip(tp: Type): Type = tp match { + case OrType(lhs, rhs) => + val llhs = strip(lhs) + val rrhs = strip(rhs) + if (rrhs.isNullType) llhs + else if (llhs.isNullType) rrhs + else if ((lhs eq llhs) && (rhs eq rrhs)) tp + else OrType(llhs, rrhs) + case _ => + if (tp.isNullType) { + if (tp.isJavaNullType) hasJavaNull = true + else hasNull = true + } + tp + } + (strip(self), hasNull, hasJavaNull) + } + /** Is this type a reference to `Null`, possibly after aliasing? */ def isNullType(implicit ctx: Context): Boolean = self.isRef(defn.NullClass) /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ def isNullableUnion(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) - self.widenDealias.normNullableUnion match { - case OrType(_, right) => right.isNullType - case _ => false - } + val (_, hasNull, hasJavaNull) = self.widenDealias.findAndStripNull + hasNull || hasJavaNull } /** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */ @@ -32,17 +51,8 @@ object NullOpsDecorator { /** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */ def isJavaNullableUnion(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) - def peelOff(tpe: Type): Boolean = tpe match { - case OrType(left, right) => - right.isJavaNullType || right.isNullType && peelOff(left) - case _ => false - } - val norm = self.widenDealias.normNullableUnion - // We can't just look at the r.h.s of the normalized union. - // The problem is that after normalizing we could get a type like `(String | JavaNull) | Null`, - // where the r.h.s of the union isn't a `JavaNull`, but if we keep peeling nulls off starting - // from the right we'll eventually get to the `JavaNull`. - peelOff(norm) + val (_, _, hasJavaNull) = self.widenDealias.findAndStripNull + hasJavaNull } /** Is this type guaranteed not to have `null` as a value? */ @@ -63,46 +73,52 @@ object NullOpsDecorator { * In the process, it also flattens the type so that there are no nested unions at the top level. * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null | Null` */ + // def normNullableUnion(implicit ctx: Context): Type = { + // assert(ctx.explicitNulls) + // def split(tp: Type, nonNull: List[Type], nll: List[Type]): (List[Type], List[Type]) = { + // tp match { + // case OrType(left, right) => + // // Recurse on the right first so we get the types in pre-order. + // val (nonNull1, nll1) = split(right, nonNull, nll) + // split(left, nonNull1, nll1) + // case _ => + // if (tp.isNullType) (nonNull, tp :: nll) else (tp :: nonNull, nll) + // } + // } + // val (nonNull, nll) = split(self, Nil, Nil) + // val all = nonNull ++ nll + // assert(all.nonEmpty) + // all.tail.foldLeft(all.head)(OrType.apply) + // } + def normNullableUnion(implicit ctx: Context): Type = { - assert(ctx.explicitNulls) - def split(tp: Type, nonNull: List[Type], nll: List[Type]): (List[Type], List[Type]) = { - tp match { - case OrType(left, right) => - // Recurse on the right first so we get the types in pre-order. - val (nonNull1, nll1) = split(right, nonNull, nll) - split(left, nonNull1, nll1) - case _ => - if (tp.isNullType) (nonNull, tp :: nll) else (tp :: nonNull, nll) - } - } - val (nonNull, nll) = split(self, Nil, Nil) - val all = nonNull ++ nll - assert(all.nonEmpty) - all.tail.foldLeft(all.head)(OrType.apply) + val (tp, hasNull, hasJavaNull) = findAndStripNull + val tp1 = if (hasNull) OrType(tp, defn.NullType) else tp + if (hasJavaNull) OrType(tp1, defn.JavaNullAliasType) else tp1 } /** Strips `Null` from type unions in this type. * @param onlyJavaNull if true, we delete only `JavaNull` and not vanilla `Null`. */ - private def stripNullImpl(onlyJavaNull: Boolean)(implicit ctx: Context): Type = { - assert(ctx.explicitNulls) - def strip(tp: Type, changed: Boolean): (Type, Boolean) = { - tp match { - case OrType(left, right) if right.isNullType => - if (!onlyJavaNull || right.isJavaNullType) strip(left, changed = true) - else { - val (tp1, changed1) = strip(left, changed) - (OrType(tp1, right), changed1) - } - case _ => (tp, changed) - } - } - // If there are no `Null`s to strip off, try to keep the method a no-op - // by keeping track of whether the result has changed. - // Otherwise, we would widen and dealias as a side effect. - val (tp, diff) = strip(self.widenDealias.normNullableUnion, changed = false) - if (diff) tp else self - } + // private def stripNullImpl(onlyJavaNull: Boolean)(implicit ctx: Context): Type = { + // assert(ctx.explicitNulls) + // def strip(tp: Type, changed: Boolean): (Type, Boolean) = { + // tp match { + // case OrType(left, right) if right.isNullType => + // if (!onlyJavaNull || right.isJavaNullType) strip(left, changed = true) + // else { + // val (tp1, changed1) = strip(left, changed) + // (OrType(tp1, right), changed1) + // } + // case _ => (tp, changed) + // } + // } + // // If there are no `Null`s to strip off, try to keep the method a no-op + // // by keeping track of whether the result has changed. + // // Otherwise, we would widen and dealias as a side effect. + // val (tp, diff) = strip(self.widenDealias.normNullableUnion, changed = false) + // if (diff) tp else self + // } /** Syntactically strips the nullability from this type. If the normalized form (as per `normNullableUnion`) * of this type is `T1 | ... | Tk | ... | Tn`, and all types in the range `Tk ... Tn` are references to `Null`, @@ -111,13 +127,15 @@ object NullOpsDecorator { */ def stripNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) - stripNullImpl(onlyJavaNull = false) + val (tp, _, _) = self.widenDealias.findAndStripNull + tp } /** Like `stripNull`, but removes only the `JavaNull`s. */ def stripJavaNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) - stripNullImpl(onlyJavaNull = true) + val (tp, hasNull, _) = self.widenDealias.findAndStripNull + if (hasNull) OrType(tp, defn.NullType) else tp } /** Collapses all `JavaNull` unions within this type, and not just the outermost ones (as `stripJavaNull` does). diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 29538e42d74a..d5ad58b3ed30 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1068,18 +1068,24 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(implicit ctx: Context): Type = widen match { + def widenUnion(implicit ctx: Context): Type = widen match { case tp @ OrType(tp1, tp2) => - if (ctx.explicitNulls && tp.isNullableUnion) { - // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - val OrType(leftTpe, nullTpe) = tp.normNullableUnion - OrType(leftTpe.widenUnion, nullTpe) - } else { + lazy val defaultp = ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { case union: OrType => union.join case res => res } + if (ctx.explicitNulls) { + // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + val (ttp, hasNull, hasJavaNull) = tp.findAndStripNull + if (hasNull || hasJavaNull) { + val ttp1 = ttp.widenUnion + val ttp2 = if (hasNull) OrType(ttp1, defn.NullType) else ttp1 + if (hasJavaNull) OrType(ttp2, defn.JavaNullAliasType) else ttp2 + } + else defaultp } + else defaultp case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnion, tp2.widenUnion) case tp: RefinedType => From 0000c28be6c840c6a8fd85a08c84e52ec9213e9d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 2 Oct 2019 15:14:23 -0400 Subject: [PATCH 20/54] rename and adjust methods --- .../tools/dotc/core/NullOpsDecorator.scala | 158 ++++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 16 +- tests/explicit-nulls/neg/nullnull.scala | 9 + 3 files changed, 92 insertions(+), 91 deletions(-) create mode 100644 tests/explicit-nulls/neg/nullnull.scala diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 2d55c1991ccc..02f1e96857e4 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -8,17 +8,45 @@ import dotty.tools.dotc.core.Types.{AndType, ClassInfo, ConstantType, OrType, Ty object NullOpsDecorator { implicit class NullOps(val self: Type) { - def findAndStripNull(implicit ctx: Context): (Type, Boolean, Boolean) = { + + /** Is this type a reference to `Null`, possibly after aliasing? */ + def isNullType(implicit ctx: Context): Boolean = self.isRef(defn.NullClass) + + /** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */ + def isJavaNullType(implicit ctx: Context): Boolean = { + assert(ctx.explicitNulls) + // We can't do `self == defn.JavaNull` because when trees are unpickled new references + // to `JavaNull` could be created that are different from `defn.JavaNull`. + // Instead, we compare the symbol. + self.isDirectRef(defn.JavaNullAlias) + } + + /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of + * all other types. + * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null` + * e.g. `JavaNull | (T1 | Null) | Null` => `T1 | Null | JavaNull` + * + * 1. If self is not a union type, the result is not a union type. + * 2. If self is a union type, + * (1) If it only contains `Null` (or `JavaNull`) types, the result is still a union + only contains `Null` types. + * (2) If it contains multiple Null types, at most 2 different Null types + * will appear at the right of result. + * (3) If it contains both `Null` and `JavaNull`, the result is `(tp | Null) | JavaNull`. + * (4) If it onlt contains `Null` or `JavaNull`, the result is `tp | Null` or `tp | JavaNull`. + */ + def normNullableUnion(implicit ctx: Context): Type = { + var isUnion = false var hasNull = false var hasJavaNull = false def strip(tp: Type): Type = tp match { - case OrType(lhs, rhs) => + case tp @ OrType(lhs, rhs) => + isUnion = true val llhs = strip(lhs) val rrhs = strip(rhs) if (rrhs.isNullType) llhs else if (llhs.isNullType) rrhs - else if ((lhs eq llhs) && (rhs eq rrhs)) tp - else OrType(llhs, rrhs) + else tp.derivedOrType(llhs, rrhs) case _ => if (tp.isNullType) { if (tp.isJavaNullType) hasJavaNull = true @@ -26,33 +54,31 @@ object NullOpsDecorator { } tp } - (strip(self), hasNull, hasJavaNull) + val tp = strip(self) + if (!isUnion) self + else { + val tp1 = if (hasNull) OrType(tp, defn.NullType) else tp + if (hasJavaNull) OrType(tp1, defn.JavaNullAliasType) else tp1 + } } - /** Is this type a reference to `Null`, possibly after aliasing? */ - def isNullType(implicit ctx: Context): Boolean = self.isRef(defn.NullClass) - /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ def isNullableUnion(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) - val (_, hasNull, hasJavaNull) = self.widenDealias.findAndStripNull - hasNull || hasJavaNull - } - - /** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */ - def isJavaNullType(implicit ctx: Context): Boolean = { - assert(ctx.explicitNulls) - // We can't do `self == defn.JavaNull` because when trees are unpickled new references - // to `JavaNull` could be created that are different from `defn.JavaNull`. - // Instead, we compare the symbol. - self.isDirectRef(defn.JavaNullAlias) + self.widenDealias.normNullableUnion match { + case OrType(_, rhs) => rhs.isNullType + case _ => false + } } /** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */ def isJavaNullableUnion(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) - val (_, _, hasJavaNull) = self.widenDealias.findAndStripNull - hasJavaNull + self.widenDealias.normNullableUnion match { + // If it a `JavaNull` Union, the most right type must be a `JavaNull`. + case OrType(_, rhs) => rhs.isJavaNullType + case _ => false + } } /** Is this type guaranteed not to have `null` as a value? */ @@ -69,73 +95,33 @@ object NullOpsDecorator { def maybeNullable(implicit ctx: Context): Type = if (ctx.explicitNulls) OrType(self, defn.NullType) else self - /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of all other types. - * In the process, it also flattens the type so that there are no nested unions at the top level. - * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null | Null` - */ - // def normNullableUnion(implicit ctx: Context): Type = { - // assert(ctx.explicitNulls) - // def split(tp: Type, nonNull: List[Type], nll: List[Type]): (List[Type], List[Type]) = { - // tp match { - // case OrType(left, right) => - // // Recurse on the right first so we get the types in pre-order. - // val (nonNull1, nll1) = split(right, nonNull, nll) - // split(left, nonNull1, nll1) - // case _ => - // if (tp.isNullType) (nonNull, tp :: nll) else (tp :: nonNull, nll) - // } - // } - // val (nonNull, nll) = split(self, Nil, Nil) - // val all = nonNull ++ nll - // assert(all.nonEmpty) - // all.tail.foldLeft(all.head)(OrType.apply) - // } - - def normNullableUnion(implicit ctx: Context): Type = { - val (tp, hasNull, hasJavaNull) = findAndStripNull - val tp1 = if (hasNull) OrType(tp, defn.NullType) else tp - if (hasJavaNull) OrType(tp1, defn.JavaNullAliasType) else tp1 - } - - /** Strips `Null` from type unions in this type. - * @param onlyJavaNull if true, we delete only `JavaNull` and not vanilla `Null`. - */ - // private def stripNullImpl(onlyJavaNull: Boolean)(implicit ctx: Context): Type = { - // assert(ctx.explicitNulls) - // def strip(tp: Type, changed: Boolean): (Type, Boolean) = { - // tp match { - // case OrType(left, right) if right.isNullType => - // if (!onlyJavaNull || right.isJavaNullType) strip(left, changed = true) - // else { - // val (tp1, changed1) = strip(left, changed) - // (OrType(tp1, right), changed1) - // } - // case _ => (tp, changed) - // } - // } - // // If there are no `Null`s to strip off, try to keep the method a no-op - // // by keeping track of whether the result has changed. - // // Otherwise, we would widen and dealias as a side effect. - // val (tp, diff) = strip(self.widenDealias.normNullableUnion, changed = false) - // if (diff) tp else self - // } - - /** Syntactically strips the nullability from this type. If the normalized form (as per `normNullableUnion`) - * of this type is `T1 | ... | Tk | ... | Tn`, and all types in the range `Tk ... Tn` are references to `Null`, - * then return `T1 | ... | Tk-1`. + /** Syntactically strips the nullability from this type. + * If the normalized form (as per `normNullableUnion`) of this type is `T1 | ... | Tn-1 | Tn`, + * and `Tn-1` `Tn` are references to `Null`, then return `T1 | ... | Tn-2`. * If this type isn't (syntactically) nullable, then returns the type unchanged. */ def stripNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) - val (tp, _, _) = self.widenDealias.findAndStripNull - tp + self.widenDealias.normNullableUnion match { + case OrType(lhs, rhs) if rhs.isNullType => + lhs match { + case OrType(llhs, lrhs) if lrhs.isNullType => + // The result type contains at most 2 `Null` (or aliases to `Null`) type. + llhs + case _ => + lhs + } + case _ => self + } } /** Like `stripNull`, but removes only the `JavaNull`s. */ def stripJavaNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) - val (tp, hasNull, _) = self.widenDealias.findAndStripNull - if (hasNull) OrType(tp, defn.NullType) else tp + self.widenDealias.normNullableUnion match { + case OrType(lhs, rhs) if rhs.isJavaNullType => lhs + case _ => self + } } /** Collapses all `JavaNull` unions within this type, and not just the outermost ones (as `stripJavaNull` does). @@ -147,12 +133,14 @@ object NullOpsDecorator { assert(ctx.explicitNulls) var diff = false object RemoveNulls extends TypeMap { - override def apply(tp: Type): Type = tp match { - case tp: OrType if tp.isJavaNullableUnion => - diff = true - mapOver(tp.stripJavaNull) - case _ => mapOver(tp) - } + override def apply(tp: Type): Type = + tp.normNullableUnion match { + // It it is JavaNullable Union + case OrType(lhs, rhs) if rhs.isJavaNullType => + diff = true + mapOver(lhs) + case _ => mapOver(tp) + } } val rem = RemoveNulls(self.widenDealias) if (diff) rem else self diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d5ad58b3ed30..3662708e1d65 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1077,13 +1077,17 @@ object Types { } if (ctx.explicitNulls) { // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - val (ttp, hasNull, hasJavaNull) = tp.findAndStripNull - if (hasNull || hasJavaNull) { - val ttp1 = ttp.widenUnion - val ttp2 = if (hasNull) OrType(ttp1, defn.NullType) else ttp1 - if (hasJavaNull) OrType(ttp2, defn.JavaNullAliasType) else ttp2 + tp.normNullableUnion match { + // If it is a nullable union + case OrType(lhs, rhs) if rhs.isNullType => + lhs match { + case OrType(llhs, lrhs) if lrhs.isNullType => + OrType(OrType(llhs.widenUnion, lrhs), rhs) + case _ => + OrType(lhs.widenUnion, rhs) + } + case _ => defaultp } - else defaultp } else defaultp case tp @ AndType(tp1, tp2) => diff --git a/tests/explicit-nulls/neg/nullnull.scala b/tests/explicit-nulls/neg/nullnull.scala new file mode 100644 index 000000000000..0e87be931fca --- /dev/null +++ b/tests/explicit-nulls/neg/nullnull.scala @@ -0,0 +1,9 @@ +// Test that Null | Null will not cause crash during typing. + +class Foo { + def foo1: Unit = { + val x: Null | Null | Null = ??? + if (x == null) return () + val y = x.length + } +} \ No newline at end of file From aabb4e12a699b5fcbb85b7be424aae280c140988 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 2 Oct 2019 15:19:28 -0400 Subject: [PATCH 21/54] update nullnull test --- tests/explicit-nulls/neg/nullnull.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/explicit-nulls/neg/nullnull.scala b/tests/explicit-nulls/neg/nullnull.scala index 0e87be931fca..7d74b32c5538 100644 --- a/tests/explicit-nulls/neg/nullnull.scala +++ b/tests/explicit-nulls/neg/nullnull.scala @@ -4,6 +4,6 @@ class Foo { def foo1: Unit = { val x: Null | Null | Null = ??? if (x == null) return () - val y = x.length + val y = x.length // error: x: Null is inferred } -} \ No newline at end of file +} From 712e2ca9e66393deb54f324b1d87a8865c5fece6 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 4 Oct 2019 11:33:57 -0400 Subject: [PATCH 22/54] simplify the result of normNullableUnion --- .../tools/dotc/core/NullOpsDecorator.scala | 37 +++++++------------ .../src/dotty/tools/dotc/core/Types.scala | 13 ++----- tests/explicit-nulls/neg/nullnull.scala | 11 +++++- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 02f1e96857e4..9fd572050ffa 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -8,7 +8,6 @@ import dotty.tools.dotc.core.Types.{AndType, ClassInfo, ConstantType, OrType, Ty object NullOpsDecorator { implicit class NullOps(val self: Type) { - /** Is this type a reference to `Null`, possibly after aliasing? */ def isNullType(implicit ctx: Context): Boolean = self.isRef(defn.NullClass) @@ -24,16 +23,15 @@ object NullOpsDecorator { /** Normalizes unions so that all `Null`s (or aliases to `Null`) appear to the right of * all other types. * e.g. `Null | (T1 | Null) | T2` => `T1 | T2 | Null` - * e.g. `JavaNull | (T1 | Null) | Null` => `T1 | Null | JavaNull` + * e.g. `JavaNull | (T1 | Null) | Null` => `T1 | JavaNull` * - * 1. If self is not a union type, the result is not a union type. - * 2. If self is a union type, - * (1) If it only contains `Null` (or `JavaNull`) types, the result is still a union - only contains `Null` types. - * (2) If it contains multiple Null types, at most 2 different Null types - * will appear at the right of result. - * (3) If it contains both `Null` and `JavaNull`, the result is `(tp | Null) | JavaNull`. - * (4) If it onlt contains `Null` or `JavaNull`, the result is `tp | Null` or `tp | JavaNull`. + * Let `self` denote the current type: + * 1. If `self` is not a union, then the result is not a union and equal to `self`. + * 2. If `self` is a union then + * 2.1 If `self` does not contain `Null` as part of the union, then the result is `self`. + * 2.2 If `self` contains `Null` (resp `JavaNull`) as part of the union, let `self2` denote + * the same type as `self`, but where all instances of `Null` (`JavaNull`) in the union + * have been removed. Then the result is `self2 | Null` (`self2 | JavaNull`). */ def normNullableUnion(implicit ctx: Context): Type = { var isUnion = false @@ -56,10 +54,9 @@ object NullOpsDecorator { } val tp = strip(self) if (!isUnion) self - else { - val tp1 = if (hasNull) OrType(tp, defn.NullType) else tp - if (hasJavaNull) OrType(tp1, defn.JavaNullAliasType) else tp1 - } + else if (hasJavaNull) OrType(tp, defn.JavaNullAliasType) + else if (hasNull) OrType(tp, defn.NullType) + else self } /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ @@ -97,20 +94,13 @@ object NullOpsDecorator { /** Syntactically strips the nullability from this type. * If the normalized form (as per `normNullableUnion`) of this type is `T1 | ... | Tn-1 | Tn`, - * and `Tn-1` `Tn` are references to `Null`, then return `T1 | ... | Tn-2`. + * and `Tn` references to `Null` (or `JavaNull`), then return `T1 | ... | Tn-1`. * If this type isn't (syntactically) nullable, then returns the type unchanged. */ def stripNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) self.widenDealias.normNullableUnion match { - case OrType(lhs, rhs) if rhs.isNullType => - lhs match { - case OrType(llhs, lrhs) if lrhs.isNullType => - // The result type contains at most 2 `Null` (or aliases to `Null`) type. - llhs - case _ => - lhs - } + case OrType(lhs, rhs) if rhs.isNullType => lhs case _ => self } } @@ -135,7 +125,6 @@ object NullOpsDecorator { object RemoveNulls extends TypeMap { override def apply(tp: Type): Type = tp.normNullableUnion match { - // It it is JavaNullable Union case OrType(lhs, rhs) if rhs.isJavaNullType => diff = true mapOver(lhs) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3662708e1d65..70566bcd8345 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1078,15 +1078,10 @@ object Types { if (ctx.explicitNulls) { // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. tp.normNullableUnion match { - // If it is a nullable union - case OrType(lhs, rhs) if rhs.isNullType => - lhs match { - case OrType(llhs, lrhs) if lrhs.isNullType => - OrType(OrType(llhs.widenUnion, lrhs), rhs) - case _ => - OrType(lhs.widenUnion, rhs) - } - case _ => defaultp + case tp @ OrType(lhs, rhs) if rhs.isNullType => + tp.derivedOrType(lhs.widenUnion, rhs) + case _ => + defaultp } } else defaultp diff --git a/tests/explicit-nulls/neg/nullnull.scala b/tests/explicit-nulls/neg/nullnull.scala index 7d74b32c5538..1ebb25bf6238 100644 --- a/tests/explicit-nulls/neg/nullnull.scala +++ b/tests/explicit-nulls/neg/nullnull.scala @@ -1,4 +1,7 @@ -// Test that Null | Null will not cause crash during typing. +// Test that `Null | Null | ... | Null` will not cause crash during typing. +// We want to strip `Null`s from the type after the `if` statement. +// After `normNullableUnion`, `Null | Null | ... | Null` should become +// `Null | Null`, and `stripNull` will return type `Null`. class Foo { def foo1: Unit = { @@ -6,4 +9,10 @@ class Foo { if (x == null) return () val y = x.length // error: x: Null is inferred } + + def foo2: Unit = { + val x: JavaNull | String | Null = ??? + if (x == null) return () + val y = x.length // ok: x: String is inferred + } } From 55db452267f4e72a72799a144ca3d76c07c62eeb Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 4 Oct 2019 11:36:19 -0400 Subject: [PATCH 23/54] remove extra space --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 70566bcd8345..297090f9091c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1068,7 +1068,7 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(implicit ctx: Context): Type = widen match { + def widenUnion(implicit ctx: Context): Type = widen match { case tp @ OrType(tp1, tp2) => lazy val defaultp = ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { From 9595870701c50294c4a5c7fb3b36772c79baa6d5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 4 Oct 2019 16:31:00 -0400 Subject: [PATCH 24/54] optimize widenUnion --- .../src/dotty/tools/dotc/core/Types.scala | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 297090f9091c..6945fc95ef4a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1068,31 +1068,36 @@ object Types { * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ - def widenUnion(implicit ctx: Context): Type = widen match { - case tp @ OrType(tp1, tp2) => - lazy val defaultp = - ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match { + def widenUnion(implicit ctx: Context): Type = { + def joinOrUnion(tp: Type): Type = tp match { + case OrType(tp1, tp2) => + ctx.typeComparer.lub(joinOrUnion(tp1), joinOrUnion(tp2), canConstrain = true) match { case union: OrType => union.join case res => res } - if (ctx.explicitNulls) { - // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - tp.normNullableUnion match { - case tp @ OrType(lhs, rhs) if rhs.isNullType => - tp.derivedOrType(lhs.widenUnion, rhs) - case _ => - defaultp - } - } - else defaultp - case tp @ AndType(tp1, tp2) => - tp derived_& (tp1.widenUnion, tp2.widenUnion) - case tp: RefinedType => - tp.derivedRefinedType(tp.parent.widenUnion, tp.refinedName, tp.refinedInfo) - case tp: RecType => - tp.rebind(tp.parent.widenUnion) - case tp => - tp + case tp => + tp.widenUnion + } + widen match { + case tp: OrType => + if (ctx.explicitNulls) + // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + tp.normNullableUnion match { + case tpn @ OrType(lhs, rhs) if rhs.isNullType => + tpn.derivedOrType(joinOrUnion(lhs), rhs) + case _ => + joinOrUnion(tp) + } + else joinOrUnion(tp) + case tp @ AndType(tp1, tp2) => + tp derived_& (tp1.widenUnion, tp2.widenUnion) + case tp: RefinedType => + tp.derivedRefinedType(tp.parent.widenUnion, tp.refinedName, tp.refinedInfo) + case tp: RecType => + tp.rebind(tp.parent.widenUnion) + case tp => + tp + } } /** Widen all top-level singletons reachable by dealiasing From 7663fc2061157be6124cd0b9c21ff8f4266b2a32 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 7 Oct 2019 15:35:29 -0400 Subject: [PATCH 25/54] rewrite widenUnion --- .../tools/dotc/core/NullOpsDecorator.scala | 1 - .../src/dotty/tools/dotc/core/Types.scala | 46 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 9fd572050ffa..7b6e55efe47c 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -72,7 +72,6 @@ object NullOpsDecorator { def isJavaNullableUnion(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) self.widenDealias.normNullableUnion match { - // If it a `JavaNull` Union, the most right type must be a `JavaNull`. case OrType(_, rhs) => rhs.isJavaNullType case _ => false } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6945fc95ef4a..38e4d633a358 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1069,26 +1069,40 @@ object Types { * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ def widenUnion(implicit ctx: Context): Type = { - def joinOrUnion(tp: Type): Type = tp match { - case OrType(tp1, tp2) => - ctx.typeComparer.lub(joinOrUnion(tp1), joinOrUnion(tp2), canConstrain = true) match { - case union: OrType => union.join - case res => res - } - case tp => - tp.widenUnion + def normalizeRightOrNull(tp: OrType, lhs: Type, rhs: Type): Type = lhs match { + case tpp @ OrType(tpp1, tpp2) => + if (tpp2.isJavaNullType) tpp + else tpp.derivedOrType(tpp1, rhs) + case tpp => + tp.derivedOrType(tpp, rhs) } + widen match { - case tp: OrType => - if (ctx.explicitNulls) + case tp @ OrType(lhs, rhs) => + def defaultJoin(tp1: Type, tp2: Type) = + ctx.typeComparer.lub(tp1, tp2, canConstrain = true) match { + case union: OrType => union.join + case res => res + } + if (ctx.explicitNulls) { // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - tp.normNullableUnion match { - case tpn @ OrType(lhs, rhs) if rhs.isNullType => - tpn.derivedOrType(joinOrUnion(lhs), rhs) - case _ => - joinOrUnion(tp) + if (rhs.isNullType) + normalizeRightOrNull(tp, lhs.widenUnion, rhs) + else if (lhs.isNullType) + normalizeRightOrNull(tp, rhs.widenUnion, lhs) + else (lhs.widenUnion, rhs.widenUnion) match { + case (lhsp @ OrType(lhsp1, lhsp2), rhsp @ OrType(rhsp1, rhsp2)) => + val nt = if (lhsp2.isJavaNullType) lhsp2 else rhsp2 + lhsp.derivedOrType(defaultJoin(lhsp1, rhsp1), nt) + case (lhsp @ OrType(lhsp1, lhsp2), rhsp) => + lhsp.derivedOrType(defaultJoin(lhsp1, rhsp), lhsp2) + case (lhsp, rhsp @ OrType(rhsp1, rhsp2)) => + rhsp.derivedOrType(defaultJoin(lhsp, rhsp1), rhsp2) + case (lhsp, rhsp) => + defaultJoin(lhsp, rhsp) } - else joinOrUnion(tp) + } + else defaultJoin(lhs.widenUnion, rhs.widenUnion) case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnion, tp2.widenUnion) case tp: RefinedType => From 53a88cdfa462a6270eefadc3d8b9dcdfe283638b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 8 Oct 2019 14:11:23 -0400 Subject: [PATCH 26/54] further simplify widenUnion --- .../src/dotty/tools/dotc/core/Types.scala | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 38e4d633a358..d0ce8b44c2bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1069,14 +1069,6 @@ object Types { * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ def widenUnion(implicit ctx: Context): Type = { - def normalizeRightOrNull(tp: OrType, lhs: Type, rhs: Type): Type = lhs match { - case tpp @ OrType(tpp1, tpp2) => - if (tpp2.isJavaNullType) tpp - else tpp.derivedOrType(tpp1, rhs) - case tpp => - tp.derivedOrType(tpp, rhs) - } - widen match { case tp @ OrType(lhs, rhs) => def defaultJoin(tp1: Type, tp2: Type) = @@ -1084,22 +1076,32 @@ object Types { case union: OrType => union.join case res => res } + + // Given a type `tpe`, if it is already a nullable union, return it unchanged. + // Otherwise, construct a nullable union where `tpe` is the lhs (use `orig` to + // potentially avoid creating a new object for the union). + def ensureNullableUnion(tpe: Type, orig: OrType): Type = tpe match { + case orTpe: OrType if orTpe.tp2.isNullType => tpe + case _ => orig.derivedOrType(tpe, defn.NullType) + } + + // Test for nullable union that assumes the type has already been normalized. + def isNullableUnionFast(tp: Type): Boolean = tp match { + case orTpe: OrType if orTpe.tp2.isNullType => true + case _ => false + } + if (ctx.explicitNulls) { // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - if (rhs.isNullType) - normalizeRightOrNull(tp, lhs.widenUnion, rhs) - else if (lhs.isNullType) - normalizeRightOrNull(tp, rhs.widenUnion, lhs) - else (lhs.widenUnion, rhs.widenUnion) match { - case (lhsp @ OrType(lhsp1, lhsp2), rhsp @ OrType(rhsp1, rhsp2)) => - val nt = if (lhsp2.isJavaNullType) lhsp2 else rhsp2 - lhsp.derivedOrType(defaultJoin(lhsp1, rhsp1), nt) - case (lhsp @ OrType(lhsp1, lhsp2), rhsp) => - lhsp.derivedOrType(defaultJoin(lhsp1, rhsp), lhsp2) - case (lhsp, rhsp @ OrType(rhsp1, rhsp2)) => - rhsp.derivedOrType(defaultJoin(lhsp, rhsp1), rhsp2) - case (lhsp, rhsp) => - defaultJoin(lhsp, rhsp) + if (rhs.isNullType) ensureNullableUnion(lhs.widenUnion, tp) + else if (lhs.isNullType) ensureNullableUnion(rhs.widenUnion, tp) + else { + val lhsWiden = lhs.widenUnion + val rhsWiden = rhs.widenUnion + val tmpRes = defaultJoin(lhs.widenUnion, rhs.widenUnion) + if (isNullableUnionFast(lhsWiden) || isNullableUnionFast(rhsWiden)) + ensureNullableUnion(tmpRes, tp) + else tmpRes } } else defaultJoin(lhs.widenUnion, rhs.widenUnion) From fdea080650c91fc6ec17d136281dfbf396c99a23 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 8 Oct 2019 14:42:04 -0400 Subject: [PATCH 27/54] add comment --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d0ce8b44c2bc..d1a098101912 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1093,6 +1093,8 @@ object Types { if (ctx.explicitNulls) { // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + // This part relies on the postcondition of widenUnion: the result is either a + // non-union type, or a nullable union type where the rhs is `Null` type. if (rhs.isNullType) ensureNullableUnion(lhs.widenUnion, tp) else if (lhs.isNullType) ensureNullableUnion(rhs.widenUnion, tp) else { @@ -1100,6 +1102,8 @@ object Types { val rhsWiden = rhs.widenUnion val tmpRes = defaultJoin(lhs.widenUnion, rhs.widenUnion) if (isNullableUnionFast(lhsWiden) || isNullableUnionFast(rhsWiden)) + // If either lhs or rhs is a nullable union, + // we need to ensure the result is also a nullable union. ensureNullableUnion(tmpRes, tp) else tmpRes } From f9ca2739bf1191fef83fd93c9a293ea08bfd3957 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 3 Oct 2019 16:26:26 -0400 Subject: [PATCH 28/54] inline policies in JavaNullInterop --- .../tools/dotc/core/JavaNullInterop.scala | 60 ++++++------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 3be62a45af1d..3a96c2fe9a9f 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -56,53 +56,28 @@ object JavaNullInterop { assert(ctx.explicitNulls) assert(sym.is(JavaDefined), "can only nullify java-defined members") - // A list of "policies" that special-case certain members. - // The policies should be disjoint: we use the first one that is applicable. - val whitelist: Seq[NullifyPolicy] = Seq( - // The `TYPE` field in every class: don't nullify. - NoOpPolicy(_.name == nme.TYPE_), - // The `toString` method: don't nullify the return type. - paramsOnlyPolicy(_.name == nme.toString_), - // Constructors: params are nullified, but the result type isn't. - paramsOnlyPolicy(_.isConstructor), - // Java enum instances: don't nullify. - NoOpPolicy(_.isAllOf(Flags.JavaEnumValue)) - ) - - whitelist.find(_.isApplicable(sym)) match { - case Some(pol) => pol(tp) - case None => nullifyType(tp) // default case: nullify everything - } - } - - /** A policy that special cases the handling of some symbol. */ - private sealed trait NullifyPolicy { - /** Whether the policy applies to `sym`. */ - def isApplicable(sym: Symbol): Boolean - /** Nullifies `tp` according to the policy. Should call `isApplicable` first. */ - def apply(tp: Type): Type + // 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) + // Don't nullify the return type of the `toString` method and constructors + nullifyParamsOnly(tp) + else + // Otherwise, nullify everything + nullifyType(tp) } - /** A policy that leaves the passed-in type unchanged. */ - private case class NoOpPolicy(trigger: Symbol => Boolean) extends NullifyPolicy { - override def isApplicable(sym: Symbol): Boolean = trigger(sym) - - override def apply(tp: Type): Type = tp - } - - /** A policy for handling a method or poly. - * @param trigger determines whether the policy applies to a given symbol. - * @param nnParams the indices of the method parameters that should be considered "non-null" (should not be nullified). + /** A Nullifier for handling a method or poly. + * @param nnParams the indices of the method parameters that should be considered + * "non-null" (should not be nullified). * @param nnRes whether the result type should be nullified. * * For the purposes of both `nnParams` and `nnRes`, when a parameter or return type is not nullified, * this applies only at the top level. e.g. suppose we have a Java result type `Array[String]` and `nnRes` is set. * Scala will see `Array[String|JavaNull]`; the array element type is still nullified. */ - private case class MethodPolicy(trigger: Symbol => Boolean, - nnParams: Seq[Int], - nnRes: Boolean)(implicit ctx: Context) extends TypeMap with NullifyPolicy { - override def isApplicable(sym: Symbol): Boolean = trigger(sym) + private case class MethodNullifier(nnParams: Seq[Int], nnRes: Boolean)(implicit ctx: Context) extends TypeMap { private def spare(tp: Type): Type = { nullifyType(tp).stripNull @@ -125,10 +100,9 @@ object JavaNullInterop { } } - /** A policy that nullifies only method parameters (but not result types). */ - private def paramsOnlyPolicy(trigger: Symbol => Boolean)(implicit ctx: Context): MethodPolicy = { - MethodPolicy(trigger, nnParams = Seq.empty, nnRes = true) - } + /** Only nullify method parameters (but not result types). */ + private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = + MethodNullifier(nnParams = Seq.empty, nnRes = true)(ctx)(tp) /** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ private def nullifyType(tpe: Type)(implicit ctx: Context): Type = { From 92095a2bc52b8499ecccf2c121226f7be4f1235b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 8 Oct 2019 14:03:40 -0400 Subject: [PATCH 29/54] remove extra braces --- .../tools/dotc/core/JavaNullInterop.scala | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 3a96c2fe9a9f..74ac3849498f 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -79,24 +79,20 @@ object JavaNullInterop { */ private case class MethodNullifier(nnParams: Seq[Int], nnRes: Boolean)(implicit ctx: Context) extends TypeMap { - private def spare(tp: Type): Type = { - nullifyType(tp).stripNull - } + private def spare(tp: Type): Type = nullifyType(tp).stripNull - override def apply(tp: Type): Type = { - tp match { - case ptp: PolyType => - derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) - case mtp: MethodType => - val paramTpes = mtp.paramInfos.zipWithIndex.map { - case (paramInfo, index) => - // TODO(abeln): the sequence lookup can be optimized, because the indices - // in it appear in increasing order. - if (nnParams.contains(index)) spare(paramInfo) else nullifyType(paramInfo) - } - val resTpe = if (nnRes) spare(mtp.resType) else nullifyType(mtp.resType) - derivedLambdaType(mtp)(paramTpes, resTpe) - } + override def apply(tp: Type): Type = tp match { + case ptp: PolyType => + derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) + case mtp: MethodType => + val paramTpes = mtp.paramInfos.zipWithIndex.map { + case (paramInfo, index) => + // TODO(abeln): the sequence lookup can be optimized, because the indices + // in it appear in increasing order. + if (nnParams.contains(index)) spare(paramInfo) else nullifyType(paramInfo) + } + val resTpe = if (nnRes) spare(mtp.resType) else nullifyType(mtp.resType) + derivedLambdaType(mtp)(paramTpes, resTpe) } } @@ -105,10 +101,8 @@ object JavaNullInterop { MethodNullifier(nnParams = Seq.empty, nnRes = true)(ctx)(tp) /** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ - private def nullifyType(tpe: Type)(implicit ctx: Context): Type = { - val nullMap = new JavaNullMap(alreadyNullable = false) - nullMap(tpe) - } + private def nullifyType(tpe: Type)(implicit ctx: Context): Type = + new JavaNullMap(alreadyNullable = false)(ctx)(tpe) /** A type map that adds `| JavaNull`. * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). @@ -117,7 +111,7 @@ object JavaNullInterop { */ private class JavaNullMap(var alreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the outermost level? */ - def needsTopLevelNull(tp: Type): Boolean = { + def needsTopLevelNull(tp: Type): Boolean = !alreadyNullable && (tp match { case tp: TypeRef => // We don't modify value types because they're non-nullable even in Java. @@ -132,16 +126,13 @@ object JavaNullInterop { !tp.isRef(defn.RepeatedParamClass) case _ => true }) - } /** Should we nullify the type arguments to the given generic `tp`? * We only nullify the inside of Scala-defined generics. * This is because Java classes are _all_ nullified, so both `java.util.List[String]` and * `java.util.List[String|Null]` contain nullable elements. */ - def needsNullArgs(tp: AppliedType): Boolean = { - !tp.classSymbol.is(JavaDefined) - } + def needsNullArgs(tp: AppliedType): Boolean = !tp.classSymbol.is(JavaDefined) override def apply(tp: Type): Type = { tp match { From 0b35a436ab6a9cecf3077451e79737d6dadc3c61 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 8 Oct 2019 16:17:32 -0400 Subject: [PATCH 30/54] combine two type mapper --- .../tools/dotc/core/JavaNullInterop.scala | 51 ++++++------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 74ac3849498f..9a20b1aa1655 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -68,48 +68,21 @@ object JavaNullInterop { nullifyType(tp) } - /** A Nullifier for handling a method or poly. - * @param nnParams the indices of the method parameters that should be considered - * "non-null" (should not be nullified). - * @param nnRes whether the result type should be nullified. - * - * For the purposes of both `nnParams` and `nnRes`, when a parameter or return type is not nullified, - * this applies only at the top level. e.g. suppose we have a Java result type `Array[String]` and `nnRes` is set. - * Scala will see `Array[String|JavaNull]`; the array element type is still nullified. - */ - private case class MethodNullifier(nnParams: Seq[Int], nnRes: Boolean)(implicit ctx: Context) extends TypeMap { - - private def spare(tp: Type): Type = nullifyType(tp).stripNull - - override def apply(tp: Type): Type = tp match { - case ptp: PolyType => - derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) - case mtp: MethodType => - val paramTpes = mtp.paramInfos.zipWithIndex.map { - case (paramInfo, index) => - // TODO(abeln): the sequence lookup can be optimized, because the indices - // in it appear in increasing order. - if (nnParams.contains(index)) spare(paramInfo) else nullifyType(paramInfo) - } - val resTpe = if (nnRes) spare(mtp.resType) else nullifyType(mtp.resType) - derivedLambdaType(mtp)(paramTpes, resTpe) - } - } - /** Only nullify method parameters (but not result types). */ private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = - MethodNullifier(nnParams = Seq.empty, nnRes = true)(ctx)(tp) + new JavaNullMap(alreadyNullable = false, nnRes = true)(ctx)(tp) /** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ - private def nullifyType(tpe: Type)(implicit ctx: Context): Type = - new JavaNullMap(alreadyNullable = false)(ctx)(tpe) + private def nullifyType(tp: Type)(implicit ctx: Context): Type = + new JavaNullMap(alreadyNullable = false, nnRes = false)(ctx)(tp) /** A type map that adds `| JavaNull`. * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). * This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`, * instead of `(A|JavaNull | B|JavaNull) | JavaNull`. + * @param nnRes whether the result type should be nullified. */ - private class JavaNullMap(var alreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { + private class JavaNullMap(var alreadyNullable: Boolean, nnRes: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the outermost level? */ def needsTopLevelNull(tp: Type): Boolean = !alreadyNullable && (tp match { @@ -136,13 +109,21 @@ object JavaNullInterop { override def apply(tp: Type): Type = { tp match { - case tp: TypeRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion + case tp: TypeRef if needsTopLevelNull(tp) => + tp.toJavaNullableUnion case appTp @ AppliedType(tycons, targs) => val targs2 = if (needsNullArgs(appTp)) targs map this else targs val appTp2 = derivedAppliedType(appTp, tycons, targs2) if (needsTopLevelNull(tycons)) appTp2.toJavaNullableUnion else appTp2 - case tp: LambdaType => mapOver(tp) - case tp: TypeAlias => mapOver(tp) + case ptp: PolyType => + derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) + case mtp: MethodType => + val resTpe = if (nnRes) this(mtp.resType).stripNull else this(mtp.resType) + derivedLambdaType(mtp)(mtp.paramInfos map this, resTpe) + case tp: LambdaType => + mapOver(tp) + case tp: TypeAlias => + mapOver(tp) case tp @ AndType(tp1, tp2) => // 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`. From c7396db2bcef85e2fd351f5ef4d8eaf0d44a825f Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 9 Oct 2019 14:21:08 -0400 Subject: [PATCH 31/54] remove toJavaNullableUnion --- .../tools/dotc/core/JavaNullInterop.scala | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 9a20b1aa1655..02cd3d05205e 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -70,19 +70,20 @@ object JavaNullInterop { /** Only nullify method parameters (but not result types). */ private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = - new JavaNullMap(alreadyNullable = false, nnRes = true)(ctx)(tp) + new JavaNullMap(alreadyNullable = false, nonNullResultType = 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(alreadyNullable = false, nnRes = false)(ctx)(tp) + new JavaNullMap(alreadyNullable = false, nonNullResultType = false)(ctx)(tp) + /** A type map that adds `| JavaNull`. * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). * This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`, * instead of `(A|JavaNull | B|JavaNull) | JavaNull`. - * @param nnRes whether the result type should be nullified. + * @param nonNullResultType whether the result type of the top function should be nullified. */ - private class JavaNullMap(var alreadyNullable: Boolean, nnRes: Boolean)(implicit ctx: Context) extends TypeMap { + private class JavaNullMap(var alreadyNullable: Boolean, var nonNullResultType: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the outermost level? */ def needsTopLevelNull(tp: Type): Boolean = !alreadyNullable && (tp match { @@ -109,30 +110,31 @@ object JavaNullInterop { override def apply(tp: Type): Type = { tp match { - case tp: TypeRef if needsTopLevelNull(tp) => - tp.toJavaNullableUnion + case tp: TypeRef if needsTopLevelNull(tp) => OrType(tp, defn.JavaNullAliasType) case appTp @ AppliedType(tycons, targs) => + val oldAN = alreadyNullable + alreadyNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs + alreadyNullable = oldAN val appTp2 = derivedAppliedType(appTp, tycons, targs2) - if (needsTopLevelNull(tycons)) appTp2.toJavaNullableUnion else appTp2 + if (needsTopLevelNull(tycons)) OrType(appTp2, defn.JavaNullAliasType) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => - val resTpe = if (nnRes) this(mtp.resType).stripNull else this(mtp.resType) + val resTpe = if (nonNullResultType) { + nonNullResultType = false + this(mtp.resType).stripNull + } + else this(mtp.resType) derivedLambdaType(mtp)(mtp.paramInfos map this, resTpe) - case tp: LambdaType => - mapOver(tp) - case tp: TypeAlias => - mapOver(tp) - case tp @ AndType(tp1, tp2) => + case tp: LambdaType => mapOver(tp) + 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`. alreadyNullable = true - derivedAndType(tp, this(tp1), this(tp2)).toJavaNullableUnion - case tp @ OrType(tp1, tp2) if !tp.isJavaNullableUnion => - alreadyNullable = true - derivedOrType(tp, this(tp1), this(tp2)).toJavaNullableUnion - case tp: TypeParamRef if needsTopLevelNull(tp) => tp.toJavaNullableUnion + OrType(derivedAndType(tp, this(tp.tp1), this(tp.tp2)), defn.JavaNullAliasType) + case tp: TypeParamRef if needsTopLevelNull(tp) => OrType(tp, defn.JavaNullAliasType) case _ => tp } } From 28552f594ed6c1276b66fdb7b2f1e6b16d3ef312 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 9 Oct 2019 16:18:31 -0400 Subject: [PATCH 32/54] add a helper for OrType(tp, JavaNull) --- .../dotty/tools/dotc/core/JavaNullInterop.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 02cd3d05205e..1393c38d9cf5 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -81,7 +81,9 @@ object JavaNullInterop { * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). * This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`, * instead of `(A|JavaNull | B|JavaNull) | JavaNull`. - * @param nonNullResultType whether the result type of the top function should be nullified. + * @param nonNullResultType if true, then the current type is a method or generic method type, + * and we don't want to nullify its return type at the top level. + * This is useful e.g. for constructors. */ private class JavaNullMap(var alreadyNullable: Boolean, var nonNullResultType: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the outermost level? */ @@ -109,15 +111,19 @@ object JavaNullInterop { def needsNullArgs(tp: AppliedType): Boolean = !tp.classSymbol.is(JavaDefined) 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 needsTopLevelNull(tp) => OrType(tp, defn.JavaNullAliasType) + case tp: TypeRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp) case appTp @ AppliedType(tycons, targs) => val oldAN = alreadyNullable alreadyNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs alreadyNullable = oldAN val appTp2 = derivedAppliedType(appTp, tycons, targs2) - if (needsTopLevelNull(tycons)) OrType(appTp2, defn.JavaNullAliasType) else appTp2 + if (needsTopLevelNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => @@ -133,8 +139,8 @@ object JavaNullInterop { // 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`. alreadyNullable = true - OrType(derivedAndType(tp, this(tp.tp1), this(tp.tp2)), defn.JavaNullAliasType) - case tp: TypeParamRef if needsTopLevelNull(tp) => OrType(tp, defn.JavaNullAliasType) + toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) + case tp: TypeParamRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp) case _ => tp } } From e2c47d770071abfb73bf123d8c056a4acdeba04d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 11 Oct 2019 14:12:31 -0400 Subject: [PATCH 33/54] null is not allowed to be thrown; small edit to the code style --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 14 +++++++------- .../tools/dotc/transform/FirstTransform.scala | 1 - .../src/dotty/tools/dotc/typer/Applications.scala | 12 ++++-------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +------- tests/explicit-nulls/neg/throw-null.scala | 14 ++++++++++++++ tests/explicit-nulls/pos/throw-null.scala | 8 -------- 6 files changed, 26 insertions(+), 31 deletions(-) create mode 100644 tests/explicit-nulls/neg/throw-null.scala delete mode 100644 tests/explicit-nulls/pos/throw-null.scala diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index f4c13fdb6c0d..674ecfc85f1d 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -146,9 +146,6 @@ object Contexts { protected def gadt_=(gadt: GadtConstraint): Unit = _gadt = gadt final def gadt: GadtConstraint = _gadt - /** The terms currently known to be non-null (in spite of their declared type) */ - def flowFacts: FlowFacts = store(flowFactsLoc) - /** The history of implicit searches that are currently active */ private[this] var _searchHistory: SearchHistory = null protected def searchHistory_= (searchHistory: SearchHistory): Unit = _searchHistory = searchHistory @@ -213,6 +210,9 @@ object Contexts { /** The current compiler-run profiler */ def profiler: Profiler = store(profilerLoc) + /** The terms currently known to be non-null (in spite of their declared type) */ + def flowFacts: FlowFacts = store(flowFactsLoc) + /** The new implicit references that are introduced by this scope */ protected var implicitsCache: ContextualImplicits = null def implicits: ContextualImplicits = { @@ -546,10 +546,6 @@ object Contexts { def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this } def setGadt(gadt: GadtConstraint): this.type = { this.gadt = gadt; this } def setFreshGADTBounds: this.type = setGadt(gadt.fresh) - def addFlowFacts(facts: FlowFacts): this.type = { - assert(settings.YexplicitNulls.value) - updateStore(flowFactsLoc, store(flowFactsLoc) ++ facts) - } def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this } def setSource(source: SourceFile): this.type = { this.source = source; this } def setTypeComparerFn(tcfn: Context => TypeComparer): this.type = { this.typeComparer = tcfn(this); this } @@ -569,6 +565,10 @@ object Contexts { def setRun(run: Run): this.type = updateStore(runLoc, run) def setProfiler(profiler: Profiler): this.type = updateStore(profilerLoc, profiler) def setFreshNames(freshNames: FreshNameCreator): this.type = updateStore(freshNamesLoc, freshNames) + def addFlowFacts(facts: FlowFacts): this.type = { + assert(settings.YexplicitNulls.value) + updateStore(flowFactsLoc, store(flowFactsLoc) ++ facts) + } def setProperty[T](key: Key[T], value: T): this.type = setMoreProperties(moreProperties.updated(key, value)) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 1c805d3ed77e..465c44d45388 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -62,7 +62,6 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => // must derive from the type of the owner of `length`, which is `String`. Because we don't // know which `JavaNull`s were used to find the `length` member, we conservatively remove // all of them. - // TODO(abeln): is it too expensive to call `stripAllJavaNull` for all selections? qual.tpe.stripAllJavaNull } else { qual.tpe diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index b54cd7e98cbb..05ba7ef00052 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -858,22 +858,20 @@ trait Applications extends Compatibility { /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = { - val ctx1 = if (ctx.explicitNulls) { + implicit val ctxWithFacts: Context = if (ctx.explicitNulls) { // The flow facts of lhs are cached in the FlowFactsOnTree attachment val facts = fun1.getAttachment(Typer.FlowFactsOnTree) match { case Some(fs) => fs case None => - val fs = FlowTyper.inferWithinCond(fun1) + val fs = FlowTyper.inferWithinCond(fun1)(ctx) fun1.putAttachment(Typer.FlowFactsOnTree, fs) fs } if (facts.isEmpty) ctx else ctx.fresh.addFlowFacts(facts) - } else { - ctx } + else ctx - // Separate into a function so we can pass the updated context. - def proc(implicit ctx: Context): tpd.Tree = methPart(fun1).tpe match { + methPart(fun1).tpe match { case funRef: TermRef => val app = if (proto.allArgTypesAreCurrent()) @@ -884,8 +882,6 @@ trait Applications extends Compatibility { case _ => handleUnexpectedFunType(tree, fun1) } - - proc(ctx1) } /** Try same application with an implicit inserted around the qualifier of the function diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 93a66f03495f..d66d8fb95c34 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1276,13 +1276,7 @@ class Typer extends Namer } def typedThrow(tree: untpd.Throw)(implicit ctx: Context): Tree = { - val pt = if (ctx.explicitNulls) { - // `throw null` is valid Scala code - OrType(defn.ThrowableType, defn.NullType) - } else { - defn.ThrowableType - } - val expr1 = typed(tree.expr, pt) + val expr1 = typed(tree.expr, defn.ThrowableType) Throw(expr1).withSpan(tree.span) } diff --git a/tests/explicit-nulls/neg/throw-null.scala b/tests/explicit-nulls/neg/throw-null.scala new file mode 100644 index 000000000000..1fc3d4721155 --- /dev/null +++ b/tests/explicit-nulls/neg/throw-null.scala @@ -0,0 +1,14 @@ +// `throws null` is valid program in dotty but not valid with explicit null, +// since this statement will throw `NullPointerException` during runtime. +// https://stackoverflow.com/questions/17576922/why-can-i-throw-null-in-java + +class Foo { + def test1() = { + throw null // error: the expression cannot be `Null` + } + + def test2() = { + val t: Throwable | Null = ??? + throw t // error: the expression cannot be `Null` + } +} diff --git a/tests/explicit-nulls/pos/throw-null.scala b/tests/explicit-nulls/pos/throw-null.scala deleted file mode 100644 index 54def30492d2..000000000000 --- a/tests/explicit-nulls/pos/throw-null.scala +++ /dev/null @@ -1,8 +0,0 @@ -// Check that `throws null` still typechecks. -// https://stackoverflow.com/questions/17576922/why-can-i-throw-null-in-java - -class Foo { - throw null // throws an npe - val npe: NullPointerException|Null = ??? - throw npe -} From ec880ade9b922c8fa99b0c2a6261dfd43a91e3f5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 11 Oct 2019 16:44:10 -0400 Subject: [PATCH 34/54] change type eq test in isNullSpace --- compiler/src/dotty/tools/dotc/transform/patmat/Space.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 4098b44e59e2..c95d58bc19ce 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -305,7 +305,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { /** Does the given space contain just the value `null`? */ def isNullSpace(space: Space): Boolean = space match { - case Typ(tpe, _) => tpe =:= constantNullType || tpe.isNullType + case Typ(tpe, _) => tpe.dealias == constantNullType || tpe.isNullType case Or(spaces) => spaces.forall(isNullSpace) case _ => false } From 513890d76b8f363cdf9d872f04452eaf57329755 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 15 Oct 2019 15:56:49 -0400 Subject: [PATCH 35/54] edit comment --- compiler/src/dotty/tools/dotc/core/Flags.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 47c7f3a26bf2..72ed0c3bfcf8 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -451,7 +451,8 @@ object Flags { */ val AfterLoadFlags: FlagSet = commonFlags( FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, - Enum, StableRealizable) // TODO: change to JavaEnumValue in future, blocked by possible bug in FlagSet union + // We cannot use JavaEnumValue here because it has not been initialized + Enum, StableRealizable) /** A value that's unstable unless complemented with a Stable flag */ val UnstableValueFlags: FlagSet = Mutable | Method From 6c898c77910af5b2d19e412de40e232d14b7837a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 17 Oct 2019 14:38:04 -0400 Subject: [PATCH 36/54] update comment on flags --- compiler/src/dotty/tools/dotc/core/Flags.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 72ed0c3bfcf8..b2183d759169 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -451,7 +451,10 @@ object Flags { */ val AfterLoadFlags: FlagSet = commonFlags( FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, - // We cannot use JavaEnumValue here because it has not been initialized + // We would like to add JavaEnumValue to this set so that we can correctly + // detect it in JavaNullInterop. However, JavaEnumValue is not initialized at this + // point, so we just make sure that all the "primitive" flags contained in JavaEnumValue + // are mentioned here as well. Enum, StableRealizable) /** A value that's unstable unless complemented with a Stable flag */ From 6b25d48514bb4c9782abc2262d4ddd94c948c547 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 17 Oct 2019 16:07:50 -0400 Subject: [PATCH 37/54] revert change to throwMethod; clean up if-else --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 5 ++--- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 6 ++---- compiler/src/dotty/tools/dotc/core/Types.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Namer.scala | 6 ++---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 +-- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 46a9aafe8012..289f1212b373 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -337,9 +337,8 @@ class Definitions { pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0))) /** Method representing a throw */ - @tu lazy val throwMethod: TermSymbol = { - enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType.maybeNullable), NothingType)) - } + @tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, + MethodType(List(ThrowableType), NothingType)) @tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 201af73bd28f..852520d62170 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -787,18 +787,16 @@ object SymDenotations { } /** Is this symbol a class of which `null` is a value? */ - final def isNullableClass(implicit ctx: Context): Boolean = { + final def isNullableClass(implicit ctx: Context): Boolean = if (ctx.explicitNulls && !ctx.phase.erasedTypes) symbol == defn.NullClass || symbol == defn.AnyClass else isNullableClassAfterErasure - } /** Is this symbol a class of which `null` is a value after erasure? * For example, if `-Yexplicit-nulls` is set, `String` is not nullable before erasure, * but it becomes nullable after erasure. */ - final def isNullableClassAfterErasure(implicit ctx: Context): Boolean = { + final def isNullableClassAfterErasure(implicit ctx: Context): Boolean = isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass - } /** Is this definition accessible as a member of tree with type `pre`? * @param pre The type of the tree from which the selection is made diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d1a098101912..bdd6b6f77e14 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -594,7 +594,8 @@ object Types { // We need to strip `JavaNull` from both the type and the prefix so that // `pre <: tp` continues to hold. tp.stripJavaNull.findMember(name, pre.stripJavaNull, required, excluded) - } else { + } + else { // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` // achieved that by narrowing `pre` to each alternative, but it led to merge errors in // lots of places. The present strategy is instead of widen `tp` using `join` to be a diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ef0a0bd555b1..fcd5cac267bb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1396,11 +1396,9 @@ class Namer { typer: Typer => WildcardType } val memTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) - if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) { + if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) JavaNullInterop.nullifyMember(sym, memTpe) - } else { - memTpe - } + else memTpe } /** The type signature of a DefDef with given symbol */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a7e7ecb20593..3210927ca9c4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -823,9 +823,8 @@ class Typer extends Namer val (thenCtx, elseCtx) = if (ctx.explicitNulls) { val Inferred(ifTrue, ifFalse) = FlowTyper.inferFromCond(cond1) (ctx.fresh.addFlowFacts(ifTrue), ctx.fresh.addFlowFacts(ifFalse)) - } else { - (ctx, ctx) } + else (ctx, ctx) if (tree.elsep.isEmpty) { val thenp1 = typed(tree.thenp, defn.UnitType)(thenCtx) From 6e92146fd3da25746655e707c35d08504d325ce1 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 18 Oct 2019 15:52:36 -0400 Subject: [PATCH 38/54] add notnull detect --- .../tools/dotc/core/JavaNullInterop.scala | 25 +++++++++++++++++-- .../dotty/tools/dotc/CompilationTests.scala | 5 +++- .../pos-separate/notnull/J_1.java | 18 +++++++++++++ .../pos-separate/notnull/S_2.scala | 6 +++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/explicit-nulls/pos-separate/notnull/J_1.java create mode 100644 tests/explicit-nulls/pos-separate/notnull/S_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 1393c38d9cf5..a437108ca689 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -4,7 +4,7 @@ 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.{Symbol, defn, _} -import dotty.tools.dotc.core.Types.{AndType, AppliedType, LambdaType, MethodType, OrType, PolyType, Type, TypeAlias, TypeMap, TypeParamRef, TypeRef} +import dotty.tools.dotc.core.Types.{AndType, AppliedType, LambdaType, MethodType, OrType, PolyType, ThisType, Type, TypeAlias, TypeMap, TypeParamRef, TypeRef} import NullOpsDecorator._ /** This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, @@ -60,14 +60,35 @@ object JavaNullInterop { 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) + else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNull(sym)) // Don't nullify the return type of the `toString` method and constructors + // or it has a NotNull annotation nullifyParamsOnly(tp) else // Otherwise, nullify everything nullifyType(tp) } + private val notNullAnnotations: List[String] = + "javax.annotation.Nonnull" :: + "edu.umd.cs.findbugs.annotations.NonNull" :: + "androidx.annotation.NonNull" :: + "android.support.annotation.NonNull" :: + "android.annotation.NonNull" :: + "com.android.annotations.NonNull" :: + "org.eclipse.jdt.annotation.NonNull" :: + "org.checkerframework.checker.nullness.qual.NonNull" :: + "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: + "org.jetbrains.annotations.NotNull" :: + "lombok.NonNull" :: + "io.reactivex.annotations.NonNull" :: + // mytest + "mytests.anno.MyAnno" :: Nil + + private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.annotations.exists(anno => notNullAnnotations.contains(anno.symbol.showFullName)) + } + /** Only nullify method parameters (but not result types). */ private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = new JavaNullMap(alreadyNullable = false, nonNullResultType = true)(ctx)(tp) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index f7097c7d8047..9d0b7729503c 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -263,7 +263,10 @@ class CompilationTests extends ParallelTesting { @Test def explicitNullsPos: Unit = { implicit val testGroup: TestGroup = TestGroup("explicitNullsPos") - compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions) + aggregateTests( + compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions), + compileFilesInDir("tests/explicit-nulls/pos-separate", explicitNullsOptions) + ) }.checkCompile() @Test def explicitNullsRun: Unit = { diff --git a/tests/explicit-nulls/pos-separate/notnull/J_1.java b/tests/explicit-nulls/pos-separate/notnull/J_1.java new file mode 100644 index 000000000000..24be0a1d9a88 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/J_1.java @@ -0,0 +1,18 @@ +package mytests.anno; + +public class J_1 { + + @TestNotNull() + public static String m(int i) { + return "m: " + i; + } + + @TestNotNull + public static String n(int i) { + return "n: " + i; + } +} + +@interface TestNotNull { + int value() default 1; +} diff --git a/tests/explicit-nulls/pos-separate/notnull/S_2.scala b/tests/explicit-nulls/pos-separate/notnull/S_2.scala new file mode 100644 index 000000000000..6b23ef085c41 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/S_2.scala @@ -0,0 +1,6 @@ +import mytests.anno.J_1 + +class S_2 { + def mm(i: Int): String = J_1.m(i) + def nn(i: Int): String = J_1.n(i) +} \ No newline at end of file From 9b6846e8a9dd63c6c57888d98a6b28c7fe32919b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 21 Oct 2019 15:16:29 -0400 Subject: [PATCH 39/54] fix cyclicref --- compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 4 ++++ tests/explicit-nulls/pos-separate/notnull/J_1.java | 2 +- tests/explicit-nulls/pos-separate/notnull/S_2.scala | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index a437108ca689..66f4e7e0af84 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -83,10 +83,10 @@ object JavaNullInterop { "lombok.NonNull" :: "io.reactivex.annotations.NonNull" :: // mytest - "mytests.anno.MyAnno" :: Nil + "mytests.annot.TestNotNull" :: Nil private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = { - sym.annotations.exists(anno => notNullAnnotations.contains(anno.symbol.showFullName)) + sym.unforcedAnnotations.exists(anno => notNullAnnotations.contains(anno.symbol.showFullName)) } /** Only nullify method parameters (but not result types). */ diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 852520d62170..02460768fab9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -314,6 +314,10 @@ object SymDenotations { ensureCompleted(); myAnnotations } + final def unforcedAnnotations(implicit ctx: Context): List[Annotation] = { + myAnnotations + } + /** Update the annotations of this denotation */ final def annotations_=(annots: List[Annotation]): Unit = myAnnotations = annots diff --git a/tests/explicit-nulls/pos-separate/notnull/J_1.java b/tests/explicit-nulls/pos-separate/notnull/J_1.java index 24be0a1d9a88..ae812446ee9e 100644 --- a/tests/explicit-nulls/pos-separate/notnull/J_1.java +++ b/tests/explicit-nulls/pos-separate/notnull/J_1.java @@ -1,4 +1,4 @@ -package mytests.anno; +package mytests.annot; public class J_1 { diff --git a/tests/explicit-nulls/pos-separate/notnull/S_2.scala b/tests/explicit-nulls/pos-separate/notnull/S_2.scala index 6b23ef085c41..ba8d0256a285 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_2.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_2.scala @@ -1,4 +1,4 @@ -import mytests.anno.J_1 +import mytests.annot.J_1 class S_2 { def mm(i: Int): String = J_1.m(i) From cbb876ca87cca66cafc2e502a5656438e6ac35e4 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 22 Oct 2019 14:29:53 -0400 Subject: [PATCH 40/54] change NotNull string list to ClassSymbol list --- .../dotty/tools/dotc/core/Definitions.scala | 16 ++++++++++++++ .../tools/dotc/core/JavaNullInterop.scala | 21 ++----------------- .../tools/dotc/core/SymDenotations.scala | 4 ---- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 289f1212b373..97fd7c170310 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -813,6 +813,22 @@ class Definitions { @tu lazy val InfixAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.infix") @tu lazy val AlphaAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.alpha") + @tu lazy val NotNullAnnots: List[ClassSymbol] = + ("javax.annotation.Nonnull" :: + "edu.umd.cs.findbugs.annotations.NonNull" :: + "androidx.annotation.NonNull" :: + "android.support.annotation.NonNull" :: + "android.annotation.NonNull" :: + "com.android.annotations.NonNull" :: + "org.eclipse.jdt.annotation.NonNull" :: + "org.checkerframework.checker.nullness.qual.NonNull" :: + "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: + "org.jetbrains.annotations.NotNull" :: + "lombok.NonNull" :: + "io.reactivex.annotations.NonNull" :: + // A special NotNull annotation for test + "mytests.annot.TestNotNull" :: Nil).map(s => ctx.requiredClass(PreNamedString(s))) + // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 66f4e7e0af84..1ec4b642587c 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -69,25 +69,8 @@ object JavaNullInterop { nullifyType(tp) } - private val notNullAnnotations: List[String] = - "javax.annotation.Nonnull" :: - "edu.umd.cs.findbugs.annotations.NonNull" :: - "androidx.annotation.NonNull" :: - "android.support.annotation.NonNull" :: - "android.annotation.NonNull" :: - "com.android.annotations.NonNull" :: - "org.eclipse.jdt.annotation.NonNull" :: - "org.checkerframework.checker.nullness.qual.NonNull" :: - "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: - "org.jetbrains.annotations.NotNull" :: - "lombok.NonNull" :: - "io.reactivex.annotations.NonNull" :: - // mytest - "mytests.annot.TestNotNull" :: Nil - - private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = { - sym.unforcedAnnotations.exists(anno => notNullAnnotations.contains(anno.symbol.showFullName)) - } + private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = + ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) /** Only nullify method parameters (but not result types). */ private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 02460768fab9..852520d62170 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -314,10 +314,6 @@ object SymDenotations { ensureCompleted(); myAnnotations } - final def unforcedAnnotations(implicit ctx: Context): List[Annotation] = { - myAnnotations - } - /** Update the annotations of this denotation */ final def annotations_=(annots: List[Annotation]): Unit = myAnnotations = annots From 750dd502161d42525103c2801ced01d8351f27ab Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 22 Oct 2019 15:02:49 -0400 Subject: [PATCH 41/54] ensure NotNull works for fields --- tests/explicit-nulls/pos-separate/notnull/J_1.java | 3 +++ tests/explicit-nulls/pos-separate/notnull/S_2.scala | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/explicit-nulls/pos-separate/notnull/J_1.java b/tests/explicit-nulls/pos-separate/notnull/J_1.java index ae812446ee9e..cbd0ba2721de 100644 --- a/tests/explicit-nulls/pos-separate/notnull/J_1.java +++ b/tests/explicit-nulls/pos-separate/notnull/J_1.java @@ -2,6 +2,9 @@ public class J_1 { + @TestNotNull + public static final String s = "s"; + @TestNotNull() public static String m(int i) { return "m: " + i; diff --git a/tests/explicit-nulls/pos-separate/notnull/S_2.scala b/tests/explicit-nulls/pos-separate/notnull/S_2.scala index ba8d0256a285..5fce4b66aa13 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_2.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_2.scala @@ -1,6 +1,7 @@ import mytests.annot.J_1 class S_2 { + def ss: String = J_1.s def mm(i: Int): String = J_1.m(i) def nn(i: Int): String = J_1.n(i) } \ No newline at end of file From c960acf187eaf0d43805eac4869bbfb4ec39b26d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 23 Oct 2019 16:49:19 -0400 Subject: [PATCH 42/54] Optimize JavaNullInterop; add tests for Java files; remove dummy notnull annotation --- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../tools/dotc/core/JavaNullInterop.scala | 66 ++++++++++--------- .../pos-separate/notnull/J_1.java | 21 ------ .../pos-separate/notnull/J_2.java | 38 +++++++++++ .../pos-separate/notnull/Nonnull_1.java | 8 +++ .../pos-separate/notnull/S_2.scala | 7 -- .../pos-separate/notnull/S_3.scala | 13 ++++ tests/explicit-nulls/pos/notnull/J.java | 33 ++++++++++ tests/explicit-nulls/pos/notnull/Nonnull.java | 8 +++ tests/explicit-nulls/pos/notnull/S.scala | 13 ++++ 10 files changed, 150 insertions(+), 62 deletions(-) delete mode 100644 tests/explicit-nulls/pos-separate/notnull/J_1.java create mode 100644 tests/explicit-nulls/pos-separate/notnull/J_2.java create mode 100644 tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java delete mode 100644 tests/explicit-nulls/pos-separate/notnull/S_2.scala create mode 100644 tests/explicit-nulls/pos-separate/notnull/S_3.scala create mode 100644 tests/explicit-nulls/pos/notnull/J.java create mode 100644 tests/explicit-nulls/pos/notnull/Nonnull.java create mode 100644 tests/explicit-nulls/pos/notnull/S.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b91628ceed0b..ffd980ab38a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -813,6 +813,7 @@ class Definitions { @tu lazy val InfixAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.infix") @tu lazy val AlphaAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.alpha") + // A list of NotNull annotations @tu lazy val NotNullAnnots: List[ClassSymbol] = ("javax.annotation.Nonnull" :: "edu.umd.cs.findbugs.annotations.NonNull" :: @@ -825,9 +826,7 @@ class Definitions { "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: "org.jetbrains.annotations.NotNull" :: "lombok.NonNull" :: - "io.reactivex.annotations.NonNull" :: - // A special NotNull annotation for test - "mytests.annot.TestNotNull" :: Nil).map(s => ctx.requiredClass(PreNamedString(s))) + "io.reactivex.annotations.NonNull" :: Nil).map(ctx.requiredClass(_)) // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 1ec4b642587c..4b55dffdc63d 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -61,9 +61,10 @@ object JavaNullInterop { // Don't nullify the `TYPE` field in every class and Java enum instances tp else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNull(sym)) - // Don't nullify the return type of the `toString` method and constructors - // or it has a NotNull annotation - nullifyParamsOnly(tp) + // 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) @@ -72,27 +73,32 @@ object JavaNullInterop { private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) - /** Only nullify method parameters (but not result types). */ - private def nullifyParamsOnly(tp: Type)(implicit ctx: Context): Type = - new JavaNullMap(alreadyNullable = false, nonNullResultType = true)(ctx)(tp) + /** 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(alreadyNullable = false, nonNullResultType = false)(ctx)(tp) + new JavaNullMap(false)(ctx)(tp) /** A type map that adds `| JavaNull`. - * @param alreadyNullable whether the type being mapped is already nullable (at the outermost level). - * This is needed so that `JavaNullMap(A | B)` gives back `(A | B) | JavaNull`, - * instead of `(A|JavaNull | B|JavaNull) | JavaNull`. - * @param nonNullResultType if true, then the current type is a method or generic method type, - * and we don't want to nullify its return type at the top level. - * This is useful e.g. for constructors. + * If a field has a final flag and its value is not null, then the type of it is a ConstantType, + * we don't need to nullify it. + * @param currentNullable whether the type need to be nullable at current level. + * Initialize this value to ture, so that the field types and the return types of + * methods (for example, constructors) are not nullable. + * This is also needed so that `JavaNullMap(A & B)` gives back `(A & B) | JavaNull`, + * instead of `(A|JavaNull & B|JavaNull) | JavaNull`. */ - private class JavaNullMap(var alreadyNullable: Boolean, var nonNullResultType: Boolean)(implicit ctx: Context) extends TypeMap { - /** Should we nullify `tp` at the outermost level? */ - def needsTopLevelNull(tp: Type): Boolean = - !alreadyNullable && (tp match { + private class JavaNullMap(var currentNullable: Boolean)(implicit ctx: Context) extends TypeMap { + /** Should we nullify `tp` at the current level? */ + def needsNullHere(tp: Type): Boolean = + !currentNullable && (tp match { case tp: TypeRef => // We don't modify value types because they're non-nullable even in Java. !tp.symbol.isValueClass && @@ -120,31 +126,29 @@ object JavaNullInterop { def toJavaNullableUnion(tpe: Type): Type = OrType(tpe, defn.JavaNullAliasType) tp match { - case tp: TypeRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp) + case tp: TypeRef if needsNullHere(tp) => toJavaNullableUnion(tp) case appTp @ AppliedType(tycons, targs) => - val oldAN = alreadyNullable - alreadyNullable = false + val oldCN = currentNullable + currentNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs - alreadyNullable = oldAN + currentNullable = oldCN val appTp2 = derivedAppliedType(appTp, tycons, targs2) - if (needsTopLevelNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 + if (needsNullHere(tycons)) toJavaNullableUnion(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => - val resTpe = if (nonNullResultType) { - nonNullResultType = false - this(mtp.resType).stripNull - } - else this(mtp.resType) - derivedLambdaType(mtp)(mtp.paramInfos map this, resTpe) - case tp: LambdaType => mapOver(tp) + val oldCN = currentNullable + currentNullable = false + val paramInfos2 = mtp.paramInfos map this + currentNullable = oldCN + 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`. - alreadyNullable = true + currentNullable = true toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) - case tp: TypeParamRef if needsTopLevelNull(tp) => toJavaNullableUnion(tp) + case tp: TypeParamRef if needsNullHere(tp) => toJavaNullableUnion(tp) case _ => tp } } diff --git a/tests/explicit-nulls/pos-separate/notnull/J_1.java b/tests/explicit-nulls/pos-separate/notnull/J_1.java deleted file mode 100644 index cbd0ba2721de..000000000000 --- a/tests/explicit-nulls/pos-separate/notnull/J_1.java +++ /dev/null @@ -1,21 +0,0 @@ -package mytests.annot; - -public class J_1 { - - @TestNotNull - public static final String s = "s"; - - @TestNotNull() - public static String m(int i) { - return "m: " + i; - } - - @TestNotNull - public static String n(int i) { - return "n: " + i; - } -} - -@interface TestNotNull { - int value() default 1; -} diff --git a/tests/explicit-nulls/pos-separate/notnull/J_2.java b/tests/explicit-nulls/pos-separate/notnull/J_2.java new file mode 100644 index 000000000000..04c2e1fb788d --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/J_2.java @@ -0,0 +1,38 @@ +package javax.annotation; +import java.util.*; + +public class J_2 { + + // Since the value of the constant field is not null, + // the type of the field is ConstantType("k"), which we + // don't need to nullify + public static final String k = "k"; + + @Nonnull + public static String l = "l"; + + @Nonnull + // Since the value of the constant field is null, + // the type of the field before nullifying is TypeRef(String). + // With the Nonnull annotation, the result of nullifying would + // be TypeRef(String). + public final String m = null; + + @Nonnull + public String n = "n"; + + @Nonnull + public static final String f(int i) { + return "f: " + i; + } + + @Nonnull + public static String g(int i) { + return "g: " + i; + } + + @Nonnull + public String h(int i) { + return "h: " + i; + } +} diff --git a/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java b/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java new file mode 100644 index 000000000000..ab90a642f7d9 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java @@ -0,0 +1,8 @@ +package javax.annotation; + +import java.lang.annotation.*; + +// A "fake" Nonnull Annotation for jsr305 +@Retention(value = RetentionPolicy.RUNTIME) +@interface Nonnull { +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos-separate/notnull/S_2.scala b/tests/explicit-nulls/pos-separate/notnull/S_2.scala deleted file mode 100644 index 5fce4b66aa13..000000000000 --- a/tests/explicit-nulls/pos-separate/notnull/S_2.scala +++ /dev/null @@ -1,7 +0,0 @@ -import mytests.annot.J_1 - -class S_2 { - def ss: String = J_1.s - def mm(i: Int): String = J_1.m(i) - def nn(i: Int): String = J_1.n(i) -} \ No newline at end of file diff --git a/tests/explicit-nulls/pos-separate/notnull/S_3.scala b/tests/explicit-nulls/pos-separate/notnull/S_3.scala new file mode 100644 index 000000000000..07fd3a2c7fe8 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -0,0 +1,13 @@ +// Test the NotNull annotations are working in class files' + +import javax.annotation.J_2 + +class S_3 { + def kk: String = J_2.k + def ll: String = J_2.l + def mm: String = (new J_2).m + def nn: String = (new J_2).n + def ff(i: Int): String = J_2.f(i) + def gg(i: Int): String = J_2.g(i) + def hh(i: Int): String = (new J_2).h(i) +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java new file mode 100644 index 000000000000..92e0f8fafa78 --- /dev/null +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -0,0 +1,33 @@ +package javax.annotation; +import java.util.*; + +public class J { + + @Nonnull + // JavaParser will never assign ConstantType to fields currently. + public static final String k = "k"; + + @Nonnull + public static String l = "l"; + + @Nonnull + public final String m = null; + + @Nonnull + public String n = "n"; + + @Nonnull + public static final String f(int i) { + return "f: " + i; + } + + @Nonnull + public static String g(int i) { + return "g: " + i; + } + + @Nonnull + public String h(int i) { + return "h: " + i; + } +} diff --git a/tests/explicit-nulls/pos/notnull/Nonnull.java b/tests/explicit-nulls/pos/notnull/Nonnull.java new file mode 100644 index 000000000000..ab90a642f7d9 --- /dev/null +++ b/tests/explicit-nulls/pos/notnull/Nonnull.java @@ -0,0 +1,8 @@ +package javax.annotation; + +import java.lang.annotation.*; + +// A "fake" Nonnull Annotation for jsr305 +@Retention(value = RetentionPolicy.RUNTIME) +@interface Nonnull { +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala new file mode 100644 index 000000000000..c226b3f958cd --- /dev/null +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -0,0 +1,13 @@ +// Test the NotNull annotations are working in class files' + +import javax.annotation.J + +class S_3 { + def kk: String = J.k + def ll: String = J.l + def mm: String = (new J).m + def nn: String = (new J).n + def ff(i: Int): String = J.f(i) + def gg(i: Int): String = J.g(i) + def hh(i: Int): String = (new J).h(i) +} \ No newline at end of file From b0acf924f87fdf33a93cc69aded7ec0d148a057e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 24 Oct 2019 14:35:27 -0400 Subject: [PATCH 43/54] rename variables --- .../tools/dotc/core/JavaNullInterop.scala | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 4b55dffdc63d..938db7495bf4 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -3,8 +3,8 @@ 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.{Symbol, defn, _} -import dotty.tools.dotc.core.Types.{AndType, AppliedType, LambdaType, MethodType, OrType, PolyType, ThisType, Type, TypeAlias, TypeMap, TypeParamRef, TypeRef} +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, @@ -60,7 +60,7 @@ object JavaNullInterop { 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 || hasNotNull(sym)) + 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. @@ -70,7 +70,7 @@ object JavaNullInterop { nullifyType(tp) } - private def hasNotNull(sym: Symbol)(implicit ctx: Context): Boolean = + 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, @@ -89,16 +89,16 @@ object JavaNullInterop { /** A type map that adds `| JavaNull`. * If a field has a final flag and its value is not null, then the type of it is a ConstantType, * we don't need to nullify it. - * @param currentNullable whether the type need to be nullable at current level. - * Initialize this value to ture, so that the field types and the return types of + * @param currentLevelNullable whether the type needs to be nullable at current level. + * Initialize this value to true, so that the field types and the return types of * methods (for example, constructors) are not nullable. * This is also needed so that `JavaNullMap(A & B)` gives back `(A & B) | JavaNull`, * instead of `(A|JavaNull & B|JavaNull) | JavaNull`. */ - private class JavaNullMap(var currentNullable: Boolean)(implicit ctx: Context) extends TypeMap { + private class JavaNullMap(var currentLevelNullable: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the current level? */ - def needsNullHere(tp: Type): Boolean = - !currentNullable && (tp match { + def needsNull(tp: Type): Boolean = + !currentLevelNullable && (tp match { case tp: TypeRef => // We don't modify value types because they're non-nullable even in Java. !tp.symbol.isValueClass && @@ -126,29 +126,29 @@ object JavaNullInterop { def toJavaNullableUnion(tpe: Type): Type = OrType(tpe, defn.JavaNullAliasType) tp match { - case tp: TypeRef if needsNullHere(tp) => toJavaNullableUnion(tp) + case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) case appTp @ AppliedType(tycons, targs) => - val oldCN = currentNullable - currentNullable = false + val oldCLN = currentLevelNullable + currentLevelNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs - currentNullable = oldCN + currentLevelNullable = oldCLN val appTp2 = derivedAppliedType(appTp, tycons, targs2) - if (needsNullHere(tycons)) toJavaNullableUnion(appTp2) else appTp2 + if (needsNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => - val oldCN = currentNullable - currentNullable = false + val oldCLN = currentLevelNullable + currentLevelNullable = false val paramInfos2 = mtp.paramInfos map this - currentNullable = oldCN + currentLevelNullable = oldCLN 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`. - currentNullable = true + currentLevelNullable = true toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) - case tp: TypeParamRef if needsNullHere(tp) => toJavaNullableUnion(tp) + case tp: TypeParamRef if needsNull(tp) => toJavaNullableUnion(tp) case _ => tp } } From 9365e1be7246d864bd2036bbd53cda2f86958a06 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 24 Oct 2019 14:58:57 -0400 Subject: [PATCH 44/54] add generic tests --- tests/explicit-nulls/pos-separate/notnull/J_2.java | 14 ++++++++++++++ .../explicit-nulls/pos-separate/notnull/S_3.scala | 2 ++ tests/explicit-nulls/pos/notnull/J.java | 14 ++++++++++++++ tests/explicit-nulls/pos/notnull/S.scala | 2 ++ 4 files changed, 32 insertions(+) diff --git a/tests/explicit-nulls/pos-separate/notnull/J_2.java b/tests/explicit-nulls/pos-separate/notnull/J_2.java index 04c2e1fb788d..f97b7a9bf9e9 100644 --- a/tests/explicit-nulls/pos-separate/notnull/J_2.java +++ b/tests/explicit-nulls/pos-separate/notnull/J_2.java @@ -35,4 +35,18 @@ public static String g(int i) { public String h(int i) { return "h: " + i; } + + @Nonnull + public String[] gemericf(T a) { + String[] as = new String[1]; + as[0] = "" + a; + return as; + } + + @Nonnull + public List gemericg(T a) { + List as = new ArrayList(); + as.add(a); + return as; + } } diff --git a/tests/explicit-nulls/pos-separate/notnull/S_3.scala b/tests/explicit-nulls/pos-separate/notnull/S_3.scala index 07fd3a2c7fe8..8f0f9c442309 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_3.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -10,4 +10,6 @@ class S_3 { def ff(i: Int): String = J_2.f(i) def gg(i: Int): String = J_2.g(i) def hh(i: Int): String = (new J_2).h(i) + def genericff(a: String | Null): Array[String | JavaNull] = (new J_2).gemericf(a) + def genericgg(a: String | Null): java.util.List[String] = (new J_2).gemericg(a) } \ No newline at end of file diff --git a/tests/explicit-nulls/pos/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java index 92e0f8fafa78..a719c015f797 100644 --- a/tests/explicit-nulls/pos/notnull/J.java +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -30,4 +30,18 @@ public static String g(int i) { public String h(int i) { return "h: " + i; } + + @Nonnull + public String[] gemericf(T a) { + String[] as = new String[1]; + as[0] = "" + a; + return as; + } + + @Nonnull + public List gemericg(T a) { + List as = new ArrayList(); + as.add(a); + return as; + } } diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala index c226b3f958cd..d228e6ff22fc 100644 --- a/tests/explicit-nulls/pos/notnull/S.scala +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -10,4 +10,6 @@ class S_3 { def ff(i: Int): String = J.f(i) def gg(i: Int): String = J.g(i) def hh(i: Int): String = (new J).h(i) + def genericff(a: String | Null): Array[String | JavaNull] = (new J).gemericf(a) + def genericgg(a: String | Null): java.util.List[String] = (new J).gemericg(a) } \ No newline at end of file From ca62bf950780320db321d42e536387b6a2c36774 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 25 Oct 2019 12:13:31 -0400 Subject: [PATCH 45/54] fix typos and indents --- .../src/dotty/tools/dotc/core/Definitions.scala | 4 +++- .../src/dotty/tools/dotc/core/JavaNullInterop.scala | 13 ++++++++----- tests/explicit-nulls/pos-separate/notnull/J_2.java | 4 ++-- .../pos-separate/notnull/Nonnull_1.java | 2 +- tests/explicit-nulls/pos-separate/notnull/S_3.scala | 8 ++++---- tests/explicit-nulls/pos/notnull/J.java | 4 ++-- tests/explicit-nulls/pos/notnull/Nonnull.java | 2 +- tests/explicit-nulls/pos/notnull/S.scala | 8 ++++---- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ffd980ab38a8..9d5e9f684773 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -813,7 +813,9 @@ class Definitions { @tu lazy val InfixAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.infix") @tu lazy val AlphaAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.alpha") - // A list of NotNull annotations + // A list of annotations that are commonly used to indicate that a field/method argument or return + // type is not null. These annotations are used by the nullification logic in JavaNullInterop to + // improve the precision of type nullification. @tu lazy val NotNullAnnots: List[ClassSymbol] = ("javax.annotation.Nonnull" :: "edu.umd.cs.findbugs.annotations.NonNull" :: diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 938db7495bf4..3e017f5708e5 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -77,7 +77,7 @@ object JavaNullInterop { * 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) @@ -90,10 +90,10 @@ object JavaNullInterop { * If a field has a final flag and its value is not null, then the type of it is a ConstantType, * we don't need to nullify it. * @param currentLevelNullable whether the type needs to be nullable at current level. - * Initialize this value to true, so that the field types and the return types of - * methods (for example, constructors) are not nullable. - * This is also needed so that `JavaNullMap(A & B)` gives back `(A & B) | JavaNull`, - * instead of `(A|JavaNull & B|JavaNull) | JavaNull`. + * Initialize this value to true, so that the field types and the return + * types of methods (for example, constructors) are not nullable. + * This is also needed so that `JavaNullMap(A & B)` gives back + * `(A & B) | JavaNull`, instead of `(A|JavaNull & B|JavaNull) | JavaNull`. */ private class JavaNullMap(var currentLevelNullable: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the current level? */ @@ -149,6 +149,9 @@ object JavaNullInterop { currentLevelNullable = 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 } } diff --git a/tests/explicit-nulls/pos-separate/notnull/J_2.java b/tests/explicit-nulls/pos-separate/notnull/J_2.java index f97b7a9bf9e9..b8837a41f966 100644 --- a/tests/explicit-nulls/pos-separate/notnull/J_2.java +++ b/tests/explicit-nulls/pos-separate/notnull/J_2.java @@ -37,14 +37,14 @@ public String h(int i) { } @Nonnull - public String[] gemericf(T a) { + public String[] genericf(T a) { String[] as = new String[1]; as[0] = "" + a; return as; } @Nonnull - public List gemericg(T a) { + public List genericg(T a) { List as = new ArrayList(); as.add(a); return as; diff --git a/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java b/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java index ab90a642f7d9..d30447e632cb 100644 --- a/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java +++ b/tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java @@ -5,4 +5,4 @@ // A "fake" Nonnull Annotation for jsr305 @Retention(value = RetentionPolicy.RUNTIME) @interface Nonnull { -} \ No newline at end of file +} diff --git a/tests/explicit-nulls/pos-separate/notnull/S_3.scala b/tests/explicit-nulls/pos-separate/notnull/S_3.scala index 8f0f9c442309..0b7d67765487 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_3.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -1,4 +1,4 @@ -// Test the NotNull annotations are working in class files' +// Test the NotNull annotations are working in class files import javax.annotation.J_2 @@ -10,6 +10,6 @@ class S_3 { def ff(i: Int): String = J_2.f(i) def gg(i: Int): String = J_2.g(i) def hh(i: Int): String = (new J_2).h(i) - def genericff(a: String | Null): Array[String | JavaNull] = (new J_2).gemericf(a) - def genericgg(a: String | Null): java.util.List[String] = (new J_2).gemericg(a) -} \ No newline at end of file + def genericff(a: String | Null): Array[String | JavaNull] = (new J_2).genericf(a) + def genericgg(a: String | Null): java.util.List[String] = (new J_2).genericg(a) +} diff --git a/tests/explicit-nulls/pos/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java index a719c015f797..f3ac88afd99b 100644 --- a/tests/explicit-nulls/pos/notnull/J.java +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -32,14 +32,14 @@ public String h(int i) { } @Nonnull - public String[] gemericf(T a) { + public String[] genericf(T a) { String[] as = new String[1]; as[0] = "" + a; return as; } @Nonnull - public List gemericg(T a) { + public List genericg(T a) { List as = new ArrayList(); as.add(a); return as; diff --git a/tests/explicit-nulls/pos/notnull/Nonnull.java b/tests/explicit-nulls/pos/notnull/Nonnull.java index ab90a642f7d9..d30447e632cb 100644 --- a/tests/explicit-nulls/pos/notnull/Nonnull.java +++ b/tests/explicit-nulls/pos/notnull/Nonnull.java @@ -5,4 +5,4 @@ // A "fake" Nonnull Annotation for jsr305 @Retention(value = RetentionPolicy.RUNTIME) @interface Nonnull { -} \ No newline at end of file +} diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala index d228e6ff22fc..3077be8f43d9 100644 --- a/tests/explicit-nulls/pos/notnull/S.scala +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -1,4 +1,4 @@ -// Test the NotNull annotations are working in class files' +// Test the NotNull annotations are working in Java files import javax.annotation.J @@ -10,6 +10,6 @@ class S_3 { def ff(i: Int): String = J.f(i) def gg(i: Int): String = J.g(i) def hh(i: Int): String = (new J).h(i) - def genericff(a: String | Null): Array[String | JavaNull] = (new J).gemericf(a) - def genericgg(a: String | Null): java.util.List[String] = (new J).gemericg(a) -} \ No newline at end of file + def genericff(a: String | Null): Array[String | JavaNull] = (new J).genericf(a) + def genericgg(a: String | Null): java.util.List[String] = (new J).genericg(a) +} From a49d4c3e76878a6be1ddec12c82cee40538dca5e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 25 Oct 2019 12:18:43 -0400 Subject: [PATCH 46/54] remove redundent comment --- compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 3e017f5708e5..fbc8f10b624a 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -87,8 +87,6 @@ object JavaNullInterop { /** A type map that adds `| JavaNull`. - * If a field has a final flag and its value is not null, then the type of it is a ConstantType, - * we don't need to nullify it. * @param currentLevelNullable whether the type needs to be nullable at current level. * Initialize this value to true, so that the field types and the return * types of methods (for example, constructors) are not nullable. From 8481c2ab9a171eed2c285b80af46bc49cf76153b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 25 Oct 2019 15:46:34 -0400 Subject: [PATCH 47/54] update comments --- .../tools/dotc/core/JavaNullInterop.scala | 35 ++++++++++--------- .../pos-separate/notnull/S_3.scala | 2 +- tests/explicit-nulls/pos/notnull/S.scala | 2 +- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index fbc8f10b624a..48354489053d 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -85,18 +85,21 @@ object JavaNullInterop { private def nullifyType(tp: Type)(implicit ctx: Context): Type = new JavaNullMap(false)(ctx)(tp) - - /** A type map that adds `| JavaNull`. - * @param currentLevelNullable whether the type needs to be nullable at current level. - * Initialize this value to true, so that the field types and the return - * types of methods (for example, constructors) are not nullable. - * This is also needed so that `JavaNullMap(A & B)` gives back - * `(A & B) | JavaNull`, instead of `(A|JavaNull & B|JavaNull) | JavaNull`. + /** 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 currentLevelNullable: Boolean)(implicit ctx: Context) extends TypeMap { + private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { /** Should we nullify `tp` at the current level? */ def needsNull(tp: Type): Boolean = - !currentLevelNullable && (tp match { + !outermostLevelAlreadyNullable && (tp match { case tp: TypeRef => // We don't modify value types because they're non-nullable even in Java. !tp.symbol.isValueClass && @@ -126,25 +129,25 @@ object JavaNullInterop { tp match { case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) case appTp @ AppliedType(tycons, targs) => - val oldCLN = currentLevelNullable - currentLevelNullable = false + val oldCLN = outermostLevelAlreadyNullable + outermostLevelAlreadyNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs - currentLevelNullable = oldCLN + outermostLevelAlreadyNullable = oldCLN val appTp2 = derivedAppliedType(appTp, tycons, targs2) if (needsNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => - val oldCLN = currentLevelNullable - currentLevelNullable = false + val oldCLN = outermostLevelAlreadyNullable + outermostLevelAlreadyNullable = false val paramInfos2 = mtp.paramInfos map this - currentLevelNullable = oldCLN + outermostLevelAlreadyNullable = oldCLN 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`. - currentLevelNullable = true + 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. diff --git a/tests/explicit-nulls/pos-separate/notnull/S_3.scala b/tests/explicit-nulls/pos-separate/notnull/S_3.scala index 0b7d67765487..96705e36d185 100644 --- a/tests/explicit-nulls/pos-separate/notnull/S_3.scala +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -1,4 +1,4 @@ -// Test the NotNull annotations are working in class files +// Test that NotNull annotations are working in class files. import javax.annotation.J_2 diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala index 3077be8f43d9..5e99c45c8547 100644 --- a/tests/explicit-nulls/pos/notnull/S.scala +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -1,4 +1,4 @@ -// Test the NotNull annotations are working in Java files +// Test that NotNull annotations are working in Java files. import javax.annotation.J From ae4253aefe0f42182c8b884b99deafa3211bcce1 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 28 Oct 2019 11:50:43 -0400 Subject: [PATCH 48/54] update comments and var names --- .../src/dotty/tools/dotc/core/JavaNullInterop.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 48354489053d..b40bb37123fd 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -97,7 +97,7 @@ object JavaNullInterop { * 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 current level? */ + /** Should we nullify `tp` at the outermost level? */ def needsNull(tp: Type): Boolean = !outermostLevelAlreadyNullable && (tp match { case tp: TypeRef => @@ -129,19 +129,19 @@ object JavaNullInterop { tp match { case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) case appTp @ AppliedType(tycons, targs) => - val oldCLN = outermostLevelAlreadyNullable + val oldOutermostNullable = outermostLevelAlreadyNullable outermostLevelAlreadyNullable = false val targs2 = if (needsNullArgs(appTp)) targs map this else targs - outermostLevelAlreadyNullable = oldCLN + outermostLevelAlreadyNullable = oldOutermostNullable val appTp2 = derivedAppliedType(appTp, tycons, targs2) if (needsNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => - val oldCLN = outermostLevelAlreadyNullable + val oldOutermostNullable = outermostLevelAlreadyNullable outermostLevelAlreadyNullable = false val paramInfos2 = mtp.paramInfos map this - outermostLevelAlreadyNullable = oldCLN + outermostLevelAlreadyNullable = oldOutermostNullable derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) case tp: TypeAlias => mapOver(tp) case tp: AndType => From 3a1d17c8533519710e1de615fb8e0f4b5e82fa08 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 31 Oct 2019 14:34:30 -0400 Subject: [PATCH 49/54] fix nullifying type arguements --- .../tools/dotc/core/JavaNullInterop.scala | 20 ++++++++----------- .../pos/interop-poly-src/J.java | 15 ++++++++++++++ .../pos/interop-poly-src/S.scala | 6 ++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index b40bb37123fd..5137fe213828 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -114,13 +114,6 @@ object JavaNullInterop { case _ => true }) - /** Should we nullify the type arguments to the given generic `tp`? - * We only nullify the inside of Scala-defined generics. - * This is because Java classes are _all_ nullified, so both `java.util.List[String]` and - * `java.util.List[String|Null]` contain nullable elements. - */ - def needsNullArgs(tp: AppliedType): Boolean = !tp.classSymbol.is(JavaDefined) - override def apply(tp: Type): Type = { // Fast version of Type::toJavaNullableUnion that doesn't check whether the type // is already a union. @@ -128,13 +121,16 @@ object JavaNullInterop { tp match { case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) - case appTp @ AppliedType(tycons, targs) => + case appTp @ AppliedType(tycon, targs) => val oldOutermostNullable = outermostLevelAlreadyNullable - outermostLevelAlreadyNullable = false - val targs2 = if (needsNullArgs(appTp)) targs map this else targs + // 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, tycons, targs2) - if (needsNull(tycons)) toJavaNullableUnion(appTp2) else appTp2 + 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 => diff --git a/tests/explicit-nulls/pos/interop-poly-src/J.java b/tests/explicit-nulls/pos/interop-poly-src/J.java index 03e146942f58..c9c27758d699 100644 --- a/tests/explicit-nulls/pos/interop-poly-src/J.java +++ b/tests/explicit-nulls/pos/interop-poly-src/J.java @@ -1,3 +1,4 @@ +import java.util.*; class JavaCat { T prop; @@ -11,4 +12,18 @@ static ScalaCat getScalaCat() { static JavaCat getJavaCat() { return null; } + + static List getListOfStringArray() { + List as = new ArrayList(); + as.add(new String[1]); + return as; + } + + static List[] getArrayOfStringList() { + return (List[]) new List[1]; + } + + static List[]> getComplexStrings() { + return new ArrayList[]>(); + } } diff --git a/tests/explicit-nulls/pos/interop-poly-src/S.scala b/tests/explicit-nulls/pos/interop-poly-src/S.scala index 868ddce0cc98..e21b9430f72b 100644 --- a/tests/explicit-nulls/pos/interop-poly-src/S.scala +++ b/tests/explicit-nulls/pos/interop-poly-src/S.scala @@ -11,4 +11,10 @@ class Test { val jc: JavaCat[String]|Null = J.getJavaCat[String]() // ScalaCat is Java-defined, so we need the inner |Null. val sc: ScalaCat[String|Null]|Null = J.getScalaCat[String]() + + import java.util.List + + val las: List[Array[String|Null]]|Null = J.getListOfStringArray() + val als: Array[List[String]|Null]|Null = J.getArrayOfStringList() + val css: List[Array[List[Array[String|Null]]|Null]]|Null = J.getComplexStrings() } From 303d2de2baffe9867ab315e338648bea7f51e3ff Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 31 Oct 2019 14:36:56 -0400 Subject: [PATCH 50/54] fix indent --- .../pos/interop-poly-src/J.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/explicit-nulls/pos/interop-poly-src/J.java b/tests/explicit-nulls/pos/interop-poly-src/J.java index c9c27758d699..a0d5c109605e 100644 --- a/tests/explicit-nulls/pos/interop-poly-src/J.java +++ b/tests/explicit-nulls/pos/interop-poly-src/J.java @@ -1,29 +1,29 @@ import java.util.*; class JavaCat { - T prop; + T prop; } class J { - static ScalaCat getScalaCat() { - return null; - } + static ScalaCat getScalaCat() { + return null; + } - static JavaCat getJavaCat() { - return null; - } + static JavaCat getJavaCat() { + return null; + } - static List getListOfStringArray() { + static List getListOfStringArray() { List as = new ArrayList(); as.add(new String[1]); return as; - } + } - static List[] getArrayOfStringList() { + static List[] getArrayOfStringList() { return (List[]) new List[1]; - } + } - static List[]> getComplexStrings() { + static List[]> getComplexStrings() { return new ArrayList[]>(); - } + } } From 9d77d648206a8f62a0e7913d917003de32b96c40 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 4 Nov 2019 13:32:07 -0500 Subject: [PATCH 51/54] fix typo in the test --- tests/explicit-nulls/pos/interop-poly-src/S.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/explicit-nulls/pos/interop-poly-src/S.scala b/tests/explicit-nulls/pos/interop-poly-src/S.scala index e21b9430f72b..1fea277efe90 100644 --- a/tests/explicit-nulls/pos/interop-poly-src/S.scala +++ b/tests/explicit-nulls/pos/interop-poly-src/S.scala @@ -9,7 +9,7 @@ class Test { // because JavaCat, being a Java class, _already_ nullifies its // fields. val jc: JavaCat[String]|Null = J.getJavaCat[String]() - // ScalaCat is Java-defined, so we need the inner |Null. + // ScalaCat is Scala-defined, so we need the inner |Null. val sc: ScalaCat[String|Null]|Null = J.getScalaCat[String]() import java.util.List From 8c195dc91670a20a7a4a811601de160d1679c79b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 4 Nov 2019 14:28:19 -0500 Subject: [PATCH 52/54] fix syntax error --- library/src/dotty/DottyPredef.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 53ff14946ea3..855b2dcae03b 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -48,7 +48,7 @@ object DottyPredef { * * Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE. */ - def (x: T|Null) nn[T]: T = + def[T] (x: T|Null) nn: T = if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") else x.asInstanceOf[T] From 856ba31e3e20bc04c5c672e558e576f97f809f95 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 6 Nov 2019 16:08:29 -0500 Subject: [PATCH 53/54] change the way of creating ClassSymbol; add neg test for annotation --- .../dotty/tools/dotc/core/Definitions.scala | 26 +++++++++---------- .../src/dotty/tools/dotc/core/Symbols.scala | 6 +++++ tests/explicit-nulls/neg/notnull/J.java | 13 ++++++++++ tests/explicit-nulls/neg/notnull/NotNull.java | 8 ++++++ tests/explicit-nulls/neg/notnull/S.scala | 8 ++++++ tests/explicit-nulls/pos/notnull/J.java | 1 + 6 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 tests/explicit-nulls/neg/notnull/J.java create mode 100644 tests/explicit-nulls/neg/notnull/NotNull.java create mode 100644 tests/explicit-nulls/neg/notnull/S.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 042ef71140c2..40383e0150d0 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -816,19 +816,19 @@ class Definitions { // A list of annotations that are commonly used to indicate that a field/method argument or return // type is not null. These annotations are used by the nullification logic in JavaNullInterop to // improve the precision of type nullification. - @tu lazy val NotNullAnnots: List[ClassSymbol] = - ("javax.annotation.Nonnull" :: - "edu.umd.cs.findbugs.annotations.NonNull" :: - "androidx.annotation.NonNull" :: - "android.support.annotation.NonNull" :: - "android.annotation.NonNull" :: - "com.android.annotations.NonNull" :: - "org.eclipse.jdt.annotation.NonNull" :: - "org.checkerframework.checker.nullness.qual.NonNull" :: - "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: - "org.jetbrains.annotations.NotNull" :: - "lombok.NonNull" :: - "io.reactivex.annotations.NonNull" :: Nil).map(ctx.requiredClass(_)) + @tu lazy val NotNullAnnots: List[ClassSymbol] = ctx.getClassesIfDefined( + "javax.annotation.Nonnull" :: + "edu.umd.cs.findbugs.annotations.NonNull" :: + "androidx.annotation.NonNull" :: + "android.support.annotation.NonNull" :: + "android.annotation.NonNull" :: + "com.android.annotations.NonNull" :: + "org.eclipse.jdt.annotation.NonNull" :: + "org.checkerframework.checker.nullness.qual.NonNull" :: + "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: + "org.jetbrains.annotations.NotNull" :: + "lombok.NonNull" :: + "io.reactivex.annotations.NonNull" :: Nil map PreNamedString) // convenient one-parameter method types def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 3ee8751a1f19..9429c2111ee1 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -383,6 +383,12 @@ trait Symbols { this: Context => .requiredSymbol("class", name, generateStubs = false)(_.isClass) } + def getClassesIfDefined(pathes: List[PreName]): List[ClassSymbol] = + pathes.foldLeft(List.empty){ case (acc, path) => getClassIfDefined(path) match { + case cls: ClassSymbol => cls :: acc + case _ => acc + }} + /** Get ClassSymbol if package is either defined in current compilation run * or present on classpath. * Returns NoSymbol otherwise. */ diff --git a/tests/explicit-nulls/neg/notnull/J.java b/tests/explicit-nulls/neg/notnull/J.java new file mode 100644 index 000000000000..36b09fb310f1 --- /dev/null +++ b/tests/explicit-nulls/neg/notnull/J.java @@ -0,0 +1,13 @@ +import java.util.*; +import notnull.NotNull; + +public class J { + + @NotNull + // TODO: remove annotaion after #7483 + // JavaParser will never assign ConstantType to fields currently. + public static final String k = "k"; + + @NotNull + public static String l = "l"; +} diff --git a/tests/explicit-nulls/neg/notnull/NotNull.java b/tests/explicit-nulls/neg/notnull/NotNull.java new file mode 100644 index 000000000000..79c36de8504c --- /dev/null +++ b/tests/explicit-nulls/neg/notnull/NotNull.java @@ -0,0 +1,8 @@ +package notnull; + +import java.lang.annotation.*; + +// A NotNull Annotation not in the list +@Retention(value = RetentionPolicy.RUNTIME) +public @interface NotNull { +} diff --git a/tests/explicit-nulls/neg/notnull/S.scala b/tests/explicit-nulls/neg/notnull/S.scala new file mode 100644 index 000000000000..0d1ddbd31d71 --- /dev/null +++ b/tests/explicit-nulls/neg/notnull/S.scala @@ -0,0 +1,8 @@ +// Test that NotNull annotations not in the list are not working in Java files. + +class S { + // TODO: this will not be an error after #7483 + def kk: String = J.k // error: k is not a constant and the NotNull annotation is not in the list + + def ll: String = J.l // error: the NotNull annotation is not in the list +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java index f3ac88afd99b..978ea176bf47 100644 --- a/tests/explicit-nulls/pos/notnull/J.java +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -4,6 +4,7 @@ public class J { @Nonnull + // TODO: remove annotaion after #7483 // JavaParser will never assign ConstantType to fields currently. public static final String k = "k"; From 4cd11140463fd36c843691a8590ec20460ecf982 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 7 Nov 2019 14:47:28 -0500 Subject: [PATCH 54/54] update comments --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Symbols.scala | 3 +++ tests/explicit-nulls/neg/notnull/J.java | 8 +++++--- tests/explicit-nulls/neg/notnull/S.scala | 3 +-- tests/explicit-nulls/pos/notnull/J.java | 8 +++++--- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 40383e0150d0..6e405c186ca6 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -816,6 +816,8 @@ class Definitions { // A list of annotations that are commonly used to indicate that a field/method argument or return // type is not null. These annotations are used by the nullification logic in JavaNullInterop to // improve the precision of type nullification. + // We don't require that any of these annotations be present in the class path, but we want to + // create Symbols for the ones that are present, so they can be checked during nullification. @tu lazy val NotNullAnnots: List[ClassSymbol] = ctx.getClassesIfDefined( "javax.annotation.Nonnull" :: "edu.umd.cs.findbugs.annotations.NonNull" :: diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 9429c2111ee1..ebecb2980a2c 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -383,6 +383,9 @@ trait Symbols { this: Context => .requiredSymbol("class", name, generateStubs = false)(_.isClass) } + /** Get a List of ClassSymbols which are either defined in current compilation + * run or present on classpath. + */ def getClassesIfDefined(pathes: List[PreName]): List[ClassSymbol] = pathes.foldLeft(List.empty){ case (acc, path) => getClassIfDefined(path) match { case cls: ClassSymbol => cls :: acc diff --git a/tests/explicit-nulls/neg/notnull/J.java b/tests/explicit-nulls/neg/notnull/J.java index 36b09fb310f1..6230e44eb828 100644 --- a/tests/explicit-nulls/neg/notnull/J.java +++ b/tests/explicit-nulls/neg/notnull/J.java @@ -3,10 +3,12 @@ public class J { + private static String getK() { + return "k"; + } + @NotNull - // TODO: remove annotaion after #7483 - // JavaParser will never assign ConstantType to fields currently. - public static final String k = "k"; + public static final String k = getK(); @NotNull public static String l = "l"; diff --git a/tests/explicit-nulls/neg/notnull/S.scala b/tests/explicit-nulls/neg/notnull/S.scala index 0d1ddbd31d71..eada60eea6e7 100644 --- a/tests/explicit-nulls/neg/notnull/S.scala +++ b/tests/explicit-nulls/neg/notnull/S.scala @@ -1,8 +1,7 @@ // Test that NotNull annotations not in the list are not working in Java files. class S { - // TODO: this will not be an error after #7483 - def kk: String = J.k // error: k is not a constant and the NotNull annotation is not in the list + def kk: String = J.k // error: k doesn't have a constant type and the NotNull annotation is not in the list def ll: String = J.l // error: the NotNull annotation is not in the list } \ No newline at end of file diff --git a/tests/explicit-nulls/pos/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java index 978ea176bf47..58351827b862 100644 --- a/tests/explicit-nulls/pos/notnull/J.java +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -3,10 +3,12 @@ public class J { + private static String getK() { + return "k"; + } + @Nonnull - // TODO: remove annotaion after #7483 - // JavaParser will never assign ConstantType to fields currently. - public static final String k = "k"; + public static final String k = getK(); @Nonnull public static String l = "l";