From bc972b2f532449943702b9b6bfccba08a993e754 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 8 Nov 2019 15:46:15 -0500 Subject: [PATCH 01/39] remove flow typing from main branch --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/core/Contexts.scala | 3 + .../dotty/tools/dotc/core/Definitions.scala | 103 ++++- .../src/dotty/tools/dotc/core/Flags.scala | 8 +- .../tools/dotc/core/JavaNullInterop.scala | 156 +++++++ .../tools/dotc/core/NullOpsDecorator.scala | 144 +++++++ .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 10 +- .../src/dotty/tools/dotc/core/Symbols.scala | 9 + .../dotty/tools/dotc/core/TypeComparer.scala | 14 +- .../dotty/tools/dotc/core/TypeErasure.scala | 4 +- .../src/dotty/tools/dotc/core/Types.scala | 185 ++++++--- .../dotc/core/classfile/ClassfileParser.scala | 2 + .../transform/CollectNullableFields.scala | 6 +- .../tools/dotc/transform/FirstTransform.scala | 22 +- .../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 | 52 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 5 +- .../dotty/tools/dotc/CompilationTests.scala | 22 + .../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 library/src/dotty/DottyPredef.scala | 28 ++ 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/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 + tests/explicit-nulls/neg/notnull/J.java | 15 + tests/explicit-nulls/neg/notnull/NotNull.java | 8 + tests/explicit-nulls/neg/notnull/S.scala | 7 + .../explicit-nulls/neg/null-subtype-any.scala | 17 + .../neg/override-java-object-arg.scala | 26 ++ .../neg/override-java-object-arg2.scala | 13 + tests/explicit-nulls/neg/throw-null.scala | 14 + tests/explicit-nulls/neg/type-arg.scala | 13 + .../pos-separate/notnull/J_2.java | 52 +++ .../pos-separate/notnull/Nonnull_1.java | 8 + .../pos-separate/notnull/S_3.scala | 15 + 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 + .../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 | 29 ++ .../pos/interop-poly-src/S.scala | 20 + .../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 + .../pos/java-varargs-src/Names.java | 4 + .../pos/java-varargs-src/S.scala | 19 + tests/explicit-nulls/pos/java-varargs.scala | 22 + tests/explicit-nulls/pos/nn.scala | 20 + tests/explicit-nulls/pos/nn2.scala | 10 + tests/explicit-nulls/pos/notnull/J.java | 50 +++ tests/explicit-nulls/pos/notnull/Nonnull.java | 8 + tests/explicit-nulls/pos/notnull/S.scala | 15 + tests/explicit-nulls/pos/nullable-union.scala | 14 + .../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 ++ .../explicit-nulls/pos/pattern-matching.scala | 38 ++ tests/explicit-nulls/pos/ref-eq.scala | 31 ++ .../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 + 105 files changed, 2396 insertions(+), 101 deletions(-) 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 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/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/notnull/J.java create mode 100644 tests/explicit-nulls/neg/notnull/NotNull.java create mode 100644 tests/explicit-nulls/neg/notnull/S.scala create mode 100644 tests/explicit-nulls/neg/null-subtype-any.scala 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/neg/throw-null.scala create mode 100644 tests/explicit-nulls/neg/type-arg.scala create mode 100644 tests/explicit-nulls/pos-separate/notnull/J_2.java create mode 100644 tests/explicit-nulls/pos-separate/notnull/Nonnull_1.java create mode 100644 tests/explicit-nulls/pos-separate/notnull/S_3.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/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/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 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/notnull/J.java create mode 100644 tests/explicit-nulls/pos/notnull/Nonnull.java create mode 100644 tests/explicit-nulls/pos/notnull/S.scala create mode 100644 tests/explicit-nulls/pos/nullable-union.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 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/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 8b053263ed00..d78f1cbd8b3d 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -162,6 +162,7 @@ class ScalaSettings extends Settings.SettingGroup { // Extremely experimental language features val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Enable kind polymorphism (see https://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 bbcfe509fd21..6023fb9b0f49 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -421,6 +421,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 diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index df5f6194d8ed..4a6f004ebebd 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 @@ -295,8 +295,16 @@ 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. + 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. + 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) @tu lazy val Object_clone: TermSymbol = enterMethod(ObjectClass, nme.clone_, MethodType(Nil, ObjectType), Protected) @@ -336,11 +344,29 @@ class Definitions { 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 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") + /** 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 = JavaNullAlias.typeRef + @tu lazy val ImplicitScrutineeTypeSym = newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef @@ -508,12 +534,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 @@ -783,10 +813,29 @@ 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 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" :: + "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) 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(ObjectType.maybeNullable), tp) // Derived types @@ -947,8 +996,16 @@ class Definitions { name.drop(prefix.length).forall(_.isDigit)) def isBottomClass(cls: Symbol): Boolean = - cls == NothingClass || cls == NullClass + 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 = + 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. @@ -1292,18 +1349,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 6dcd33a15df5..4b1e30157005 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -450,8 +450,12 @@ object Flags { * is completed) */ val AfterLoadFlags: FlagSet = commonFlags( - FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined) - + FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, + // 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 */ val UnstableValueFlags: FlagSet = Mutable | Method 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..5137fe213828 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -0,0 +1,156 @@ +package dotty.tools.dotc.core + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags.JavaDefined +import dotty.tools.dotc.core.StdNames.{jnme, nme} +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import NullOpsDecorator._ + +/** This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, + * as Scala types, which are explicitly nullable. + * + * The transformation is (conceptually) a function `n` that adheres to the following rules: + * (1) n(T) = T|JavaNull if T is a reference type + * (2) n(T) = T if T is a value type + * (3) n(C[T]) = C[T]|JavaNull if C is Java-defined + * (4) n(C[T]) = C[n(T)]|JavaNull if C is Scala-defined + * (5) n(A|B) = n(A)|n(B)|JavaNull + * (6) n(A&B) = n(A) & n(B) + * (7) n((A1, ..., Am)R) = (n(A1), ..., n(Am))n(R) for a method with arguments (A1, ..., Am) and return type R + * (8) n(T) = T otherwise + * + * Treatment of generics (rules 3 and 4): + * - if `C` is Java-defined, then `n(C[T]) = C[T]|JavaNull`. That is, we don't recurse + * on the type argument, and only add JavaNull on the outside. This is because + * `C` itself will be nullified, and in particular so will be usages of `C`'s type argument within C's body. + * e.g. calling `get` on a `java.util.List[String]` already returns `String|Null` and not `String`, so + * we don't need to write `java.util.List[String|Null]`. + * - if `C` is Scala-defined, however, then we want `n(C[T]) = C[n(T)]|JavaNull`. This is because + * `C` won't be nullified, so we need to indicate that its type argument is nullable. + * + * Notice that since the transformation is only applied to types attached to Java symbols, it doesn't need + * to handle the full spectrum of Scala types. Additionally, some kinds of symbols like constructors and + * enum instances get special treatment. + */ +object JavaNullInterop { + + /** Transforms the type `tp` of Java member `sym` to be explicitly nullable. + * `tp` is needed because the type inside `sym` might not be set when this method is called. + * + * e.g. given a Java method + * String foo(String arg) { return arg; } + * + * After calling `nullifyMember`, Scala will see the method as + * + * def foo(arg: String|JavaNull): String|JavaNull + * + * This nullability function uses `JavaNull` instead of vanilla `Null`, for usability. + * This means that we can select on the return of `foo`: + * + * val len = foo("hello").length + * + * But the selection can throw an NPE if the returned value is `null`. + */ + def nullifyMember(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + assert(sym.is(JavaDefined), "can only nullify java-defined members") + + // Some special cases when nullifying the type + if (sym.name == nme.TYPE_ || sym.isAllOf(Flags.JavaEnumValue)) + // Don't nullify the `TYPE` field in every class and Java enum instances + tp + else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym)) + // Don't nullify the return type of the `toString` method. + // Don't nullify the return type of constructors. + // Don't nullify the return type of methods with a not-null annotation. + nullifyExceptReturnType(tp) + else + // Otherwise, nullify everything + nullifyType(tp) + } + + private def hasNotNullAnnot(sym: Symbol)(implicit ctx: Context): Boolean = + ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) + + /** If tp is a MethodType, the parameters and the inside of return type are nullified, + * but the result return type is not nullable. + * If tp is a type of a field, the inside of the type is nullified, + * but the result type is not nullable. + */ + private def nullifyExceptReturnType(tp: Type)(implicit ctx: Context): Type = + new JavaNullMap(true)(ctx)(tp) + + /** Nullifies a Java type by adding `| JavaNull` in the relevant places. */ + private def nullifyType(tp: Type)(implicit ctx: Context): Type = + new JavaNullMap(false)(ctx)(tp) + + /** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| JavaNull` + * in the right places to make the nulls explicit in Scala. + * + * @param outermostLevelAlreadyNullable whether this type is already nullable at the outermost level. + * For example, `Array[String]|JavaNull` is already nullable at the + * outermost level, but `Array[String|JavaNull]` isn't. + * If this parameter is set to true, then the types of fields, and the return + * types of methods will not be nullified. + * This is useful for e.g. constructors, and also so that `A & B` is nullified + * to `(A & B) | JavaNull`, instead of `(A|JavaNull & B|JavaNull) | JavaNull`. + */ + private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(implicit ctx: Context) extends TypeMap { + /** Should we nullify `tp` at the outermost level? */ + def needsNull(tp: Type): Boolean = + !outermostLevelAlreadyNullable && (tp match { + case tp: TypeRef => + // We don't modify value types because they're non-nullable even in Java. + !tp.symbol.isValueClass && + // We don't modify `Any` because it's already nullable. + !tp.isRef(defn.AnyClass) && + // We don't nullify Java varargs at the top level. + // Example: if `setNames` is a Java method with signature `void setNames(String... names)`, + // then its Scala signature will be `def setNames(names: (String|JavaNull)*): Unit`. + // This is because `setNames(null)` passes as argument a single-element array containing the value `null`, + // and not a `null` array. + !tp.isRef(defn.RepeatedParamClass) + case _ => true + }) + + override def apply(tp: Type): Type = { + // Fast version of Type::toJavaNullableUnion that doesn't check whether the type + // is already a union. + def toJavaNullableUnion(tpe: Type): Type = OrType(tpe, defn.JavaNullAliasType) + + tp match { + case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) + case appTp @ AppliedType(tycon, targs) => + val oldOutermostNullable = outermostLevelAlreadyNullable + // We don't make the outmost levels of type arguements nullable if tycon is Java-defined. + // This is because Java classes are _all_ nullified, so both `java.util.List[String]` and + // `java.util.List[String|Null]` contain nullable elements. + outermostLevelAlreadyNullable = tp.classSymbol.is(JavaDefined) + val targs2 = targs map this + outermostLevelAlreadyNullable = oldOutermostNullable + val appTp2 = derivedAppliedType(appTp, tycon, targs2) + if (needsNull(tycon)) toJavaNullableUnion(appTp2) else appTp2 + case ptp: PolyType => + derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) + case mtp: MethodType => + val oldOutermostNullable = outermostLevelAlreadyNullable + outermostLevelAlreadyNullable = false + val paramInfos2 = mtp.paramInfos map this + outermostLevelAlreadyNullable = oldOutermostNullable + derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) + case tp: TypeAlias => mapOver(tp) + case tp: AndType => + // nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add + // duplicate `JavaNull`s at the outermost level inside `A` and `B`. + outermostLevelAlreadyNullable = true + toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) + case tp: TypeParamRef if needsNull(tp) => toJavaNullableUnion(tp) + // In all other cases, return the type unchanged. + // In particular, if the type is a ConstantType, then we don't nullify it because it is the + // type of a final non-nullable field. + case _ => tp + } + } + } +} 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..7b6e55efe47c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -0,0 +1,144 @@ +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 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 | 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 + var hasNull = false + var hasJavaNull = false + def strip(tp: Type): Type = tp match { + 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 tp.derivedOrType(llhs, rrhs) + case _ => + if (tp.isNullType) { + if (tp.isJavaNullType) hasJavaNull = true + else hasNull = true + } + tp + } + val tp = strip(self) + if (!isUnion) self + 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`? */ + def isNullableUnion(implicit ctx: Context): Boolean = { + assert(ctx.explicitNulls) + 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) + self.widenDealias.normNullableUnion match { + case OrType(_, rhs) => rhs.isJavaNullType + case _ => false + } + } + + /** 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 + } + + def maybeNullable(implicit ctx: Context): Type = + if (ctx.explicitNulls) OrType(self, defn.NullType) else self + + /** Syntactically strips the nullability from this type. + * If the normalized form (as per `normNullableUnion`) of this type is `T1 | ... | Tn-1 | Tn`, + * 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 + case _ => self + } + } + + /** Like `stripNull`, but removes only the `JavaNull`s. */ + def stripJavaNull(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + 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). + * 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.normNullableUnion match { + case OrType(lhs, rhs) if rhs.isJavaNullType => + diff = true + mapOver(lhs) + 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 b4983df55b0b..56d5cabf9c47 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 50b940ec9211..383b49248950 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -786,8 +786,16 @@ 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? */ + /** 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`? diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 3ee8751a1f19..ebecb2980a2c 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -383,6 +383,15 @@ 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 + case _ => acc + }} + /** Get ClassSymbol if package is either defined in current compilation run * or present on classpath. * Returns NoSymbol otherwise. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index a881b361d553..edc1b8765ff0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -18,6 +18,7 @@ import scala.util.control.NonFatal import typer.ProtoTypes.constrained import typer.Applications.productSelectorTypes import reporting.trace +import NullOpsDecorator.NullOps final class AbsentContext object AbsentContext { @@ -1606,9 +1607,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/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 700fe6134940..394e9a58fe5e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -289,8 +289,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 fb0f712a3e8f..ac1349c95d91 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] @@ -598,11 +588,20 @@ 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 => @@ -1066,21 +1065,60 @@ 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 @ 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 + def widenUnion(implicit ctx: Context): Type = { + widen match { + 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 + } + + // 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. + // 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 { + val lhsWiden = lhs.widenUnion + 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 + } + } + else defaultJoin(lhs.widenUnion, rhs.widenUnion) + 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 @@ -1886,18 +1924,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) = { - 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 - } + private def computeDenot(implicit ctx: Context): Denotation = { def fromDesignator = designator match { case name: Name => @@ -2197,7 +2235,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) newLikeThis(prefix, sym).asInstanceOf[ThisType] else this /** A reference like this one, but with the given denotation, if it exists. @@ -2244,10 +2282,10 @@ object Types { d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod else lastSymbol.asSeenFrom(prefix).signature) - NamedType(prefix, name, d) + newLikeThis(prefix, name, d) } if (prefix eq this.prefix) this - else if (lastDenotation == null) NamedType(prefix, designator) + else if (lastDenotation == null) newLikeThis(prefix, designator) else designator match { case sym: Symbol => if (infoDependsOnPrefix(sym, prefix) && !prefix.isArgPrefixOf(sym)) { @@ -2256,10 +2294,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) newLikeThis(prefix, sym.name, denot.asSeenFrom(prefix)) else candidate } - else NamedType(prefix, sym) + else newLikeThis(prefix, sym) case name: Name => reload() } } @@ -2282,6 +2320,12 @@ object Types { } override def eql(that: Type): Boolean = this eq that // safe because named types are hash-consed separately + + protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType = + NamedType(prefix, designator) + + 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 @@ -2298,7 +2342,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 @@ -2353,6 +2397,34 @@ 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 + + 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) { assert((prefix ne NoPrefix) || designator.isInstanceOf[Symbol]) myHash = hc @@ -2384,9 +2456,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) @@ -2405,6 +2479,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 df28f90dd188..59c49dabb5d3 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 28cf0924a017..0fd3bdde12d3 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 18a31b12cbf2..465c44d45388 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,25 @@ 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. + 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 c41c6e7729c1..00509e12e865 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 6930539ec990..762076ed6e9f 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 928d965025e6..2bcf4088f1e4 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 { @@ -187,7 +188,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 b1fdf12aaf50..6120ed356805 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.dealias == 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.provablyDisjoint(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 } @@ -763,10 +782,10 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (!redundancyCheckable(sel)) return val targetSpace = - if (selTyp.classSymbol.isPrimitiveValueClass) + if (ctx.explicitNulls || selTyp.classSymbol.isPrimitiveValueClass) Typ(selTyp, true) else - Or(Typ(selTyp, true) :: nullSpace :: Nil) + 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 +794,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 +810,18 @@ 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) ) { + // 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) ) { simplify(minus(covered, prevs)) match { - case Typ(`nullType`, _) => + case Typ(`constantNullType`, _) => ctx.warning(MatchCaseOnlyNullWarning(), pat.sourcePos) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 3d8583b78c6e..e32360e299f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1418,7 +1418,10 @@ 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/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 78915d3ce2f8..37ca6360152d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -254,6 +254,28 @@ 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") + aggregateTests( + compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions), + compileFilesInDir("tests/explicit-nulls/pos-separate", 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/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 529266cb3922..855b2dcae03b 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[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] + + /** 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/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/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/notnull/J.java b/tests/explicit-nulls/neg/notnull/J.java new file mode 100644 index 000000000000..6230e44eb828 --- /dev/null +++ b/tests/explicit-nulls/neg/notnull/J.java @@ -0,0 +1,15 @@ +import java.util.*; +import notnull.NotNull; + +public class J { + + private static String getK() { + return "k"; + } + + @NotNull + public static final String k = getK(); + + @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..eada60eea6e7 --- /dev/null +++ b/tests/explicit-nulls/neg/notnull/S.scala @@ -0,0 +1,7 @@ +// Test that NotNull annotations not in the list are not working in Java files. + +class S { + 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/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/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/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/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-separate/notnull/J_2.java b/tests/explicit-nulls/pos-separate/notnull/J_2.java new file mode 100644 index 000000000000..b8837a41f966 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/J_2.java @@ -0,0 +1,52 @@ +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; + } + + @Nonnull + public String[] genericf(T a) { + String[] as = new String[1]; + as[0] = "" + a; + return as; + } + + @Nonnull + 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 new file mode 100644 index 000000000000..d30447e632cb --- /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 { +} 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..96705e36d185 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/notnull/S_3.scala @@ -0,0 +1,15 @@ +// Test that 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) + 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/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/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..a0d5c109605e --- /dev/null +++ b/tests/explicit-nulls/pos/interop-poly-src/J.java @@ -0,0 +1,29 @@ +import java.util.*; + +class JavaCat { + T prop; +} + +class J { + static ScalaCat getScalaCat() { + return null; + } + + 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 new file mode 100644 index 000000000000..1fea277efe90 --- /dev/null +++ b/tests/explicit-nulls/pos/interop-poly-src/S.scala @@ -0,0 +1,20 @@ +// 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 Scala-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() +} 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/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") +} 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/notnull/J.java b/tests/explicit-nulls/pos/notnull/J.java new file mode 100644 index 000000000000..58351827b862 --- /dev/null +++ b/tests/explicit-nulls/pos/notnull/J.java @@ -0,0 +1,50 @@ +package javax.annotation; +import java.util.*; + +public class J { + + private static String getK() { + return "k"; + } + + @Nonnull + public static final String k = getK(); + + @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; + } + + @Nonnull + public String[] genericf(T a) { + String[] as = new String[1]; + as[0] = "" + a; + return as; + } + + @Nonnull + 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 new file mode 100644 index 000000000000..d30447e632cb --- /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 { +} diff --git a/tests/explicit-nulls/pos/notnull/S.scala b/tests/explicit-nulls/pos/notnull/S.scala new file mode 100644 index 000000000000..5e99c45c8547 --- /dev/null +++ b/tests/explicit-nulls/pos/notnull/S.scala @@ -0,0 +1,15 @@ +// Test that NotNull annotations are working in Java 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) + 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) +} 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/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 = { + } + } + } + +} 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/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 b48643f14b3fff96531b0e4933f7da479ccf32c9 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 8 Nov 2019 16:48:59 -0500 Subject: [PATCH 02/39] remove NonNullTermRef --- .../src/dotty/tools/dotc/core/Types.scala | 87 +++------------ docs/docs/internals/explicit-nulls.md | 88 +-------------- .../other-new-features/explicit-nulls.md | 104 ++---------------- 3 files changed, 27 insertions(+), 252 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ac1349c95d91..3b6bb5d24998 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1924,19 +1924,19 @@ object Types { else computeDenot } - 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 - } - private def computeDenot(implicit ctx: Context): Denotation = { + def finish(d: 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 fromDesignator = designator match { case name: Name => val sym = lastSymbol @@ -2235,7 +2235,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) newLikeThis(prefix, sym).asInstanceOf[ThisType] + if ((designator ne sym) && sym.exists) NamedType(prefix, sym).asInstanceOf[ThisType] else this /** A reference like this one, but with the given denotation, if it exists. @@ -2282,10 +2282,10 @@ object Types { d = disambiguate(d, if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod else lastSymbol.asSeenFrom(prefix).signature) - newLikeThis(prefix, name, d) + NamedType(prefix, name, d) } if (prefix eq this.prefix) this - else if (lastDenotation == null) newLikeThis(prefix, designator) + else if (lastDenotation == null) NamedType(prefix, designator) else designator match { case sym: Symbol => if (infoDependsOnPrefix(sym, prefix) && !prefix.isArgPrefixOf(sym)) { @@ -2294,10 +2294,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) newLikeThis(prefix, sym.name, denot.asSeenFrom(prefix)) + if (falseOverride) NamedType(prefix, sym.name, denot.asSeenFrom(prefix)) else candidate } - else newLikeThis(prefix, sym) + else NamedType(prefix, sym) case name: Name => reload() } } @@ -2320,12 +2320,6 @@ object Types { } override def eql(that: Type): Boolean = this eq that // safe because named types are hash-consed separately - - protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType = - NamedType(prefix, designator) - - 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 @@ -2342,7 +2336,7 @@ object Types { private var myDesignator: Designator) extends NamedType with SingletonType with ImplicitRef { - type ThisType >: this.type <: TermRef + type ThisType = TermRef type ThisName = TermName override def designator: Designator = myDesignator @@ -2397,34 +2391,6 @@ 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 - - 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) { assert((prefix ne NoPrefix) || designator.isInstanceOf[Symbol]) myHash = hc @@ -2456,11 +2422,9 @@ 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) @@ -2479,23 +2443,6 @@ 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/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 4ad817ce5eeb..4900ab48aacf 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -12,7 +12,6 @@ The implementation of the feature in dotty can be conceptually divided in severa 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 ------------ @@ -29,7 +28,7 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by: Java Interop ------------ -TODO(abeln): add support for recognizing nullability annotations a la +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)`, @@ -99,88 +98,3 @@ are methods of the `Type` class, so call them with `this` as a receiver: 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 index f8ef015abf85..60f143a15052 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -38,7 +38,7 @@ The unsoundness happens because uninitialized fields in a class start out as `nu ```scala class C { val f: String = foo(f) - def foo(f2: String): String = if (f2 == null) "field is null" else f2 + def foo(f2: String): String = if (f2 == null) "field is null" else f2 } val c = new C() // c.f == "field is null" @@ -46,7 +46,7 @@ val c = new C() 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. +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 @@ -146,7 +146,7 @@ We do the patching with a "nullification" function `n` on types: ```scala class C { T foo() { return null; } } ==> - class C[T] { def foo(): T|Null } + class C[T] { def foo(): T|Null } ``` Notice this is rule is sometimes too conservative, as witnessed by @@ -156,7 +156,7 @@ We do the patching with a "nullification" function `n` on types: 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(); } @@ -165,23 +165,23 @@ We do the patching with a "nullification" function `n` on types: 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. @@ -190,7 +190,7 @@ We do the patching with a "nullification" function `n` on types: 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 @@ -294,89 +294,3 @@ class Old() { 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 - } - ``` From 763b05cafa59320975f60bce1170e0defda401e0 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 14 Nov 2019 16:28:02 -0500 Subject: [PATCH 03/39] add extractor for Null ops; remove useless imports; reduce side effects --- .../tools/dotc/core/NullOpsDecorator.scala | 20 ++----- .../src/dotty/tools/dotc/core/Symbols.scala | 4 +- .../dotty/tools/dotc/core/TypeComparer.scala | 16 ++++-- .../src/dotty/tools/dotc/core/Types.scala | 56 ++++++++++++++----- .../dotty/tools/dotc/transform/LazyVals.scala | 1 - .../tools/dotc/transform/PatternMatcher.scala | 1 - .../tools/dotc/transform/TypeTestsCasts.scala | 1 - 7 files changed, 61 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 7b6e55efe47c..81f5b5d2a125 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -45,6 +45,8 @@ object NullOpsDecorator { if (rrhs.isNullType) llhs else if (llhs.isNullType) rrhs else tp.derivedOrType(llhs, rrhs) + case tp @ AndType(tp1, tp2) => + tp.derivedAndType(strip(tp1), strip(tp2)) case _ => if (tp.isNullType) { if (tp.isJavaNullType) hasJavaNull = true @@ -77,17 +79,6 @@ object NullOpsDecorator { } } - /** 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 - } - def maybeNullable(implicit ctx: Context): Type = if (ctx.explicitNulls) OrType(self, defn.NullType) else self @@ -120,18 +111,17 @@ object NullOpsDecorator { */ def stripAllJavaNull(implicit ctx: Context): Type = { assert(ctx.explicitNulls) - var diff = false object RemoveNulls extends TypeMap { override def apply(tp: Type): Type = tp.normNullableUnion match { case OrType(lhs, rhs) if rhs.isJavaNullType => - diff = true mapOver(lhs) case _ => mapOver(tp) } } - val rem = RemoveNulls(self.widenDealias) - if (diff) rem else self + val self1 = self.widenDealias + val rem = RemoveNulls(self1) + if (rem ne self1) rem else self } /** Injects this type into a union with `JavaNull`. */ diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index ebecb2980a2c..d0226669c9d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -386,8 +386,8 @@ trait Symbols { this: Context => /** 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 { + def getClassesIfDefined(paths: List[PreName]): List[ClassSymbol] = + paths.foldLeft(List.empty){ case (acc, path) => getClassIfDefined(path) match { case cls: ClassSymbol => cls :: acc case _ => acc }} diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index edc1b8765ff0..17fbd9e4efbd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1610,12 +1610,18 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w // 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) + def formal1IsObject = + if (ctx.explicitNulls) formal1 match { + case OrNull(formal1b) => formal1b.isRef(ObjectClass) + case _ => false + } else formal1.isRef(ObjectClass) + def formal2IsObject = + if (ctx.explicitNulls) formal2 match { + case OrNull(formal2b) => formal2b.isRef(ObjectClass) + case _ => false + } + else formal2.isRef(ObjectClass) (isSameTypeWhenFrozen(formal1, formal2a) || tp1.isJavaMethod && formal2IsObject && (formal1 isRef AnyClass) || tp2.isJavaMethod && formal1IsObject && (formal2 isRef AnyClass)) && diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3b6bb5d24998..b1af922762b5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -273,6 +273,17 @@ 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] @@ -588,19 +599,19 @@ object Types { case AndType(l, r) => goAnd(l, r) case tp: OrType => - 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) + tp match { + case OrJavaNull(tp1) => + // 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. + tp1.findMember(name, pre.stripJavaNull, required, excluded) + case _ => + // 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) @@ -2924,6 +2935,25 @@ object Types { else apply(tp1, tp2) } + object OrNull { + def apply(tp: Type)(given Context) = + OrType(tp, defn.NullType) + def unapply(tp: Type)(given Context): Option[Type] = + val tp1 = tp.stripNull + if tp1 ne tp then Some(tp1) else None + } + + object OrJavaNull { + def apply(tp: Type)(given Context) = + OrType(tp, defn.JavaNullAliasType) + def unapply(tp: Type)(given ctx: Context): Option[Type] = + if (ctx.explicitNulls) { + val tp1 = tp.stripJavaNull + if tp1 ne tp then Some(tp1) else None + } + else None + } + // ----- ExprType and LambdaTypes ----------------------------------- // Note: method types are cached whereas poly types are not. The reason diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 00509e12e865..186bc9521041 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -16,7 +16,6 @@ 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 762076ed6e9f..6930539ec990 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -17,7 +17,6 @@ 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/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 7099c0b26292..4b31f5ae28cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -14,7 +14,6 @@ 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 From 00d6607df700996f89ef40edf779d4e6020b07f7 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Nov 2019 15:35:12 -0500 Subject: [PATCH 04/39] merge upstream (Nullability Analysis without NotNull #7556) --- community-build/community-projects/scalatest | 2 +- .../backend/jvm/DottyBackendInterface.scala | 3 +- .../dotty/tools/backend/sjs/JSEncoding.scala | 13 +- .../dotty/tools/dotc/CompilationUnit.scala | 12 + .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 10 +- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 4 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 14 +- .../dotc/classpath/DirectoryClassPath.scala | 2 +- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../src/dotty/tools/dotc/core/Contexts.scala | 32 +- .../dotty/tools/dotc/core/Definitions.scala | 4 + .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../dotc/core/PatternTypeConstrainer.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 5 + .../dotty/tools/dotc/core/TypeComparer.scala | 8 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 29 +- .../src/dotty/tools/dotc/core/Types.scala | 35 +- .../dotc/core/tasty/PositionPickler.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 6 +- .../tools/dotc/interactive/Completion.scala | 2 +- .../tools/dotc/parsing/JavaParsers.scala | 69 +++- .../tools/dotc/parsing/JavaScanners.scala | 48 +++ .../dotty/tools/dotc/parsing/Parsers.scala | 22 +- .../dotty/tools/dotc/parsing/Scanners.scala | 24 +- .../tools/dotc/printing/RefinedPrinter.scala | 14 +- .../dotty/tools/dotc/reporting/Reporter.scala | 2 +- .../dotc/reporting/diagnostic/messages.scala | 2 +- .../ReflectionCompilerInterface.scala | 28 +- .../dotty/tools/dotc/transform/Erasure.scala | 5 +- .../tools/dotc/transform/ExpandSAMs.scala | 2 +- .../tools/dotc/transform/FirstTransform.scala | 5 +- .../dotc/transform/NonLocalReturns.scala | 2 +- .../dotc/transform/PCPCheckAndHeal.scala | 10 +- .../dotty/tools/dotc/transform/Pickler.scala | 2 +- .../tools/dotc/transform/ReifyQuotes.scala | 8 +- .../tools/dotc/transform/TreeChecker.scala | 6 +- .../dotty/tools/dotc/typer/Applications.scala | 6 +- .../dotty/tools/dotc/typer/ConstFold.scala | 11 +- .../dotty/tools/dotc/typer/Docstrings.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 12 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 5 +- .../src/dotty/tools/dotc/typer/Namer.scala | 11 +- .../dotty/tools/dotc/typer/Nullables.scala | 360 ++++++++++++++++++ .../dotty/tools/dotc/typer/RefChecks.scala | 6 +- .../src/dotty/tools/dotc/typer/Typer.scala | 137 ++++--- .../tools/dotc/typer/VarianceChecker.scala | 2 +- .../dotty/tools/dotc/util/SourceFile.scala | 6 - .../tools/repl/CollectTopLevelImports.scala | 2 +- .../src/dotty/tools/repl/ReplDriver.scala | 2 +- compiler/test-resources/repl/i4852 | 6 + compiler/test-resources/repl/i5218 | 2 +- compiler/test/dotc/pos-from-tasty.blacklist | 6 +- .../test/dotc/pos-test-pickling.blacklist | 3 + .../dotty/tools/dotc/CompilationTests.scala | 12 +- .../test/dotty/tools/repl/LoadTests.scala | 85 +++++ .../tools/vulpix/TestConfiguration.scala | 2 +- .../test/dotty/tools/dottydoc/GenDocs.scala | 2 +- .../2016-02-17-scaling-dot-soundness.md | 2 +- .../changed-features/vararg-patterns.md | 2 +- .../contextual/relationship-implicits.md | 27 +- .../reference/dropped-features/auto-apply.md | 2 +- .../dropped-features/procedure-syntax.md | 2 +- .../docs/reference/features-classification.md | 6 +- .../other-new-features/indentation.md | 2 +- docs/docs/reference/overview.md | 6 +- library/src/dotty/DottyPredef.scala | 6 +- library/src/scala/compiletime/package.scala | 12 +- .../tasty/reflect/CompilerInterface.scala | 1 + library/src/scala/tasty/reflect/TreeOps.scala | 1 + project/Build.scala | 4 +- .../simple/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../added/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../binary/project/DottyInjectedPlugin.scala | 2 +- .../by-name/project/DottyInjectedPlugin.scala | 2 +- .../canon/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../empty-a/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../erasure/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../named/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../sealed/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../struct/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- .../var/project/DottyInjectedPlugin.scala | 2 +- .../project/DottyInjectedPlugin.scala | 2 +- tests/neg/i6606.scala | 8 + tests/neg/i6762.check | 5 + tests/neg/i6762.scala | 5 + tests/neg/i6762b.check | 5 + tests/neg/i6762b.scala | 13 + tests/neg/i6779.check | 15 + tests/neg/i6779.scala | 13 + tests/neg/i7359-b.check | 4 + tests/neg/i7359-b.scala | 3 + tests/neg/i7359-c.check | 4 + tests/neg/i7359-c.scala | 3 + tests/neg/i7359-d.check | 4 + tests/neg/i7359-d.scala | 3 + tests/neg/i7359-e.check | 4 + tests/neg/i7359-e.scala | 3 + tests/neg/i7359-f.check | 7 + tests/neg/i7359-f.scala | 3 + tests/neg/i7359-g.check | 5 + tests/neg/i7359-g.scala | 5 + tests/neg/i7359.check | 4 + tests/neg/i7359.scala | 3 + tests/pos-macros/i7513/Macro_1.scala | 12 + tests/pos-macros/i7513/Test_2.scala | 3 + tests/pos-macros/i7513b/Macro_1.scala | 12 + tests/pos-macros/i7513b/Test_2.scala | 3 + tests/pos-macros/i7513c/Macro_1.scala | 14 + tests/pos-macros/i7513c/Test_2.scala | 3 + tests/pos-special/notNull.scala | 21 + tests/pos-special/nullable.scala | 67 ++++ tests/pos/i6475.scala | 4 + tests/pos/i7359.scala | 135 +++++++ tests/pos/i7519.scala | 13 + tests/pos/i7519b.scala | 13 + tests/pos/i7525/Interface.java | 5 + tests/pos/i7532.scala | 18 + tests/pos/t3236/AnnotationTest.scala | 33 ++ tests/pos/t3236/BooleanAnnotation.java | 7 + tests/pos/t3236/ByteAnnotation.java | 7 + tests/pos/t3236/CharAnnotation.java | 7 + tests/pos/t3236/Constants.java | 34 ++ tests/pos/t3236/DoubleAnnotation.java | 7 + tests/pos/t3236/FloatAnnotation.java | 7 + tests/pos/t3236/IntAnnotation.java | 7 + tests/pos/t3236/LongAnnotation.java | 7 + tests/pos/t3236/ShortAnnotation.java | 7 + tests/pos/t3236/StringAnnotation.java | 7 + tests/pos/t3236/Test.scala | 44 +++ tests/run-macros/i7519c.check | 1 + tests/run-macros/i7519c/Macro_1.scala | 13 + tests/run-macros/i7519c/Test_2.scala | 5 + .../tasty-seal-method/quoted_1.scala | 16 +- tests/run/i5094.scala | 17 + tests/run/i7359.check | 6 + tests/run/i7359.scala | 46 +++ 249 files changed, 1802 insertions(+), 359 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/Nullables.scala create mode 100644 compiler/test-resources/repl/i4852 create mode 100644 compiler/test/dotty/tools/repl/LoadTests.scala create mode 100644 tests/neg/i6606.scala create mode 100644 tests/neg/i6762.check create mode 100644 tests/neg/i6762.scala create mode 100644 tests/neg/i6762b.check create mode 100644 tests/neg/i6762b.scala create mode 100644 tests/neg/i6779.check create mode 100644 tests/neg/i6779.scala create mode 100644 tests/neg/i7359-b.check create mode 100644 tests/neg/i7359-b.scala create mode 100644 tests/neg/i7359-c.check create mode 100644 tests/neg/i7359-c.scala create mode 100644 tests/neg/i7359-d.check create mode 100644 tests/neg/i7359-d.scala create mode 100644 tests/neg/i7359-e.check create mode 100644 tests/neg/i7359-e.scala create mode 100644 tests/neg/i7359-f.check create mode 100644 tests/neg/i7359-f.scala create mode 100644 tests/neg/i7359-g.check create mode 100644 tests/neg/i7359-g.scala create mode 100644 tests/neg/i7359.check create mode 100644 tests/neg/i7359.scala create mode 100644 tests/pos-macros/i7513/Macro_1.scala create mode 100644 tests/pos-macros/i7513/Test_2.scala create mode 100644 tests/pos-macros/i7513b/Macro_1.scala create mode 100644 tests/pos-macros/i7513b/Test_2.scala create mode 100644 tests/pos-macros/i7513c/Macro_1.scala create mode 100644 tests/pos-macros/i7513c/Test_2.scala create mode 100644 tests/pos-special/notNull.scala create mode 100644 tests/pos-special/nullable.scala create mode 100644 tests/pos/i6475.scala create mode 100644 tests/pos/i7359.scala create mode 100644 tests/pos/i7519.scala create mode 100644 tests/pos/i7519b.scala create mode 100644 tests/pos/i7525/Interface.java create mode 100644 tests/pos/i7532.scala create mode 100644 tests/pos/t3236/AnnotationTest.scala create mode 100644 tests/pos/t3236/BooleanAnnotation.java create mode 100644 tests/pos/t3236/ByteAnnotation.java create mode 100644 tests/pos/t3236/CharAnnotation.java create mode 100644 tests/pos/t3236/Constants.java create mode 100644 tests/pos/t3236/DoubleAnnotation.java create mode 100644 tests/pos/t3236/FloatAnnotation.java create mode 100644 tests/pos/t3236/IntAnnotation.java create mode 100644 tests/pos/t3236/LongAnnotation.java create mode 100644 tests/pos/t3236/ShortAnnotation.java create mode 100644 tests/pos/t3236/StringAnnotation.java create mode 100644 tests/pos/t3236/Test.scala create mode 100644 tests/run-macros/i7519c.check create mode 100644 tests/run-macros/i7519c/Macro_1.scala create mode 100644 tests/run-macros/i7519c/Test_2.scala create mode 100644 tests/run/i5094.scala create mode 100644 tests/run/i7359.check create mode 100644 tests/run/i7359.scala diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index d59471cdc711..fca3f01cfc27 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit d59471cdc7112f0e2c6f4f8b32ed15b6fd6f5515 +Subproject commit fca3f01cfc27c5343b62376e854c1c676ed91084 diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 46c99739e169..a453caa02f62 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -846,7 +846,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def addRemoteRemoteExceptionAnnotation: Unit = () def samMethod(): Symbol = ctx.atPhase(ctx.erasurePhase) { - toDenot(sym).info.abstractTermMembers.toList match { + val samMethods = toDenot(sym).info.possibleSamMethods.toList + samMethods match { case x :: Nil => x.symbol case Nil => abort(s"${sym.show} is not a functional interface. It doesn't have abstract methods") case xs => abort(s"${sym.show} is not a functional interface. " + diff --git a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala index 97b5e3e47dd1..9d0df71f4946 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -410,8 +410,17 @@ object JSEncoding { paramAndResultTypeNames.mkString(SignatureSep, SignatureSep, "") /** Computes the internal name for a type. */ - private def internalName(tpe: Type)(implicit ctx: Context): String = - encodeTypeRef(toTypeRef(tpe)) + private def internalName(tpe: Type)(implicit ctx: Context): String = { + val typeRef = toTypeRef(tpe) + + val safeTypeRef: jstpe.TypeRef = typeRef match { + case jstpe.ClassRef("s_Null") => jstpe.ClassRef(ir.Definitions.NullClass) + case jstpe.ClassRef("s_Nothing") => jstpe.ClassRef(ir.Definitions.NothingClass) + case otherTypeRef => otherTypeRef + } + + encodeTypeRef(safeTypeRef) + } /** Encodes a [[Types.TypeRef]], such as in an encoded method signature. */ diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index ea0faa19ce6d..0682d171b1b2 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -5,11 +5,13 @@ import util.SourceFile import ast.{tpd, untpd} import tpd.{Tree, TreeTraverser} import typer.PrepareInlineable.InlineAccessors +import typer.Nullables import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.transform.SymUtils._ import util.{NoSource, SourceFile} +import util.Spans.Span import core.Decorators._ class CompilationUnit protected (val source: SourceFile) { @@ -42,6 +44,16 @@ class CompilationUnit protected (val source: SourceFile) { suspended = true ctx.run.suspendedUnits += this throw CompilationUnit.SuspendException() + + private var myAssignmentSpans: Map[Int, List[Span]] = null + + /** A map from (name-) offsets of all local variables in this compilation unit + * that can be tracked for being not null to the list of spans of assignments + * to these variables. + */ + def assignmentSpans(given Context): Map[Int, List[Span]] = + if myAssignmentSpans == null then myAssignmentSpans = Nullables.assignmentSpans + myAssignmentSpans } object CompilationUnit { diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 5fd0ff39d366..dc891b235135 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -576,7 +576,7 @@ object desugar { ensureApplied(nu) } - val copiedAccessFlags = if (ctx.scala2Setting) EmptyFlags else AccessFlags + val copiedAccessFlags = if (ctx.scala2CompatSetting) EmptyFlags else AccessFlags // Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams) // def _1: T1 = this.p1 diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8cdb7ce38e85..7352139ea0e9 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -88,6 +88,12 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => /** If this is a block, its expression part */ def stripBlock(tree: Tree): Tree = unsplice(tree) match { case Block(_, expr) => stripBlock(expr) + case Inlined(_, _, expr) => stripBlock(expr) + case _ => tree + } + + def stripInlined(tree: Tree): Tree = unsplice(tree) match { + case Inlined(_, _, expr) => stripInlined(expr) case _ => tree } @@ -391,7 +397,9 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => if (fn.symbol.is(Erased) || fn.symbol == defn.InternalQuoted_typeQuote) Pure else exprPurity(fn) case Apply(fn, args) => def isKnownPureOp(sym: Symbol) = - sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass + sym.owner.isPrimitiveValueClass + || sym.owner == defn.StringClass + || defn.pureMethods.contains(sym) if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure. || (fn.symbol.isStableMember && !fn.symbol.is(Lazy)) || fn.symbol.isPrimaryConstructor && fn.symbol.owner.isNoInitsClass) // TODO: include in isStable? diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 4c79a36b6abf..9aebea9bd594 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -121,8 +121,8 @@ class TreeTypeMap( val bind1 = tmap.transformSub(bind) val expr1 = tmap.transform(expr) cpy.Labeled(labeled)(bind1, expr1) - case Hole(n, args) => - Hole(n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe)) + case Hole(isTermHole, n, args) => + Hole(isTermHole, n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe)) case tree1 => super.transform(tree1) } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 6e3a02fe86e3..38914097cc6f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1458,7 +1458,7 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Hole(_, args) => + case Hole(_, _, args) => this(x, args) case _ => foldMoreCases(x, tree) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 1827638ba9ad..117f4f1b8697 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -711,11 +711,19 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { class TimeTravellingTreeCopier extends TypedTreeCopier { override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): Apply = - ta.assignType(untpdCpy.Apply(tree)(fun, args), fun, args) + tree match + case tree: Apply + if (tree.fun eq fun) && (tree.args eq args) + && tree.tpe.isInstanceOf[ConstantType] + && isPureExpr(tree) => tree + case _ => + ta.assignType(untpdCpy.Apply(tree)(fun, args), fun, args) // Note: Reassigning the original type if `fun` and `args` have the same types as before - // does not work here: The computed type depends on the widened function type, not - // the function type itself. A treetransform may keep the function type the + // does not work here in general: The computed type depends on the widened function type, not + // the function type itself. A tree transform may keep the function type the // same but its widened type might change. + // However, we keep constant types of pure expressions. This uses the underlying assumptions + // that pure functions yielding a constant will not change in later phases. override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply = ta.assignType(untpdCpy.TypeApply(tree)(fun, args), fun, args) diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index f3c5d48ddaa6..dc5504cffba0 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -179,7 +179,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No if (inPackage == "") ClassPathEntries(packages(inPackage), Nil) else ClassPathEntries(packages(inPackage), classes(inPackage)) - def asURLs: Seq[URL] = Seq(dir.toUri.toURL) + def asURLs: Seq[URL] = Seq(new URL("jrt:/")) // We don't yet have a scheme to represent the JDK modules in our `-classpath`. // java models them as entries in the new "module path", we'll probably need to follow this. def asClassPathStrings: Seq[String] = Nil diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index d78f1cbd8b3d..8dba6d76c52e 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -44,7 +44,7 @@ class ScalaSettings extends Settings.SettingGroup { val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80) withAbbreviation "--page-width" val strict: Setting[Boolean] = BooleanSetting("-strict", "Use strict type rules, which means some formerly legal code does not typecheck anymore.") withAbbreviation "--strict" val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.") withAbbreviation "--language" - val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") withAbbreviation "--rewrite" + val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2Compat rewrites sources to migrate to new syntax") withAbbreviation "--rewrite" val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.") withAbbreviation "--no-warnings" val fromTasty: Setting[Boolean] = BooleanSetting("-from-tasty", "Compile classes from tasty in classpath. The arguments are used as class names.") withAbbreviation "--from-tasty" diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 6023fb9b0f49..7d17d37d87e4 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -15,7 +15,8 @@ import ast.Trees._ import ast.untpd import Flags.GivenOrImplicit import util.{FreshNameCreator, NoSource, SimpleIdentityMap, SourceFile} -import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, SearchRoot, TypeAssigner, Typer} +import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} +import Nullables.{NotNullInfo, given} import Implicits.ContextualImplicits import config.Settings._ import config.Config @@ -47,7 +48,11 @@ 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 (notNullInfosLoc, store9) = store8.newLocation[List[NotNullInfo]]() + private val initialStore = store9 + + /** The current context */ + def curCtx(given ctx: Context): Context = ctx /** A context is passed basically everywhere in dotc. * This is convenient but carries the risk of captured contexts in @@ -207,6 +212,9 @@ object Contexts { /** The current compiler-run profiler */ def profiler: Profiler = store(profilerLoc) + /** The paths currently known to be not null */ + def notNullInfos = store(notNullInfosLoc) + /** The new implicit references that are introduced by this scope */ protected var implicitsCache: ContextualImplicits = null def implicits: ContextualImplicits = { @@ -239,7 +247,7 @@ object Contexts { } /** Sourcefile with given path name, memoized */ - def getSource(path: SourceFile.PathName): SourceFile = base.sourceNamed.get(path) match { + def getSource(path: TermName): SourceFile = base.sourceNamed.get(path) match { case Some(source) => source case None => @@ -559,6 +567,7 @@ 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 setNotNullInfos(notNullInfos: List[NotNullInfo]): this.type = updateStore(notNullInfosLoc, notNullInfos) def setProperty[T](key: Key[T], value: T): this.type = setMoreProperties(moreProperties.updated(key, value)) @@ -590,6 +599,17 @@ object Contexts { def setDebug: this.type = setSetting(base.settings.Ydebug, true) } + given (c: Context) + def addNotNullInfo(info: NotNullInfo) = + c.withNotNullInfos(c.notNullInfos.extendWith(info)) + + def addNotNullRefs(refs: Set[TermRef]) = + c.addNotNullInfo(NotNullInfo(refs, Set())) + + def withNotNullInfos(infos: List[NotNullInfo]): Context = + if c.notNullInfos eq infos then c else c.fresh.setNotNullInfos(infos) + + // TODO: Fix issue when converting ModeChanges and FreshModeChanges to extension givens implicit class ModeChanges(val c: Context) extends AnyVal { final def withModeBits(mode: Mode): Context = if (mode != c.mode) c.fresh.setMode(mode) else c @@ -618,7 +638,9 @@ object Contexts { typeAssigner = TypeAssigner moreProperties = Map.empty source = NoSource - store = initialStore.updated(settingsStateLoc, settingsGroup.defaultState) + store = initialStore + .updated(settingsStateLoc, settingsGroup.defaultState) + .updated(notNullInfosLoc, Nil) typeComparer = new TypeComparer(this) searchHistory = new SearchRoot gadt = EmptyGadtConstraint @@ -688,7 +710,7 @@ object Contexts { /** Sources that were loaded */ val sources: mutable.HashMap[AbstractFile, SourceFile] = new mutable.HashMap[AbstractFile, SourceFile] - val sourceNamed: mutable.HashMap[SourceFile.PathName, SourceFile] = new mutable.HashMap[SourceFile.PathName, SourceFile] + val sourceNamed: mutable.HashMap[TermName, SourceFile] = new mutable.HashMap[TermName, SourceFile] // Types state /** A table for hash consing unique types */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4a6f004ebebd..52ae52b857ea 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -318,6 +318,10 @@ class Definitions { def ObjectMethods: List[TermSymbol] = List(Object_eq, Object_ne, Object_synchronized, Object_clone, Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI) + /** Methods in Object and Any that do not have a side effect */ + @tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode, + Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne) + @tu lazy val AnyKindClass: ClassSymbol = { val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil) if (!ctx.settings.YnoKindPolymorphism.value) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 4dbaddd70739..e47def78fba1 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -486,7 +486,7 @@ object Denotations { // things, starting with the return type of this method. if (preferSym(sym2, sym1)) info2 else if (preferSym(sym1, sym2)) info1 - else if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) + else if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2CompatMode) info1 // follow Scala2 linearization - // compare with way merge is performed in SymDenotation#computeMembersNamed else throw new MergeError(ex.sym1, ex.sym2, ex.tp1, ex.tp2, pre) diff --git a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala index 09d8a0f54462..b6f9284d6269 100644 --- a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala +++ b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala @@ -199,7 +199,7 @@ trait PatternTypeConstrainer { self: TypeComparer => } } - val widePt = if (ctx.scala2Mode || refinementIsInvariant(patternTp)) scrutineeTp else widenVariantParams(scrutineeTp) + val widePt = if (ctx.scala2CompatMode || refinementIsInvariant(patternTp)) scrutineeTp else widenVariantParams(scrutineeTp) val narrowTp = SkolemType(patternTp) trace(i"constraining simple pattern type $narrowTp <:< $widePt", gadts, res => s"$res\ngadt = ${ctx.gadt.debugBoundsDescription}") { isSubType(narrowTp, widePt) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 56d5cabf9c47..1af201274f84 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -195,6 +195,7 @@ object StdNames { final val ExprApi: N = "ExprApi" final val Mirror: N = "Mirror" final val Nothing: N = "Nothing" + final val NotNull: N = "NotNull" final val Null: N = "Null" final val JavaNull: N = "JavaNull" final val Object: N = "Object" @@ -262,6 +263,7 @@ object StdNames { val MIRROR_PREFIX: N = "$m." val MIRROR_SHORT: N = "$m" val MIRROR_UNTYPED: N = "$m$untyped" + val NOT_NULL: N = "$nn" val REIFY_FREE_PREFIX: N = "free$" val REIFY_FREE_THIS_SUFFIX: N = "$this" val REIFY_FREE_VALUE_SUFFIX: N = "$value" @@ -358,6 +360,7 @@ object StdNames { val RootPackage: N = "RootPackage" val RootClass: N = "RootClass" val Scala2: N = "Scala2" + val Scala2Compat: N = "Scala2Compat" val Select: N = "Select" val Shape: N = "Shape" val StringContext: N = "StringContext" @@ -808,6 +811,7 @@ object StdNames { final val ELSEkw: N = kw("else") final val ENUMkw: N = kw("enum") final val EXTENDSkw: N = kw("extends") + final val FALSEkw: N = kw("false") final val FINALkw: N = kw("final") final val FINALLYkw: N = kw("finally") final val FLOATkw: N = kw("float") @@ -837,6 +841,7 @@ object StdNames { final val THROWkw: N = kw("throw") final val THROWSkw: N = kw("throws") final val TRANSIENTkw: N = kw("transient") + final val TRUEkw: N = kw("true") final val TRYkw: N = kw("try") final val VOIDkw: N = kw("void") final val VOLATILEkw: N = kw("volatile") diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 17fbd9e4efbd..09f7e8e2de14 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -574,7 +574,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w * am not sure how, since the code is buried so deep in subtyping logic. */ def boundsOK = - ctx.scala2Mode || + ctx.scala2CompatMode || tp1.typeParams.corresponds(tp2.typeParams)((tparam1, tparam2) => isSubType(tparam2.paramInfo.subst(tp2, tp1), tparam1.paramInfo)) val saved = comparedTypeLambdas @@ -1023,7 +1023,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w def compareS(tp: AppliedType, other: Type, fromBelow: Boolean): Boolean = tp.args match { case arg :: Nil => natValue(arg) match { - case Some(n) => + case Some(n) if n != Int.MaxValue => val succ = ConstantType(Constant(n + 1)) if (fromBelow) recur(other, succ) else recur(succ, other) case none => @@ -1746,7 +1746,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w /** The greatest lower bound of a list types */ final def glb(tps: List[Type]): Type = tps.foldLeft(AnyType: Type)(glb) - def widenInUnions(implicit ctx: Context): Boolean = ctx.scala2Mode || ctx.erasedTypes + def widenInUnions(implicit ctx: Context): Boolean = ctx.scala2CompatMode || ctx.erasedTypes /** The least upper bound of two types * @param canConstrain If true, new constraints might be added to simplify the lub. @@ -1854,7 +1854,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w else if (!tp2.exists) tp1 else tp.derivedAndType(tp1, tp2) - /** If some (&-operand of) this type is a supertype of `sub` replace it with `NoType`. + /** If some (&-operand of) `tp` is a supertype of `sub` replace it with `NoType`. */ private def dropIfSuper(tp: Type, sub: Type): Type = if (isSubTypeWhenFrozen(sub, tp)) NoType diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index f0a710ea8b17..eea252d4522e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -81,7 +81,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. // called which we override to set the `approximated` flag. range(defn.NothingType, pre) else pre - else if ((pre.termSymbol is Package) && !(thiscls is Package)) + else if (pre.termSymbol.is(Package) && !thiscls.is(Package)) toPrefix(pre.select(nme.PACKAGE), cls, thiscls) else toPrefix(pre.baseType(cls).normalizedPrefix, cls.owner, thiscls) @@ -369,7 +369,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * type parameter corresponding to the wildcard. */ def skolemizeWildcardArgs(tps: List[Type], app: Type) = app match { - case AppliedType(tycon, args) if tycon.typeSymbol.isClass && !scala2Mode => + case AppliedType(tycon, args) if tycon.typeSymbol.isClass && !scala2CompatMode => tps.zipWithConserve(tycon.typeSymbol.typeParams) { (tp, tparam) => tp match { case _: TypeBounds => app.select(tparam) @@ -500,25 +500,34 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def canAutoTuple: Boolean = !featureEnabled(nme.noAutoTupling) - def scala2Mode: Boolean = - featureEnabled(nme.Scala2) + def scala2CompatMode: Boolean = + featureEnabled(nme.Scala2Compat) || { + val scala2 = featureEnabled(nme.Scala2) + if scala2 then ctx.warning("Use `-language:Scala2Compat` or `import scala.Scala2Compat` instead of `-language:Scala2` or `import scala.Scala2`") + scala2 + } + def dynamicsEnabled: Boolean = featureEnabled(nme.dynamics) - def testScala2Mode(msg: => Message, pos: SourcePosition, replace: => Unit = ()): Boolean = { - if (scala2Mode) { + def testScala2CompatMode(msg: => Message, pos: SourcePosition, replace: => Unit = ()): Boolean = { + if (scala2CompatMode) { migrationWarning(msg, pos) replace } - scala2Mode + scala2CompatMode } - /** Is option -language:Scala2 set? + /** Is option -language:Scala2Compat set? * This test is used when we are too early in the pipeline to consider imports. */ - def scala2Setting: Boolean = - ctx.settings.language.value.contains(nme.Scala2.toString) + def scala2CompatSetting: Boolean = + ctx.settings.language.value.contains(nme.Scala2Compat.toString) || { + val scala2 = ctx.settings.language.value.contains(nme.Scala2.toString) + if scala2 then ctx.warning("Use -language:Scala2Compat instead of -language:Scala2") + scala2 + } /** Refine child based on parent * diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b1af922762b5..f1b3beaae39a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -38,6 +38,8 @@ import java.lang.ref.WeakReference import scala.annotation.internal.sharable import scala.annotation.threadUnsafe +import dotty.tools.dotc.transform.SymUtils._ + object Types { @sharable private var nextId = 0 @@ -777,6 +779,26 @@ object Types { (name, buf) => buf ++= nonPrivateMember(name).altsWith(_.is(Deferred))) } + /** + * Returns the set of methods that are abstract and do not overlap with any of + * [[java.lang.Object]] methods. + * + * Conceptually, a SAM (functional interface) has exactly one abstract method. + * If an interface declares an abstract method overriding one of the public + * methods of [[java.lang.Object]], that also does not count toward the interface's + * abstract method count. + * + * @see https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html + * + * @return the set of methods that are abstract and do not match any of [[java.lang.Object]] + * + */ + final def possibleSamMethods(implicit ctx: Context): Seq[SingleDenotation] = { + record("possibleSamMethods") + abstractTermMembers + .filterNot(m => m.symbol.matchingMember(defn.ObjectType).exists || m.symbol.isSuperAccessor) + } + /** The set of abstract type members of this type. */ final def abstractTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = { record("abstractTypeMembers") @@ -1448,6 +1470,11 @@ object Types { case _ => true } + /** Is this (an alias of) the `scala.Null` type? */ + final def isNull(given Context) = + isRef(defn.NullClass) + || classSymbol.name == tpnme.Null // !!! temporary kludge for being able to test without the explicit nulls PR + /** The resultType of a LambdaType, or ExprType, the type itself for others */ def resultType(implicit ctx: Context): Type = this @@ -2342,7 +2369,7 @@ object Types { } /** The singleton type for path prefix#myDesignator. - */ + */ abstract case class TermRef(override val prefix: Type, private var myDesignator: Designator) extends NamedType with SingletonType with ImplicitRef { @@ -3620,7 +3647,8 @@ object Types { if (defn.isCompiletime_S(tycon.symbol) && args.length == 1) trace(i"normalize S $this", typr, show = true) { args.head.normalized match { - case ConstantType(Constant(n: Int)) => ConstantType(Constant(n + 1)) + case ConstantType(Constant(n: Int)) if n >= 0 && n < Int.MaxValue => + ConstantType(Constant(n + 1)) case none => tryMatchAlias } } @@ -4437,8 +4465,7 @@ object Types { } def unapply(tp: Type)(implicit ctx: Context): Option[MethodType] = if (isInstantiatable(tp)) { - val absMems = tp.abstractTermMembers - // println(s"absMems: ${absMems map (_.show) mkString ", "}") + val absMems = tp.possibleSamMethods if (absMems.size == 1) absMems.head.info match { case mt: MethodType if !mt.isParamDependent && diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index 20b5ddd8703c..280d858eda69 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -45,7 +45,7 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Addr) { def pickleSource(source: SourceFile): Unit = { buf.writeInt(SOURCE) - buf.writeInt(pickler.nameBuffer.nameIndex(source.pathName).index) + buf.writeInt(pickler.nameBuffer.nameIndex(source.path.toTermName).index) } /** True if x's position shouldn't be reconstructed automatically from its initial span diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index feb96a4929b0..19044baac2d5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -21,7 +21,9 @@ object TreePickler { val sectionName = "ASTs" - case class Hole(idx: Int, args: List[tpd.Tree])(implicit @constructorOnly src: SourceFile) extends tpd.Tree { + case class Hole(isTermHole: Boolean, idx: Int, args: List[tpd.Tree])(implicit @constructorOnly src: SourceFile) extends tpd.Tree { + override def isTerm: Boolean = isTermHole + override def isType: Boolean = !isTermHole override def fallbackToText(printer: Printer): Text = s"[[$idx|" ~~ printer.toTextGlobal(args, ", ") ~~ "]]" } @@ -579,7 +581,7 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } - case Hole(idx, args) => + case Hole(_, idx, args) => writeByte(HOLE) withLength { writeNat(idx) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index d3178ba2bbca..ef573fb7a1df 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -207,7 +207,7 @@ object Completion { def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = if (!qual.tpe.widenDealias.isBottomType) { addAccessibleMembers(qual.tpe) - if (!mode.is(Mode.Import) && !qual.tpe.isRef(defn.NullClass)) + if (!mode.is(Mode.Import) && !qual.tpe.isNull) // Implicit conversions do not kick in when importing // and for `NullClass` they produce unapplicable completions (for unclear reasons) implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 9e41cff04106..203fa0c91b84 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -521,7 +521,7 @@ object JavaParsers { val vparams = formalParams() if (!isVoid) rtpt = optArrayBrackets(rtpt) optThrows() - val bodyOk = !inInterface || (mods.is(Flags.DefaultMethod)) + val bodyOk = !inInterface || mods.isOneOf(Flags.DefaultMethod | Flags.JavaStatic) val body = if (bodyOk && in.token == LBRACE) methodBody() @@ -601,9 +601,53 @@ object JavaParsers { def varDecl(mods: Modifiers, tpt: Tree, name: TermName): ValDef = { val tpt1 = optArrayBrackets(tpt) - if (in.token == EQUALS && !mods.is(Flags.Param)) skipTo(COMMA, SEMI) + /** Tries to detect final static literals syntactically and returns a constant type replacement */ + def optConstantTpe(): Tree = { + def constantTpe(const: Constant): Tree = TypeTree(ConstantType(const)) + + def forConst(const: Constant): Tree = { + if (in.token != SEMI) tpt1 + else { + def isStringTyped = tpt1 match { + case Ident(n: TypeName) => "String" == n.toString + case _ => false + } + if (const.tag == Constants.StringTag && isStringTyped) constantTpe(const) + else tpt1 match { + case TypedSplice(tpt2) => + if (const.tag == Constants.BooleanTag || const.isNumeric) { + //for example, literal 'a' is ok for float. 127 is ok for byte, but 128 is not. + val converted = const.convertTo(tpt2.tpe) + if (converted == null) tpt1 + else constantTpe(converted) + } + else tpt1 + case _ => tpt1 + } + } + } + + in.nextToken() // EQUALS + if (mods.is(Flags.JavaStatic) && mods.is(Flags.Final)) { + val neg = in.token match { + case MINUS | BANG => in.nextToken(); true + case _ => false + } + tryLiteral(neg).map(forConst).getOrElse(tpt1) + } + else tpt1 + } + + val tpt2: Tree = + if (in.token == EQUALS && !mods.is(Flags.Param)) { + val res = optConstantTpe() + skipTo(COMMA, SEMI) + res + } + else tpt1 + val mods1 = if (mods.is(Flags.Final)) mods else mods | Flags.Mutable - ValDef(name, tpt1, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) + ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) } def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match { @@ -881,6 +925,25 @@ object JavaParsers { case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree) } + def tryLiteral(negate: Boolean = false): Option[Constant] = { + val l = in.token match { + case TRUE => !negate + case FALSE => negate + case CHARLIT => in.strVal.charAt(0) + case INTLIT => in.intVal(negate).toInt + case LONGLIT => in.intVal(negate) + case FLOATLIT => in.floatVal(negate).toFloat + case DOUBLELIT => in.floatVal(negate) + case STRINGLIT => in.strVal + case _ => null + } + if (l == null) None + else { + in.nextToken() + Some(Constant(l)) + } + } + /** CompilationUnit ::= [package QualId semi] TopStatSeq */ def compilationUnit(): Tree = { diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index 0e437d9d0a49..bef78f06e9f0 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -479,6 +479,54 @@ object JavaScanners { setStrVal() } + /** convert name to long value + */ + def intVal(negated: Boolean): Long = + if (token == CHARLIT && !negated) + if (strVal.length > 0) strVal.charAt(0).toLong else 0 + else { + var value: Long = 0 + val divider = if (base == 10) 1 else 2 + val limit: Long = + if (token == LONGLIT) Long.MaxValue else Int.MaxValue + var i = 0 + val len = strVal.length + while (i < len) { + val d = digit2int(strVal.charAt(i), base) + if (d < 0) { + error("malformed integer number") + return 0 + } + if (value < 0 || + limit / (base / divider) < value || + limit - (d / divider) < value * (base / divider) && + !(negated && limit == value * base - 1 + d)) { + error("integer number too large") + return 0 + } + value = value * base + d + i += 1 + } + if (negated) -value else value + } + + /** convert name, base to double value + */ + def floatVal(negated: Boolean): Double = { + val limit: Double = + if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue + try { + val value: Double = java.lang.Double.valueOf(strVal.toString).doubleValue() + if (value > limit) + error("floating point number too large") + if (negated) -value else value + } catch { + case _: NumberFormatException => + error("malformed floating point number") + 0.0 + } + } + /** read a number into name and set base */ protected def getNumber(): Unit = { diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 30c874c54225..3309db3e81c8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -361,7 +361,7 @@ object Parsers { accept(SEMI) } - /** Under -language:Scala2 or -old-syntax, flag + /** Under -language:Scala2Compat or -old-syntax, flag * * extends p1 with new p1 with t1 with * p2 p2 t2 @@ -369,7 +369,7 @@ object Parsers { * as a migration warning or error since that means something else under significant indentation. */ def checkNotWithAtEOL(): Unit = - if (in.isScala2Mode || in.oldSyntax) && in.isAfterLineEnd then + if (in.isScala2CompatMode || in.oldSyntax) && in.isAfterLineEnd then in.errorOrMigrationWarning("`with` cannot be followed by new line, place at beginning of next line instead") def rewriteNotice(additionalOption: String = "") = { @@ -446,7 +446,7 @@ object Parsers { ts.map(convertToParam(_)) case t: Typed => in.errorOrMigrationWarning( - em"parentheses are required around the parameter of a lambda${rewriteNotice("-language:Scala2")}", + em"parentheses are required around the parameter of a lambda${rewriteNotice("-language:Scala2Compat")}", t.span) patch(source, t.span.startPos, "(") patch(source, t.span.endPos, ")") @@ -1180,7 +1180,7 @@ object Parsers { in.errorOrMigrationWarning(em"""symbol literal '${in.name} is no longer supported, |use a string literal "${in.name}" or an application Symbol("${in.name}") instead, |or enclose in braces '{${in.name}} if you want a quoted expression.""") - if (in.isScala2Mode) { + if (in.isScala2CompatMode) { patch(source, Span(in.offset, in.offset + 1), "Symbol(\"") patch(source, Span(in.charOffset - 1), "\")") } @@ -1681,7 +1681,7 @@ object Parsers { * the initially parsed (...) region? */ def toBeContinued(altToken: Token): Boolean = - if in.token == altToken || in.isNewLine || in.isScala2Mode then + if in.token == altToken || in.isNewLine || in.isScala2CompatMode then false // a newline token means the expression is finished else if !in.canStartStatTokens.contains(in.token) || in.isLeadingInfixOperator(inConditional = true) @@ -1803,7 +1803,7 @@ object Parsers { in.errorOrMigrationWarning( i"""`do while ' is no longer supported, |use `while ({ ; }) ()' instead. - |${rewriteNotice("-language:Scala2")} + |${rewriteNotice("-language:Scala2Compat")} """) val start = in.skipToken() atSpan(start) { @@ -1812,7 +1812,7 @@ object Parsers { val whileStart = in.offset accept(WHILE) val cond = expr() - if (in.isScala2Mode) { + if (in.isScala2CompatMode) { patch(source, Span(start, start + 2), "while ({") patch(source, Span(whileStart, whileStart + 5), ";") cond match { @@ -2002,7 +2002,7 @@ object Parsers { in.errorOrMigrationWarning(s"This syntax is no longer supported; parameter needs to be enclosed in (...)") in.nextToken() val t = infixType() - if (false && in.isScala2Mode) { + if (false && in.isScala2CompatMode) { patch(source, Span(start), "(") patch(source, Span(in.lastOffset), ")") } @@ -3106,7 +3106,7 @@ object Parsers { val toInsert = if (in.token == LBRACE) s"$resultTypeStr =" else ": Unit " // trailing space ensures that `def f()def g()` works. - in.testScala2Mode(s"Procedure syntax no longer supported; `$toInsert' should be inserted here") && { + in.testScala2CompatMode(s"Procedure syntax no longer supported; `$toInsert' should be inserted here") && { patch(source, Span(in.lastOffset), toInsert) true } @@ -3120,7 +3120,7 @@ object Parsers { case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart) } - if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) + if (in.isScala2CompatMode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) atSpan(in.offset) { subPart(constrExpr) } @@ -3159,7 +3159,7 @@ object Parsers { TypeBoundsTree(EmptyTree, toplevelTyp()) else typedOpt() } - if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) + if (in.isScala2CompatMode) newLineOptWhenFollowedBy(LBRACE) val rhs = if (in.token == EQUALS) indentRegion(name) { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 24e334d7ffeb..7b9f2e8565dd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -142,7 +142,7 @@ object Scanners { /** A switch whether operators at the start of lines can be infix operators */ private[Scanners] var allowLeadingInfixOperators = true - val isScala2Mode: Boolean = ctx.scala2Setting + val isScala2CompatMode: Boolean = ctx.scala2CompatSetting val rewrite = ctx.settings.rewrite.value.isDefined val oldSyntax = ctx.settings.oldSyntax.value @@ -154,7 +154,7 @@ object Scanners { val noindentSyntax = ctx.settings.noindent.value || ctx.settings.oldSyntax.value - || isScala2Mode + || isScala2CompatMode val indentSyntax = (if (Config.defaultIndent) !noindentSyntax else ctx.settings.indent.value) || rewriteNoIndent @@ -199,12 +199,12 @@ object Scanners { private val commentBuf = new mutable.StringBuilder private def handleMigration(keyword: Token): Token = - if (!isScala2Mode) keyword + if (!isScala2CompatMode) keyword else if (scala3keywords.contains(keyword)) treatAsIdent() else keyword private def treatAsIdent() = { - testScala2Mode(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier") + testScala2CompatMode(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier") patch(source, Span(offset), "`") patch(source, Span(offset + name.length), "`") IDENTIFIER @@ -232,14 +232,14 @@ object Scanners { // Scala 2 compatibility /** Cannot use ctx.featureEnabled because accessing the context would force too much */ - def testScala2Mode(msg: String, span: Span = Span(offset)): Boolean = { - if (isScala2Mode) ctx.migrationWarning(msg, source.atSpan(span)) - isScala2Mode + def testScala2CompatMode(msg: String, span: Span = Span(offset)): Boolean = { + if (isScala2CompatMode) ctx.migrationWarning(msg, source.atSpan(span)) + isScala2CompatMode } /** A migration warning if in Scala-2 mode, an error otherwise */ def errorOrMigrationWarning(msg: String, span: Span = Span(offset)): Unit = - if (isScala2Mode) ctx.migrationWarning(msg, source.atSpan(span)) + if (isScala2CompatMode) ctx.migrationWarning(msg, source.atSpan(span)) else ctx.error(msg, source.atSpan(span)) // Get next token ------------------------------------------------------------ @@ -371,7 +371,7 @@ object Scanners { * - it does not follow a blank line, and * - it is followed on the same line by at least one ' ' * and a token that can start an expression. - * If a leading infix operator is found and -language:Scala2 or -old-syntax is set, + * If a leading infix operator is found and -language:Scala2Compat or -old-syntax is set, * emit a change warning. */ def isLeadingInfixOperator(inConditional: Boolean = true) = ( @@ -388,7 +388,7 @@ object Scanners { canStartExprTokens.contains(lookahead.token) } && { - if isScala2Mode || oldSyntax && !rewrite then + if isScala2CompatMode || oldSyntax && !rewrite then val (what, previous) = if inConditional then ("Rest of line", "previous expression in parentheses") else ("Line", "expression on the previous line") @@ -1009,10 +1009,10 @@ object Scanners { def isNestedEnd = token == RBRACE || token == OUTDENT def canStartStatTokens = - if isScala2Mode then canStartStatTokens2 else canStartStatTokens3 + if isScala2CompatMode then canStartStatTokens2 else canStartStatTokens3 def canStartExprTokens = - if isScala2Mode then canStartExprTokens2 else canStartExprTokens3 + if isScala2CompatMode then canStartExprTokens2 else canStartExprTokens3 // Literals ----------------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 809ea70a236f..445dc99f4488 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -229,12 +229,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextParents(tp.parents) ~~ "{...}" case JavaArrayType(elemtp) => toText(elemtp) ~ "[]" - case tp: AnnotatedType if homogenizedView => - // Positions of annotations in types are not serialized - // (they don't need to because we keep the original type tree with - // the original annotation anyway. Therefore, there will always be - // one version of the annotation tree that has the correct positions). - withoutPos(super.toText(tp)) case tp: SelectionProto => "?{ " ~ toText(tp.name) ~ (Str(" ") provided !tp.name.toSimpleName.last.isLetterOrDigit) ~ @@ -522,19 +516,19 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } - def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { + def recur(rhs: Tree, tparamsTxt: => Text, printMemberArgs: Boolean): Text = rhs match { case impl: Template => templateText(tree, impl) case rhs: TypeBoundsTree => typeDefText(tparamsTxt, toText(rhs)) - case LambdaTypeTree(tparams, body) => - recur(body, tparamsText(tparams)) + case LambdaTypeTree(tparams, body) if printMemberArgs => + recur(body, tparamsText(tparams), false) case rhs: TypeTree if isBounds(rhs.typeOpt) => typeDefText(tparamsTxt, toText(rhs)) case rhs => typeDefText(tparamsTxt, optText(rhs)(" = " ~ _)) } - recur(rhs, "") + recur(rhs, "", true) case Import(expr, selectors) => keywordText("import ") ~ importText(expr, selectors) case Export(expr, selectors) => diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 529cbd025a57..a01a9874ad77 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -144,7 +144,7 @@ trait Reporting { this: Context => } def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = - if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) + if (ctx.scala2CompatMode) migrationWarning(msg, pos) else error(msg, pos) def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = reporter.report { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index efa1e95b8106..287cf5fc73ad 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1931,7 +1931,7 @@ object messages { extends Message(UnapplyInvalidReturnTypeID) { val kind = "Type Mismatch" val addendum = - if (ctx.scala2Mode && unapplyName == nme.unapplySeq) + if (ctx.scala2CompatMode && unapplyName == nme.unapplySeq) "\nYou might want to try to rewrite the extractor to use `unapply` instead." else "" val msg = em"""| ${Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${Magenta("extractor")}.$addendum""" diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index 9b262d1a41d3..d2bb51755f72 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -237,6 +237,18 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Term_underlyingArgument(self: Term)(given Context): Term = self.underlyingArgument def Term_underlying(self: Term)(given Context): Term = self.underlying + def Term_etaExpand(term: Term): Term = term.tpe.widen match { + case mtpe: Types.MethodType if !mtpe.isParamDependent => + val closureResType = mtpe.resType match { + case t: Types.MethodType => t.toFunctionType() + case t => t + } + val closureTpe = Types.MethodType(mtpe.paramNames, mtpe.paramInfos, closureResType) + val closureMethod = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, closureTpe) + tpd.Closure(closureMethod, tss => Term_etaExpand(new tpd.TreeOps(term).appliedToArgs(tss.head))) + case _ => term + } + type Ref = tpd.RefTree def matchRef(tree: Tree)(given Context): Option[Ref] = tree match { @@ -1591,19 +1603,9 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend PickledQuotes.quotedTypeToTree(self) /** Convert `Term` to an `quoted.Expr[Any]` */ - def QuotedExpr_seal(self: Term)(given ctx: Context): scala.quoted.Expr[Any] = { - def etaExpand(term: Term): Term = term.tpe.widen match { - case mtpe: Types.MethodType if !mtpe.isParamDependent => - val closureResType = mtpe.resType match { - case t: Types.MethodType => t.toFunctionType() - case t => t - } - val closureTpe = Types.MethodType(mtpe.paramNames, mtpe.paramInfos, closureResType) - val closureMethod = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, closureTpe) - tpd.Closure(closureMethod, tss => etaExpand(new tpd.TreeOps(term).appliedToArgs(tss.head))) - case _ => term - } - new scala.internal.quoted.TastyTreeExpr(etaExpand(self), compilerId) + def QuotedExpr_seal(self: Term)(given ctx: Context): scala.quoted.Expr[Any] = self.tpe.widen match { + case _: Types.MethodType | _: Types.PolyType => throw new Exception("Cannot seal a partially applied Term. Try eta-expanding the term first.") + case _ => new scala.internal.quoted.TastyTreeExpr(self, compilerId) } /** Checked cast to a `quoted.Expr[U]` */ diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index df8a3f8ca449..5f72f9214584 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -740,11 +740,12 @@ object Erasure { override def typedAnnotated(tree: untpd.Annotated, pt: Type)(implicit ctx: Context): Tree = typed(tree.arg, pt) - override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], Context) = { val stats1 = if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats) else stats - super.typedStats(stats1, exprOwner).filter(!_.isEmpty) + val (stats2, finalCtx) = super.typedStats(stats1, exprOwner) + (stats2.filter(!_.isEmpty), finalCtx) } override def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 51a7e089e2bb..25c5842bbf47 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -46,7 +46,7 @@ class ExpandSAMs extends MiniPhase { tree case tpe => val tpe1 = checkRefinements(tpe, fn) - val Seq(samDenot) = tpe1.abstractTermMembers.filter(!_.symbol.isSuperAccessor) + val Seq(samDenot) = tpe1.possibleSamMethods cpy.Block(tree)(stats, AnonClass(tpe1 :: Nil, fn.symbol.asTerm :: Nil, samDenot.symbol.asTerm.name :: Nil)) } diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 465c44d45388..4d2f75239820 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -176,8 +176,9 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => constToLiteral(tree) override def transformIf(tree: If)(implicit ctx: Context): Tree = - tree.cond match { - case Literal(Constant(c: Boolean)) => if (c) tree.thenp else tree.elsep + tree.cond.tpe match { + case ConstantType(Constant(c: Boolean)) if isPureExpr(tree.cond) => + if (c) tree.thenp else tree.elsep case _ => tree } diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala index 289d6b91b501..295ce2b5761e 100644 --- a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -88,7 +88,7 @@ class NonLocalReturns extends MiniPhase { override def transformReturn(tree: Return)(implicit ctx: Context): Tree = if (isNonLocalReturn(tree)) { - if (!ctx.scala2Mode) + if (!ctx.scala2CompatMode) ctx.strictWarning("Non local returns are deprecated; use scala.util.control.NonLocalReturns instead", tree.sourcePos) nonLocalReturnThrow(tree.expr, tree.from.symbol).withSpan(tree.span) } diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index 711ac60daf90..c976599c7f19 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -13,7 +13,6 @@ import dotty.tools.dotc.core.NameKinds._ import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.tasty.TreePickler.Hole import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.util.Spans._ @@ -137,6 +136,8 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( case tp: ThisType => assert(checkSymLevel(tp.cls, tp, pos).isEmpty) mapOver(tp) + case tp: AnnotatedType => + derivedAnnotatedType(tp, apply(tp.parent), tp.annot) case _ => mapOver(tp) } @@ -167,7 +168,12 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( case tp1: SkolemType => isStaticPathOK(tp1.info) case _ => false - if (!sym.exists || levelOK(sym) || isStaticPathOK(tp)) + /* Is a reference to an `` method on a class with a static path */ + def isStaticNew(tp1: Type): Boolean = tp1 match + case tp1: TermRef => tp1.symbol.isConstructor && isStaticPathOK(tp1.prefix) + case _ => false + + if (!sym.exists || levelOK(sym) || isStaticPathOK(tp) || isStaticNew(tp)) None else if (!sym.isStaticOwner && !isClassRef) tryHeal(sym, tp, pos) diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 21e748ec594e..4b0b77205936 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -78,7 +78,7 @@ class Pickler extends Phase { pickled.iterator.grouped(10).toList.zipWithIndex.map { case (row, i) => s"${i}0: ${row.mkString(" ")}" } - + // println(i"rawBytes = \n$rawBytes%\n%") // DEBUG if (pickling ne noPrinter) { println(i"**** pickled info of $cls") diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index ceeff4f42ddb..b7c658c51ef6 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -157,6 +157,8 @@ class ReifyQuotes extends MacroTransform { val tagDef = tagDefCache.getOrElseUpdate(prefix.symbol, mkTagSymbolAndAssignType(prefix)) tagDef.symbol.typeRef } + case AnnotatedType(parent, _) => + apply(parent) // Only keep the Annotated tree case _ => mapOver(tp) } @@ -263,7 +265,7 @@ class ReifyQuotes extends MacroTransform { assert(level == 1, "unexpected top splice outside quote") val (body1, quotes) = nested(isQuote = false).splitSplice(body)(spliceContext) val tpe = outer.embedded.getHoleType(body, splice) - val hole = makeHole(body1, quotes, tpe).withSpan(splice.span) + val hole = makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) // We do not place add the inline marker for trees that where lifted as they come from the same file as their // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. @@ -378,9 +380,9 @@ class ReifyQuotes extends MacroTransform { /** Register `body` as an `embedded` quote or splice * and return a hole with `splices` as arguments and the given type `tpe`. */ - private def makeHole(body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { + private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { val idx = embedded.addTree(body, NoSymbol) - Hole(idx, splices).withType(tpe).asInstanceOf[Hole] + Hole(isTermHole, idx, splices).withType(tpe).asInstanceOf[Hole] } override def transform(tree: Tree)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index d20105ae2586..73af47c275bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -434,9 +434,9 @@ class TreeChecker extends Phase with SymTransformer { } } - override def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type)(implicit ctx: Context): CaseDef = + override def typedCase(tree: untpd.CaseDef, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context): CaseDef = withPatSyms(tpd.patVars(tree.pat.asInstanceOf[tpd.Tree])) { - super.typedCase(tree, selType, pt) + super.typedCase(tree, sel, selType, pt) } override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = { @@ -466,7 +466,7 @@ class TreeChecker extends Phase with SymTransformer { * is that we should be able to pull out an expression as an initializer * of a helper value without having to do a change owner traversal of the expression. */ - override def typedStats(trees: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + override def typedStats(trees: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], Context) = { for (tree <- trees) tree match { case tree: untpd.DefTree => checkOwner(tree) case _: untpd.Thicket => assert(false, i"unexpanded thicket $tree in statement sequence $trees%\n%") diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index cc3641f1c94f..2f04f7f17a8f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -22,6 +22,7 @@ import NameKinds.DefaultGetterName import ProtoTypes._ import Inferencing._ import transform.TypeUtils._ +import Nullables.given import collection.mutable import config.Printers.{overload, typr, unapp} @@ -864,8 +865,9 @@ trait Applications extends Compatibility { if (proto.allArgTypesAreCurrent()) new ApplyToTyped(tree, fun1, funRef, proto.unforcedTypedArgs, pt) else - new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) - convertNewGenericArray(app.result) + new ApplyToUntyped(tree, fun1, funRef, proto, pt)( + given fun1.nullableInArgContext(given argCtx(tree))) + convertNewGenericArray(app.result).computeNullable() case _ => handleUnexpectedFunType(tree, fun1) } diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 9b4eef28369a..0fd7174dc774 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -11,6 +11,7 @@ import Constants._ import Names._ import StdNames._ import Contexts._ +import Nullables.{CompareNull, TrackedRef} object ConstFold { @@ -19,15 +20,17 @@ object ConstFold { /** If tree is a constant operation, replace with result. */ def apply[T <: Tree](tree: T)(implicit ctx: Context): T = finish(tree) { tree match { + case CompareNull(TrackedRef(ref), testEqual) + if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) => + // TODO maybe drop once we have general Nullability? + Constant(!testEqual) case Apply(Select(xt, op), yt :: Nil) => - xt.tpe.widenTermRefExpr.normalized match { + xt.tpe.widenTermRefExpr.normalized match case ConstantType(x) => - yt.tpe.widenTermRefExpr match { + yt.tpe.widenTermRefExpr match case ConstantType(y) => foldBinop(op, x, y) case _ => null - } case _ => null - } case Select(xt, op) => xt.tpe.widenTermRefExpr match { case ConstantType(x) => foldUnop(op, x) diff --git a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index a7fe1ac69b9c..7bedf6033b45 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -33,7 +33,7 @@ object Docstrings { expandComment(sym).map { expanded => val typedUsecases = expanded.usecases.map { usecase => ctx.typer.enterSymbol(ctx.typer.createSymbol(usecase.untpdCode)) - ctx.typer.typedStats(usecase.untpdCode :: Nil, owner) match { + ctx.typer.typedStats(usecase.untpdCode :: Nil, owner)._1 match { case List(df: tpd.DefDef) => usecase.typed(df) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index af30cdfa7883..a2fcd6295143 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -167,7 +167,7 @@ object ErrorReporting { } def rewriteNotice: String = - if (ctx.scala2Mode) "\nThis patch can be inserted automatically under -rewrite." + if (ctx.scala2CompatMode) "\nThis patch can be inserted automatically under -rewrite." else "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 7a41dcd5d352..4e903d519af8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -141,9 +141,9 @@ object Implicits { // The reason for leaving out `Predef_conforms` is that we know it adds // nothing since it only relates subtype with supertype. // - // We keep the old behavior under -language:Scala2. + // We keep the old behavior under -language:Scala2Compat. val isFunctionInS2 = - ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms + ctx.scala2CompatMode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass) // An implementation of <:< counts as a view val isConforms = tpw.derivesFrom(defn.SubTypeClass) @@ -265,7 +265,7 @@ object Implicits { */ override val level: Int = if (outerImplicits == null) 1 - else if (ctx.scala2Mode || + else if (ctx.scala2CompatMode || (ctx.owner eq outerImplicits.ctx.owner) && (ctx.scope eq outerImplicits.ctx.scope) && !refs.head.implicitName.is(LazyImplicitName)) outerImplicits.level @@ -567,7 +567,7 @@ trait ImplicitRunInfo { addPath(pre.cls.sourceModule.termRef) case pre: TermRef => if (pre.symbol.is(Package)) { - if (ctx.scala2Mode) { + if (ctx.scala2CompatMode) { addCompanion(pre, pre.member(nme.PACKAGE).symbol) addPath(pre.prefix) } @@ -1323,7 +1323,7 @@ trait Implicits { self: Typer => case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto if (deepPt ne pt) inferImplicit(deepPt, argument, span) - else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) + else if (ctx.scala2CompatMode && !ctx.mode.is(Mode.OldOverloadingResolution)) inferImplicit(pt, argument, span)(ctx.addMode(Mode.OldOverloadingResolution)) match { case altResult: SearchSuccess => ctx.migrationWarning( @@ -1502,7 +1502,7 @@ trait Implicits { self: Typer => negateIfNot(tryImplicit(cand, contextual)) match { case fail: SearchFailure => if (fail.isAmbiguous) - if (ctx.scala2Mode) { + if (ctx.scala2CompatMode) { val result = rank(remaining, found, NoMatchingImplicitsFailure :: rfailures) if (result.isSuccess) warnAmbiguousNegation(fail.reason.asInstanceOf[AmbiguousImplicits]) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 632b5be4ae53..9b04ce1e8b78 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -24,6 +24,7 @@ import ErrorReporting.errorTree import dotty.tools.dotc.tastyreflect.ReflectionImpl import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile, SourcePosition} import dotty.tools.dotc.parsing.Parsers.Parser +import Nullables.given import collection.mutable import reporting.trace @@ -1064,10 +1065,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { errorTree(tree, em"""cannot reduce inline if | its condition ${tree.cond} | is not a constant value""") - else { + else + cond1.computeNullableDeeply() val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) super.typedIf(if1, pt) - } } override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e32360e299f5..abc607a94374 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -559,7 +559,9 @@ class Namer { typer: Typer => case _ => () } - /** Create top-level symbols for statements and enter them into symbol table */ + /** Create top-level symbols for statements and enter them into symbol table + * @return A context that reflects all imports in `stats`. + */ def index(stats: List[Tree])(implicit ctx: Context): Context = { // module name -> (stat, moduleCls | moduleVal) @@ -1151,7 +1153,7 @@ class Namer { typer: Typer => 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 ctx.scala2CompatMode => "\n(Note that inheriting a class of the same name is no longer allowed)" case _ => "" } @@ -1345,11 +1347,10 @@ class Namer { typer: Typer => // We also drop the @Repeated annotation here to avoid leaking it in method result types // (see run/inferred-repeated-result). def widenRhs(tp: Type): Type = { - val tp1 = tp.widenTermRefExpr match { + val tp1 = tp.widenTermRefExpr.simplified match case ctp: ConstantType if isInlineVal => ctp case ref: TypeRef if ref.symbol.is(ModuleClass) => tp - case _ => tp.widenUnion - } + case tp => tp.widenUnion tp1.dropRepeatedAnnot } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala new file mode 100644 index 000000000000..d3b2d146d9c1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -0,0 +1,360 @@ +package dotty.tools +package dotc +package typer + +import core._ +import Types._, Contexts._, Symbols._, Decorators._, Constants._ +import annotation.tailrec +import StdNames.nme +import util.Property +import Names.Name +import util.Spans.Span +import Flags.Mutable +import collection.mutable + +/** Operations for implementing a flow analysis for nullability */ +object Nullables with + import ast.tpd._ + + /** A set of val or var references that are known to be not null, plus a set of + * variable references that are not known (anymore) to be not null + */ + case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]) + assert((asserted & retracted).isEmpty) + + def isEmpty = this eq NotNullInfo.empty + + def retractedInfo = NotNullInfo(Set(), retracted) + + /** The sequential combination with another not-null info */ + def seq(that: NotNullInfo): NotNullInfo = + if this.isEmpty then that + else if that.isEmpty then this + else NotNullInfo( + this.asserted.union(that.asserted).diff(that.retracted), + this.retracted.union(that.retracted).diff(that.asserted)) + + /** The alternative path combination with another not-null info. Used to merge + * the nullability info of the two branches of an if. + */ + def alt(that: NotNullInfo): NotNullInfo = + NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) + + object NotNullInfo with + val empty = new NotNullInfo(Set(), Set()) + def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = + if asserted.isEmpty && retracted.isEmpty then empty + else new NotNullInfo(asserted, retracted) + end NotNullInfo + + /** A pair of not-null sets, depending on whether a condition is `true` or `false` */ + case class NotNullConditional(ifTrue: Set[TermRef], ifFalse: Set[TermRef]) with + def isEmpty = this eq NotNullConditional.empty + + object NotNullConditional with + val empty = new NotNullConditional(Set(), Set()) + def apply(ifTrue: Set[TermRef], ifFalse: Set[TermRef]): NotNullConditional = + if ifTrue.isEmpty && ifFalse.isEmpty then empty + else new NotNullConditional(ifTrue, ifFalse) + end NotNullConditional + + /** An attachment that represents conditional flow facts established + * by this tree, which represents a condition. + */ + private[typer] val NNConditional = Property.StickyKey[NotNullConditional] + + /** An attachment that represents unconditional flow facts established + * by this tree. + */ + private[typer] val NNInfo = Property.StickyKey[NotNullInfo] + + /** An extractor for null comparisons */ + object CompareNull with + + /** Matches one of + * + * tree == null, tree eq null, null == tree, null eq tree + * tree != null, tree ne null, null != tree, null ne tree + * + * The second boolean result is true for equality tests, false for inequality tests + */ + def unapply(tree: Tree)(given Context): Option[(Tree, Boolean)] = tree match + case Apply(Select(l, _), Literal(Constant(null)) :: Nil) => + testSym(tree.symbol, l) + case Apply(Select(Literal(Constant(null)), _), r :: Nil) => + testSym(tree.symbol, r) + case _ => + None + + private def testSym(sym: Symbol, operand: Tree)(given Context) = + if sym == defn.Any_== || sym == defn.Object_eq then Some((operand, true)) + else if sym == defn.Any_!= || sym == defn.Object_ne then Some((operand, false)) + else None + + end CompareNull + + /** An extractor for null-trackable references */ + object TrackedRef + def unapply(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match + case ref: TermRef if isTracked(ref) => Some(ref) + case _ => None + end TrackedRef + + /** Is given reference tracked for nullability? + * This is the case if the reference is a path to an immutable val, or if it refers + * to a local mutable variable where all assignments to the variable are _reachable_ + * (in the sense of how it is defined in assignmentSpans). + */ + def isTracked(ref: TermRef)(given Context) = + ref.isStable + || { val sym = ref.symbol + sym.is(Mutable) + && sym.owner.isTerm + && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod + && sym.span.exists + && curCtx.compilationUnit != null // could be null under -Ytest-pickler + && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) + } + + /** The nullability context to be used after a case that matches pattern `pat`. + * If `pat` is `null`, this will assert that the selector `sel` is not null afterwards. + */ + def afterPatternContext(sel: Tree, pat: Tree)(given ctx: Context) = (sel, pat) match + case (TrackedRef(ref), Literal(Constant(null))) => ctx.addNotNullRefs(Set(ref)) + case _ => ctx + + /** The nullability context to be used for the guard and rhs of a case with + * given pattern `pat`. If the pattern can only match non-null values, this + * will assert that the selector `sel` is not null in these regions. + */ + def caseContext(sel: Tree, pat: Tree)(given ctx: Context): Context = sel match + case TrackedRef(ref) if matchesNotNull(pat) => ctx.addNotNullRefs(Set(ref)) + case _ => ctx + + private def matchesNotNull(pat: Tree)(given Context): Boolean = pat match + case _: Typed | _: UnApply => true + case Alternative(pats) => pats.forall(matchesNotNull) + // TODO: Add constant pattern if the constant type is not nullable + case _ => false + + given (infos: List[NotNullInfo]) + + /** Do the current not-null infos imply that `ref` is not null? + * Not-null infos are as a history where earlier assertions and retractions replace + * later ones (i.e. it records the assignment history in reverse, with most recent first) + */ + @tailrec def impliesNotNull(ref: TermRef): Boolean = infos match + case info :: infos1 => + if info.asserted.contains(ref) then true + else if info.retracted.contains(ref) then false + else impliesNotNull(infos1)(ref) + case _ => + false + + /** Add `info` as the most recent entry to the list of null infos. Assertions + * or retractions in `info` supersede infos in existing entries of `infos`. + */ + def extendWith(info: NotNullInfo) = + if info.isEmpty + || info.asserted.forall(infos.impliesNotNull(_)) + && !info.retracted.exists(infos.impliesNotNull(_)) + then infos + else info :: infos + + given (tree: Tree) + + /* The `tree` with added nullability attachment */ + def withNotNullInfo(info: NotNullInfo): tree.type = + if !info.isEmpty then tree.putAttachment(NNInfo, info) + tree + + /* The nullability info of `tree` */ + def notNullInfo(given Context): NotNullInfo = + stripInlined(tree).getAttachment(NNInfo) match + case Some(info) if !curCtx.erasedTypes => info + case _ => NotNullInfo.empty + + /* The nullability info of `tree`, assuming it is a condition that evaluates to `c` */ + def notNullInfoIf(c: Boolean)(given Context): NotNullInfo = + val cond = tree.notNullConditional + if cond.isEmpty then tree.notNullInfo + else tree.notNullInfo.seq(NotNullInfo(if c then cond.ifTrue else cond.ifFalse, Set())) + + /** The paths that are known to be not null if the condition represented + * by `tree` yields `true` or `false`. Two empty sets if `tree` is not + * a condition. + */ + def notNullConditional(given Context): NotNullConditional = + stripBlock(tree).getAttachment(NNConditional) match + case Some(cond) if !curCtx.erasedTypes => cond + case _ => NotNullConditional.empty + + /** The current context augmented with nullability information of `tree` */ + def nullableContext(given Context): Context = + val info = tree.notNullInfo + if info.isEmpty then curCtx else curCtx.addNotNullInfo(info) + + /** The current context augmented with nullability information, + * assuming the result of the condition represented by `tree` is the same as + * the value of `c`. + */ + def nullableContextIf(c: Boolean)(given Context): Context = + val info = tree.notNullInfoIf(c) + if info.isEmpty then curCtx else curCtx.addNotNullInfo(info) + + /** The context to use for the arguments of the function represented by `tree`. + * This is the current context, augmented with nullability information + * of the left argument, if the application is a boolean `&&` or `||`. + */ + def nullableInArgContext(given Context): Context = tree match + case Select(x, _) if !curCtx.erasedTypes => + if tree.symbol == defn.Boolean_&& then x.nullableContextIf(true) + else if tree.symbol == defn.Boolean_|| then x.nullableContextIf(false) + else curCtx + case _ => curCtx + + /** The `tree` augmented with nullability information in an attachment. + * The following operations lead to nullability info being recorded: + * + * 1. Null tests using `==`, `!=`, `eq`, `ne`, if the compared entity is + * a path (i.e. a stable TermRef) + * 2. Boolean &&, ||, ! + */ + def computeNullable()(given Context): tree.type = + def setConditional(ifTrue: Set[TermRef], ifFalse: Set[TermRef]) = + tree.putAttachment(NNConditional, NotNullConditional(ifTrue, ifFalse)) + if !curCtx.erasedTypes && analyzedOps.contains(tree.symbol.name.toTermName) then + tree match + case CompareNull(TrackedRef(ref), testEqual) => + if testEqual then setConditional(Set(), Set(ref)) + else setConditional(Set(ref), Set()) + case Apply(Select(x, _), y :: Nil) => + val xc = x.notNullConditional + val yc = y.notNullConditional + if !(xc.isEmpty && yc.isEmpty) then + if tree.symbol == defn.Boolean_&& then + setConditional(xc.ifTrue | yc.ifTrue, xc.ifFalse & yc.ifFalse) + else if tree.symbol == defn.Boolean_|| then + setConditional(xc.ifTrue & yc.ifTrue, xc.ifFalse | yc.ifFalse) + case Select(x, _) if tree.symbol == defn.Boolean_! => + val xc = x.notNullConditional + if !xc.isEmpty then + setConditional(xc.ifFalse, xc.ifTrue) + case _ => + tree + + /** Compute nullability information for this tree and all its subtrees */ + def computeNullableDeeply()(given Context): Unit = + new TreeTraverser { + def traverse(tree: Tree)(implicit ctx: Context) = + traverseChildren(tree) + tree.computeNullable() + }.traverse(tree) + + given (tree: Assign) + def computeAssignNullable()(given Context): tree.type = tree.lhs match + case TrackedRef(ref) => + tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) // TODO: refine with nullability type info + case _ => tree + + private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) + + /** A map from (name-) offsets of all local variables in this compilation unit + * that can be tracked for being not null to the list of spans of assignments + * to these variables. A variable can be tracked if it has only reachable assignments + * An assignment is reachable if the path of tree nodes between the block enclosing + * the variable declaration to the assignment consists only of if-expressions, + * while-expressions, block-expressions and type-ascriptions. + * Only reachable assignments are handled correctly in the nullability analysis. + * Therefore, variables with unreachable assignments can be assumed to be not-null + * only if their type asserts it. + * + * Note: we track the local variables through their offset and not through their name + * because of shadowing. + */ + def assignmentSpans(given Context): Map[Int, List[Span]] = + import ast.untpd._ + + object populate extends UntypedTreeTraverser with + + /** The name offsets of variables that are tracked */ + var tracked: Map[Int, List[Span]] = Map.empty + + /** Map the names of potentially trackable candidate variables in scope to the spans + * of their reachable assignments + */ + val candidates = mutable.Map[Name, List[Span]]() + + /** An assignment to a variable that's not in reachable makes the variable + * ineligible for tracking + */ + var reachable: Set[Name] = Set.empty + + def traverse(tree: Tree)(implicit ctx: Context) = + val savedReachable = reachable + tree match + case Block(stats, expr) => + var shadowed: Set[(Name, List[Span])] = Set.empty + for case (stat: ValDef) <- stats if stat.mods.is(Mutable) do + for prevSpans <- candidates.put(stat.name, Nil) do + shadowed += (stat.name -> prevSpans) + reachable += stat.name + traverseChildren(tree) + for case (stat: ValDef) <- stats if stat.mods.is(Mutable) do + for spans <- candidates.remove(stat.name) do + tracked += (stat.nameSpan.start -> spans) // candidates that survive until here are tracked + candidates ++= shadowed + case Assign(Ident(name), rhs) => + candidates.get(name) match + case Some(spans) => + if reachable.contains(name) then candidates(name) = tree.span :: spans + else candidates -= name + case None => + traverseChildren(tree) + case _: (If | WhileDo | Typed) => + traverseChildren(tree) // assignments to candidate variables are OK here ... + case _ => + reachable = Set.empty // ... but not here + traverseChildren(tree) + reachable = savedReachable + + populate.traverse(curCtx.compilationUnit.untpdTree) + populate.tracked + end assignmentSpans + + /** The initial context to be used for a while expression with given span. + * In this context, all variables that are assigned within the while expression + * have their nullability status retracted, i.e. are not known to be not null. + * While necessary for soundness, this scheme loses precision: Even if + * the initial state of the variable is not null and all assignments to the variable + * in the while expression are also known to be not null, the variable is still + * assumed to be potentially null. The loss of precision is unavoidable during + * normal typing, since we can only do a linear traversal which does not allow + * a fixpoint computation. But it could be mitigated as follows: + * + * - initially, use `whileContext` as computed here + * - when typechecking the while, delay all errors due to a variable being potentially null + * - afterwards, if there are such delayed errors, run the analysis again with + * as a fixpoint computation, reporting all previously delayed errors that remain. + * + * The following code would produce an error in the current analysis, but not in the + * refined analysis: + * + * class Links(val elem: T, val next: Links | Null) + * + * var xs: Links | Null = Links(1, null) + * var ys: Links | Null = xs + * while xs != null + * ys = Links(xs.elem, ys.next) // error in unrefined: ys is potentially null here + * xs = xs.next + */ + def whileContext(whileSpan: Span)(given Context): Context = + def isRetracted(ref: TermRef): Boolean = + val sym = ref.symbol + sym.span.exists + && assignmentSpans.getOrElse(sym.span.start, Nil).exists(whileSpan.contains(_)) + && curCtx.notNullInfos.impliesNotNull(ref) + val retractedVars = curCtx.notNullInfos.flatMap(_.asserted.filter(isRetracted)).toSet + curCtx.addNotNullInfo(NotNullInfo(Set(), retractedVars)) + +end Nullables diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 3de2dee72abe..57bd34f2f298 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -280,7 +280,7 @@ object RefChecks { member.name.is(DefaultGetterName) || // default getters are not checked for compatibility memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || - ctx.testScala2Mode(overrideErrorMsg("no longer has compatible type"), + ctx.testScala2CompatMode(overrideErrorMsg("no longer has compatible type"), (if (member.owner == clazz) member else clazz).sourcePos)) catch { case ex: MissingType => @@ -351,7 +351,7 @@ object RefChecks { // Also excluded under Scala2 mode are overrides of default methods of Java traits. if (autoOverride(member) || other.owner.isAllOf(JavaInterface) && - ctx.testScala2Mode("`override' modifier required when a Java 8 default method is re-implemented", member.sourcePos)) + ctx.testScala2CompatMode("`override' modifier required when a Java 8 default method is re-implemented", member.sourcePos)) member.setFlag(Override) else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other)) () // OK, don't complain about type aliases which are equal @@ -383,7 +383,7 @@ object RefChecks { else if (member.is(ModuleVal) && !other.isRealMethod && !other.isOneOf(Deferred | Lazy)) overrideError("may not override a concrete non-lazy value") else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy) && - !ctx.testScala2Mode(overrideErrorMsg("may not override a non-lazy value"), member.sourcePos)) + !ctx.testScala2CompatMode(overrideErrorMsg("may not override a non-lazy value"), member.sourcePos)) overrideError("may not override a non-lazy value") else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) overrideError("must be declared lazy to override a lazy value") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 505da8883248..b8e262d2d239 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -40,6 +40,7 @@ import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages} import transform.SymUtils._ import transform.TypeUtils._ import reporting.trace +import Nullables.{NotNullInfo, given} object Typer { @@ -78,8 +79,6 @@ object Typer { */ private[typer] val HiddenSearchFailure = new Property.Key[SearchFailure] } - - class Typer extends Namer with TypeAssigner with Applications @@ -301,7 +300,7 @@ class Typer extends Namer if (!curOwner.is(Package) || isDefinedInCurrentUnit(defDenot)) result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry else { - if (ctx.scala2Mode && !foundUnderScala2.exists) + if (ctx.scala2CompatMode && !foundUnderScala2.exists) foundUnderScala2 = checkNewOrShadowed(found, Definition, scala2pkg = true) if (defDenot.symbol.is(Package)) result = checkNewOrShadowed(previous orElse found, PackageClause) @@ -382,8 +381,8 @@ class Typer extends Namer if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { ctx.migrationWarning( ex"""Name resolution will change. - | currently selected : $foundUnderScala2 - | in the future, without -language:Scala2: $found""", tree.sourcePos) + | currently selected : $foundUnderScala2 + | in the future, without -language:Scala2Compat: $found""", tree.sourcePos) found = foundUnderScala2 } found @@ -453,6 +452,7 @@ class Typer extends Namer def typeSelectOnTerm(implicit ctx: Context): Tree = typedSelect(tree, pt, typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))) + .computeNullable() def typeSelectOnType(qual: untpd.Tree)(implicit ctx: Context) = typedSelect(untpd.cpy.Select(tree)(qual, tree.name.toTypeName), pt) @@ -634,6 +634,7 @@ class Typer extends Namer else if (isWildcard) tree.expr.withType(tpt.tpe) else typed(tree.expr, tpt.tpe.widenSkolem) assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) + .withNotNullInfo(expr1.notNullInfo) } if (untpd.isWildcardStarArg(tree)) { @@ -679,7 +680,7 @@ class Typer extends Namer * @pre We are in pattern-matching mode (Mode.Pattern) */ def tryWithClassTag(tree: Typed, pt: Type)(implicit ctx: Context): Tree = tree.tpt.tpe.dealias match { - case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper => + case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper && !(tref =:= pt) => require(ctx.mode.is(Mode.Pattern)) inferImplicit(defn.ClassTagClass.typeRef.appliedTo(tref), EmptyTree, tree.tpt.span)(ctx.retractMode(Mode.Pattern)) match { @@ -732,6 +733,7 @@ class Typer extends Namer val lhsBounds = TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner) assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound))) + .computeAssignNullable() } else { val pre = ref.prefix @@ -754,15 +756,19 @@ 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): (List[tpd.Tree], Context) = + index(stats) + typedStats(stats, ctx.owner) def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context): Tree = { val localCtx = ctx.retractMode(Mode.Pattern) - val (exprCtx, stats1) = typedBlockStats(tree.stats)(given localCtx) - val expr1 = typedExpr(tree.expr, pt.dropIfProto)(exprCtx) + val (stats1, exprCtx) = typedBlockStats(tree.stats)(given localCtx) + val expr1 = typedExpr(tree.expr, pt.dropIfProto)(given exprCtx) ensureNoLocalRefs( - cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1)) + cpy.Block(tree)(stats1, expr1) + .withType(expr1.tpe) + .withNotNullInfo(stats1.foldRight(expr1.notNullInfo)(_.notNullInfo.seq(_))), + pt, localSyms(stats1)) } def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = { @@ -803,21 +809,31 @@ class Typer extends Namer } } - def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = { - if (tree.isInline) checkInInlineContext("inline if", tree.posd) + def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = + if tree.isInline then checkInInlineContext("inline if", tree.posd) val cond1 = typed(tree.cond, defn.BooleanType) - if (tree.elsep.isEmpty) { - val thenp1 = typed(tree.thenp, defn.UnitType) - 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 result = + if tree.elsep.isEmpty then + val thenp1 = typed(tree.thenp, defn.UnitType)(given cond1.nullableContextIf(true)) + 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) { + val thenp0 = typed(tree.thenp, pt.dropIfProto)(given cond1.nullableContextIf(true)) + val elsep0 = typed(tree.elsep, pt.dropIfProto)(given cond1.nullableContextIf(false)) + thenp0 :: elsep0 :: Nil + } + assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1) + + def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) + def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) + result.withNotNullInfo( + if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo + else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo + else thenPathInfo.alt(elsePathInfo) + ) + end typedIf /** Decompose function prototype into a list of parameter prototypes and a result prototype * tree, using WildcardTypes where a type is not known. @@ -1124,13 +1140,18 @@ class Typer extends Namer // Overridden in InlineTyper for inline matches def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(implicit ctx: Context): Tree = { - val cases1 = harmonic(harmonize, pt)(typedCases(cases, wideSelType, pt.dropIfProto)) + val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] assignType(cpy.Match(tree)(sel, cases1), sel, cases1) } - def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context): List[CaseDef] = - cases.mapconserve(typedCase(_, selType, pt)) + def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(implicit ctx: Context): List[CaseDef] = + var caseCtx = ctx + cases.mapconserve { cas => + val case1 = typedCase(cas, sel, wideSelType, pt)(given caseCtx) + caseCtx = Nullables.afterPatternContext(sel, case1.pat) + case1 + } /** - strip all instantiated TypeVars from pattern types. * run/reducable.scala is a test case that shows stripping typevars is necessary. @@ -1157,7 +1178,7 @@ class Typer extends Namer } /** Type a case. */ - def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type)(implicit ctx: Context): CaseDef = { + def typedCase(tree: untpd.CaseDef, sel: Tree, wideSelType: Type, pt: Type)(implicit ctx: Context): CaseDef = { val originalCtx = ctx val gadtCtx: Context = ctx.fresh.setFreshGADTBounds @@ -1170,8 +1191,10 @@ class Typer extends Namer assignType(cpy.CaseDef(tree)(pat1, guard1, body1), pat1, body1) } - val pat1 = typedPattern(tree.pat, selType)(gadtCtx) - caseRest(pat1)(gadtCtx.fresh.setNewScope) + val pat1 = typedPattern(tree.pat, wideSelType)(gadtCtx) + caseRest(pat1)( + given Nullables.caseContext(sel, pat1)( + given gadtCtx.fresh.setNewScope)) } def typedLabeled(tree: untpd.Labeled)(implicit ctx: Context): Labeled = { @@ -1191,7 +1214,6 @@ class Typer extends Namer caseRest(ctx.fresh.setFreshGADTBounds.setNewScope) } - def typedReturn(tree: untpd.Return)(implicit ctx: Context): Return = { def returnProto(owner: Symbol, locals: Scope): Type = if (owner.isConstructor) defn.UnitType @@ -1238,17 +1260,19 @@ class Typer extends Namer } def typedWhileDo(tree: untpd.WhileDo)(implicit ctx: Context): Tree = { + given whileCtx: Context = Nullables.whileContext(tree.span)(given ctx) val cond1 = if (tree.cond eq EmptyTree) EmptyTree else typed(tree.cond, defn.BooleanType) - val body1 = typed(tree.body, defn.UnitType) + val body1 = typed(tree.body, defn.UnitType)(given cond1.nullableContextIf(true)) assignType(cpy.WhileDo(tree)(cond1, body1)) + .withNotNullInfo(body1.notNullInfo.retractedInfo.seq(cond1.notNullInfoIf(false))) } def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = { val expr2 :: cases2x = harmonic(harmonize, pt) { val expr1 = typed(tree.expr, pt.dropIfProto) - val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.dropIfProto) + val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto) expr1 :: cases1 } val finalizer1 = typed(tree.finalizer, defn.UnitType) @@ -1291,7 +1315,7 @@ class Typer extends Namer } def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree = { - val (exprCtx, bindings1) = typedBlockStats(tree.bindings) + val (bindings1, exprCtx) = typedBlockStats(tree.bindings) val expansion1 = typed(tree.expansion, pt)(inlineContext(tree.call)(exprCtx)) assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), bindings1, expansion1) @@ -1530,6 +1554,16 @@ class Typer extends Namer typed(annot, defn.AnnotationClass.typeRef) def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = { + sym.infoOrCompleter match + case completer: Namer#Completer + if completer.creationContext.notNullInfos ne ctx.notNullInfos => + // The RHS of a val def should know about not null facts established + // in preceding statements (unless the ValDef is completed ahead of time, + // then it is impossible). + vdef.symbol.info = Completer(completer.original)( + given completer.creationContext.withNotNullInfos(ctx.notNullInfos)) + case _ => + val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym) @@ -1728,7 +1762,7 @@ class Typer extends Namer else { val dummy = localDummy(cls, impl) val body1 = addAccessorDefs(cls, - typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol))) + typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol))._1) checkNoDoubleDeclaration(cls) val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1) @@ -1848,9 +1882,9 @@ class Typer extends Namer case pid1: RefTree if pkg.exists => if (!pkg.is(Package)) ctx.error(PackageNameAlreadyDefined(pkg), tree.sourcePos) val packageCtx = ctx.packageContext(tree, pkg) - var stats1 = typedStats(tree.stats, pkg.moduleClass)(packageCtx) + var stats1 = typedStats(tree.stats, pkg.moduleClass)(packageCtx)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))(packageCtx)._2 + stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))(packageCtx)._1 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) case _ => // Package will not exist if a duplicate type has already been entered, see `tests/neg/1708.scala` @@ -1909,7 +1943,7 @@ class Typer extends Namer case _ => val recovered = typed(qual)(ctx.fresh.setExploreTyperState()) ctx.errorOrMigrationWarning(OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen), tree.sourcePos) - if (ctx.scala2Mode) { + if (ctx.scala2CompatMode) { // Under -rewrite, patch `x _` to `(() => x)` patch(Span(tree.span.start), "(() => ") patch(Span(qual.span.end, tree.span.end), ")") @@ -1930,7 +1964,7 @@ class Typer extends Namer else s"use `$prefix$suffix` instead" ctx.errorOrMigrationWarning(i"""The syntax ` _` is no longer supported; |you can $remedy""", tree.sourcePos) - if (ctx.scala2Mode) { + if (ctx.scala2CompatMode) { patch(Span(tree.span.start), prefix) patch(Span(qual.span.end, tree.span.end), suffix) } @@ -2167,11 +2201,12 @@ class Typer extends Namer def typedTrees(trees: List[untpd.Tree])(implicit ctx: Context): List[Tree] = trees mapconserve (typed(_)) - def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): (List[Tree], Context) = { val buf = new mutable.ListBuffer[Tree] val enumContexts = new mutable.HashMap[Symbol, Context] + val initialNotNullInfos = ctx.notNullInfos // 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])(implicit ctx: Context): (List[Tree], Context) = stats match { case (imp: untpd.Import) :: rest => val imp1 = typed(imp) buf += imp1 @@ -2181,7 +2216,14 @@ class Typer extends Namer case Some(xtree) => traverse(xtree :: rest) case none => - typed(mdef) match { + val defCtx = mdef match + // Keep preceding not null facts in the current context only if `mdef` + // cannot be executed out-of-sequence. + case _: ValDef if !mdef.mods.is(Lazy) && ctx.owner.isTerm => + ctx // all preceding statements will have been executed in this case + case _ => + ctx.withNotNullInfos(initialNotNullInfos) + typed(mdef)(given defCtx) 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 @@ -2206,9 +2248,9 @@ class Typer extends Namer val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner)) checkStatementPurity(stat1)(stat, exprOwner) buf += stat1 - traverse(rest) + traverse(rest)(given stat1.nullableContext) case nil => - buf.toList + (buf.toList, ctx) } val localCtx = { val exprOwnerOpt = if (exprOwner == ctx.owner) None else Some(exprOwner) @@ -2225,9 +2267,10 @@ class Typer extends Namer case _ => stat } - val stats1 = traverse(stats)(localCtx).mapConserve(finalize) + val (stats0, finalCtx) = traverse(stats)(localCtx) + val stats1 = stats0.mapConserve(finalize) if (ctx.owner == exprOwner) checkNoAlphaConflict(stats1) - stats1 + (stats1, finalCtx) } /** Given an inline method `mdef`, the method rewritten so that its body @@ -2681,7 +2724,7 @@ class Typer extends Namer def isAutoApplied(sym: Symbol): Boolean = sym.isConstructor || sym.matchNullaryLoosely || - ctx.testScala2Mode(MissingEmptyArgumentList(sym), tree.sourcePos, + ctx.testScala2CompatMode(MissingEmptyArgumentList(sym), tree.sourcePos, patch(tree.span.endPos, "()")) // Reasons NOT to eta expand: diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index b85e65ba889a..96cef1948994 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -173,7 +173,7 @@ class VarianceChecker()(implicit ctx: Context) { def checkVariance(sym: Symbol, pos: SourcePosition) = Validator.validateDefinition(sym) match { case Some(VarianceError(tvar, required)) => def msg = i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym" - if (ctx.scala2Mode && + if (ctx.scala2CompatMode && (sym.owner.isConstructor || sym.ownersIterator.exists(_.isAllOf(ProtectedLocal)))) ctx.migrationWarning( s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index a22f6db0c927..3b32e77ebc81 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -9,10 +9,8 @@ import java.io.IOException import scala.internal.Chars._ import Spans._ import scala.io.Codec -import core.Names.TermName import core.Contexts.Context import scala.annotation.internal.sharable -import core.Decorators.PreNamedString import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable @@ -59,8 +57,6 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends override def path: String = file.path override def jfile: Optional[JFile] = Optional.ofNullable(file.file) - def pathName: PathName = file.absolutePath.toTermName - override def equals(that: Any): Boolean = (this `eq` that.asInstanceOf[AnyRef]) || { that match { @@ -202,8 +198,6 @@ object SourceFile { implicit def fromContext(implicit ctx: Context): SourceFile = ctx.source - type PathName = TermName - def fromId(id: Int): SourceFile = sourceOfChunk(id >> ChunkSizeLog) def virtual(name: String, content: String) = new SourceFile(new VirtualFile(name, content.getBytes), scala.io.Codec.UTF8) diff --git a/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala b/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala index 697c830b81a6..a872bbac022a 100644 --- a/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala +++ b/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala @@ -20,7 +20,7 @@ class CollectTopLevelImports extends Phase { def run(implicit ctx: Context): Unit = { def topLevelImports(tree: Tree) = { - val PackageDef(_, _ :: TypeDef(_, rhs: Template) :: Nil) = tree + val PackageDef(_, _ :: TypeDef(_, rhs: Template) :: _) = tree rhs.body.collect { case tree: Import => tree } } diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index acfa33f02765..e181ff042c72 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -272,7 +272,7 @@ class ReplDriver(settings: Array[String], val vals = info.fields - .filterNot(_.symbol.isOneOf(ParamAccessor | Private | Synthetic | Module)) + .filterNot(_.symbol.isOneOf(ParamAccessor | Private | Synthetic | Artifact | Module)) .filter(_.symbol.name.is(SimpleNameKind)) .sortBy(_.name) diff --git a/compiler/test-resources/repl/i4852 b/compiler/test-resources/repl/i4852 new file mode 100644 index 000000000000..32c126578259 --- /dev/null +++ b/compiler/test-resources/repl/i4852 @@ -0,0 +1,6 @@ +scala> inline def foo[T](t : T*) : Any = t +def foo[T](t: T*): Any +scala> foo(1, "hi", false) +val res0: Any = ArraySeq(1, hi, false) +scala> foo() +val res1: Any = ArraySeq() diff --git a/compiler/test-resources/repl/i5218 b/compiler/test-resources/repl/i5218 index 3402ed98e286..abe63009ef74 100644 --- a/compiler/test-resources/repl/i5218 +++ b/compiler/test-resources/repl/i5218 @@ -3,4 +3,4 @@ val tuple: (Int, String, Long) = (1,2,3) scala> 0.0 *: tuple val res0: (Double, Int, String, Long) = (0.0,1,2,3) scala> tuple ++ tuple -val res1: Int *: String *: Long *: scala.Tuple.Concat[Unit, tuple.type] = (1,2,3,1,2,3) \ No newline at end of file +val res1: Int *: String *: Long *: tuple.type = (1,2,3,1,2,3) diff --git a/compiler/test/dotc/pos-from-tasty.blacklist b/compiler/test/dotc/pos-from-tasty.blacklist index f3a7568dcf01..1fac00b403ab 100644 --- a/compiler/test/dotc/pos-from-tasty.blacklist +++ b/compiler/test/dotc/pos-from-tasty.blacklist @@ -8,4 +8,8 @@ t3612.scala t802.scala # Matchtype -i7087.scala \ No newline at end of file +i7087.scala + +# Nullability +nullable.scala +notNull.scala diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 740ff1108fad..59a9b8498080 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -28,3 +28,6 @@ i5720.scala # Tuples toexproftuple.scala + +# Nullability +nullable.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 37ca6360152d..07941fe113ca 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -36,7 +36,7 @@ class CompilationTests extends ParallelTesting { implicit val testGroup: TestGroup = TestGroup("compilePos") aggregateTests( compileFile("tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")), - compileFile("tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).copyToTarget(), + compileFile("tests/pos-scala2/rewrites.scala", scala2CompatMode.and("-rewrite")).copyToTarget(), compileFile("tests/pos-special/utf8encoded.scala", explicitUTF8), compileFile("tests/pos-special/utf16encoded.scala", explicitUTF16), compileFile("tests/pos-special/completeFromSource/Test.scala", defaultOptions.and("-sourcepath", "tests/pos-special")), @@ -48,7 +48,7 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/pos-special/strawman-collections", defaultOptions), compileFilesInDir("tests/pos-special/isInstanceOf", allowDeepSubtypes.and("-Xfatal-warnings")), compileFilesInDir("tests/new", defaultOptions), - compileFilesInDir("tests/pos-scala2", scala2Mode), + compileFilesInDir("tests/pos-scala2", scala2CompatMode), compileFilesInDir("tests/pos", defaultOptions), compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes), compileFile( @@ -59,6 +59,8 @@ class CompilationTests extends ParallelTesting { compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40")), compileFile("tests/pos-special/indent-colons.scala", defaultOptions.and("-Yindent-colons")), compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")), + compileFile("tests/pos-special/nullable.scala", defaultOptions.and("-Yexplicit-nulls")), + compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")), compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")) ).checkCompile() } @@ -118,9 +120,9 @@ class CompilationTests extends ParallelTesting { compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions-old.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), - compileFile("tests/neg-custom-args/i3246.scala", scala2Mode), - compileFile("tests/neg-custom-args/overrideClass.scala", scala2Mode), - compileFile("tests/neg-custom-args/ovlazy.scala", scala2Mode.and("-migration", "-Xfatal-warnings")), + compileFile("tests/neg-custom-args/i3246.scala", scala2CompatMode), + compileFile("tests/neg-custom-args/overrideClass.scala", scala2CompatMode), + compileFile("tests/neg-custom-args/ovlazy.scala", scala2CompatMode.and("-migration", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")), compileFile("tests/neg-custom-args/nopredef.scala", defaultOptions.and("-Yno-predef")), compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")), diff --git a/compiler/test/dotty/tools/repl/LoadTests.scala b/compiler/test/dotty/tools/repl/LoadTests.scala new file mode 100644 index 000000000000..a94d94183f11 --- /dev/null +++ b/compiler/test/dotty/tools/repl/LoadTests.scala @@ -0,0 +1,85 @@ +package dotty.tools.repl + +import java.nio.file.{ Path, Files } +import java.util.Comparator + +import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.Assert.assertEquals + +class LoadTests extends ReplTest { + import LoadTests._ + + @Test def helloworld = loadTest( + file = """|def helloWorld = "Hello, World!" + |println(helloWorld) + |""".stripMargin, + defs = """|Hello, World! + |def helloWorld: String + | + | + |""".stripMargin, + runCode = "helloWorld", + output = """|val res0: String = Hello, World! + | + |""".stripMargin + ) + + @Test def maindef = loadTest( + file = """|@main def helloWorld = println("Hello, World!") + |""".stripMargin, + defs = """|def helloWorld: Unit + | + | + |""".stripMargin, + runCode = "helloWorld", + output = """|Hello, World! + | + |""".stripMargin + ) + + @Test def maindefs = loadTest( + file = """|@main def helloWorld = println("Hello, World!") + |@main def helloTo(name: String) = println(s"Hello, $name!") + |""".stripMargin, + defs = """|def helloTo(name: String): Unit + |def helloWorld: Unit + | + | + |""".stripMargin, + runCode = """helloWorld; helloTo("Scala")""", + output = """|Hello, World! + |Hello, Scala! + | + |""".stripMargin + ) + + def loadTest(file: String, defs: String, runCode: String, output: String) = + eval(s":load ${writeFile(file)}").andThen { implicit s => + assertEquals(defs, storedOutput()) + run(runCode) + assertEquals(output, storedOutput()) + } + + private def eval(code: String): State = + fromInitialState { implicit s => run(code) } + +} + +object LoadTests { + + private var dir: Path = null + + @BeforeClass def setupDir: Unit = + dir = Files.createTempDirectory("repl_load_src") + + @AfterClass def removeDir: Unit = + Files.walk(dir).sorted(Comparator.reverseOrder).forEach(Files.delete) + dir = null + + private def writeFile(contents: String): Path = { + val file = Files.createTempFile(dir, "repl_test", ".scala") + Files.write(file, contents.getBytes) + file + } + +} diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 56f413d18359..b9a2785d72c8 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -63,7 +63,7 @@ object TestConfiguration { ) val picklingWithCompilerOptions = picklingOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath) - val scala2Mode = defaultOptions and "-language:Scala2" + val scala2CompatMode = defaultOptions and "-language:Scala2Compat" val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") diff --git a/doc-tool/test/dotty/tools/dottydoc/GenDocs.scala b/doc-tool/test/dotty/tools/dottydoc/GenDocs.scala index 6eb1f53eec74..26a773be00ef 100644 --- a/doc-tool/test/dotty/tools/dottydoc/GenDocs.scala +++ b/doc-tool/test/dotty/tools/dottydoc/GenDocs.scala @@ -22,7 +22,7 @@ trait LocalResources extends DocDriver { def withClasspath(files: Array[String]) = "-siteroot" +: "../docs" +: "-project" +: "Dotty" +: - "-language:Scala2" +: + "-language:Scala2Compat" +: "-classpath" +: TestConfiguration.basicClasspath +: files diff --git a/docs/blog/_posts/2016-02-17-scaling-dot-soundness.md b/docs/blog/_posts/2016-02-17-scaling-dot-soundness.md index 667e0cfe8d82..0be2eb54c111 100644 --- a/docs/blog/_posts/2016-02-17-scaling-dot-soundness.md +++ b/docs/blog/_posts/2016-02-17-scaling-dot-soundness.md @@ -144,7 +144,7 @@ arithmetic. To ease the transition, we will continue for a while to allow unrestricted type projections under a flag, even though they are potentially unsound. In the current Dotty compiler, that flag is a language import -`-language:Scala2`, but it could be something different for other +`-language:Scala2Compat`, but it could be something different for other compilers, e.g. `-unsafe`. Maybe we can find rules that are less restrictive than the ones we have now, and are still sound. But one aspect should be non-negotiable: Any fundamental deviations from the diff --git a/docs/docs/reference/changed-features/vararg-patterns.md b/docs/docs/reference/changed-features/vararg-patterns.md index 0b5751def420..77123d8e6f5e 100644 --- a/docs/docs/reference/changed-features/vararg-patterns.md +++ b/docs/docs/reference/changed-features/vararg-patterns.md @@ -36,5 +36,5 @@ The change to the grammar is: ## Compatibility considerations -Under the `-language:Scala2` option, Dotty will accept both the old and the new syntax. +Under the `-language:Scala2Compat` option, Dotty will accept both the old and the new syntax. A migration warning will be emitted when the old syntax is encountered. diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md index eaa54fbd75a2..ce19e6a4389a 100644 --- a/docs/docs/reference/contextual/relationship-implicits.md +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -29,22 +29,21 @@ Given instances can be mapped to combinations of implicit objects, classes and i final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T] ``` 3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type parameters nor a given clause, - it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to that - reference without caching it. + it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to + that reference without caching it. - Examples: - - ```scala - given global: ExecutionContext = new ForkJoinContext() +Examples: +```scala +given global: ExecutionContext = new ForkJoinContext() - val ctx: Context - given Context = ctx - ``` - would map to - ```scala - final implicit lazy val global: ExecutionContext = new ForkJoinContext() - final implicit def given_Context = ctx - ``` +val ctx: Context +given Context = ctx +``` +would map to +```scala +final implicit lazy val global: ExecutionContext = new ForkJoinContext() +final implicit def given_Context = ctx +``` ### Anonymous Given Instances diff --git a/docs/docs/reference/dropped-features/auto-apply.md b/docs/docs/reference/dropped-features/auto-apply.md index 95f8c4d8a6b5..a63e7b5b24aa 100644 --- a/docs/docs/reference/dropped-features/auto-apply.md +++ b/docs/docs/reference/dropped-features/auto-apply.md @@ -75,7 +75,7 @@ requirement. ### Migrating code Existing Scala code with inconsistent parameters can still be compiled -in Dotty under `-language:Scala2`. When paired with the `-rewrite` +in Dotty under `-language:Scala2Compat`. When paired with the `-rewrite` option, the code will be automatically rewritten to conform to Dotty's stricter checking. diff --git a/docs/docs/reference/dropped-features/procedure-syntax.md b/docs/docs/reference/dropped-features/procedure-syntax.md index ab4afeb177fe..812db607732c 100644 --- a/docs/docs/reference/dropped-features/procedure-syntax.md +++ b/docs/docs/reference/dropped-features/procedure-syntax.md @@ -12,7 +12,7 @@ has been dropped. You need to write one of the following instead: def f() = { ... } def f(): Unit = { ... } ``` -Dotty will accept the old syntax under the `-language:Scala2` option. +Dotty will accept the old syntax under the `-language:Scala2Compat` option. If the `-migration` option is set, it can even rewrite old syntax to new. The [ScalaFix](https://scalacenter.github.io/scalafix/) tool also can rewrite procedure syntax to make it Dotty-compatible. diff --git a/docs/docs/reference/features-classification.md b/docs/docs/reference/features-classification.md index cb669cfa0c50..b05c44fd48a0 100644 --- a/docs/docs/reference/features-classification.md +++ b/docs/docs/reference/features-classification.md @@ -77,7 +77,7 @@ These constructs are restricted to make the language safer. - [@infix and @alpha](https://github.com/lampepfl/dotty/pull/5975) make method application syntax uniform across code bases and require alphanumeric aliases for all symbolic names (proposed, not implemented). -Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-language:Scala2`. +Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-language:Scala2Compat`. **Status: now or never** @@ -108,7 +108,7 @@ The date when these constructs are dropped varies. The current status is: - Not implemented at all: - DelayedInit, existential types, weak conformance. - - Supported under `-language:Scala2`: + - Supported under `-language:Scala2Compat`: - procedure syntax, class shadowing, symbol literals, auto application, auto tupling in a restricted form. - Supported in 3.0, to be deprecated and phased out later: - XML literals, compound types. @@ -133,7 +133,7 @@ These constructs have undergone changes to make them more regular and useful. - [Eta expansion](changed-features/eta-expansion.md) is now performed universally also in the absence of an expected type. The postfix `_` operator is thus made redundant. It will be deprecated and dropped after Scala 3.0. - [Implicit Resolution](changed-features/implicit-resolution.md): The implicit resolution rules have been cleaned up to make them more useful and less surprising. Implicit scope is restricted to no longer include package prefixes. -Most aspects of old-style implicit resolution are still available under `-language:Scala2`. The other changes in this list are applied unconditionally. +Most aspects of old-style implicit resolution are still available under `-language:Scala2Compat`. The other changes in this list are applied unconditionally. **Status: strongly advisable** diff --git a/docs/docs/reference/other-new-features/indentation.md b/docs/docs/reference/other-new-features/indentation.md index 054176d21428..0d89f5bd1ec3 100644 --- a/docs/docs/reference/other-new-features/indentation.md +++ b/docs/docs/reference/other-new-features/indentation.md @@ -175,7 +175,7 @@ end IndentWidth ### Settings and Rewrites -Significant indentation is enabled by default. It can be turned off by giving any of the options `-noindent`, `old-syntax` and `language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues an error (or, in the case of `-language:Scala2`, a migration warning). +Significant indentation is enabled by default. It can be turned off by giving any of the options `-noindent`, `old-syntax` and `language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues an error (or, in the case of `-language:Scala2Compat`, a migration warning). The Dotty compiler can rewrite source code to indented code and back. When invoked with options `-rewrite -indent` it will rewrite braces to diff --git a/docs/docs/reference/overview.md b/docs/docs/reference/overview.md index f699d54a323d..1165d878dc7b 100644 --- a/docs/docs/reference/overview.md +++ b/docs/docs/reference/overview.md @@ -63,7 +63,7 @@ These constructs are restricted to make the language safer. - [@infix and @alpha](https://github.com/lampepfl/dotty/pull/5975) make method application syntax uniform across code bases and require alphanumeric aliases for all symbolic names (proposed, not implemented). -Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-language:Scala2`. +Unrestricted implicit conversions continue to be available in Scala 3.0, but will be deprecated and removed later. Unrestricted versions of the other constructs in the list above are available only under `-language:Scala2Compat`. ## Dropped Constructs @@ -85,7 +85,7 @@ The date when these constructs are dropped varies. The current status is: - Not implemented at all: - DelayedInit, existential types, weak conformance. - - Supported under `-language:Scala2`: + - Supported under `-language:Scala2Compat`: - procedure syntax, class shadowing, symbol literals, auto application, auto tupling in a restricted form. - Supported in 3.0, to be deprecated and phased out later: - XML literals, compound types. @@ -100,7 +100,7 @@ These constructs have undergone changes to make them more regular and useful. - [Eta expansion](changed-features/eta-expansion.md) is now performed universally also in the absence of an expected type. The postfix `_` operator is thus made redundant. It will be deprecated and dropped after Scala 3.0. - [Implicit Resolution](changed-features/implicit-resolution.md): The implicit resolution rules have been cleaned up to make them more useful and less surprising. Implicit scope is restricted to no longer include package prefixes. -Most aspects of old-style implicit resolution are still available under `-language:Scala2`. The other changes in this list are applied unconditionally. +Most aspects of old-style implicit resolution are still available under `-language:Scala2Compat`. The other changes in this list are applied unconditionally. ## New Constructs diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 855b2dcae03b..046ae8e6a739 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -8,13 +8,13 @@ object DottyPredef { assertFail(message) } - inline final def assert(assertion: => Boolean): Unit = { + inline final def assert(assertion: => Boolean) <: Unit = { if (!assertion) assertFail() } - def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") - def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) + def assertFail(): Nothing = throw new java.lang.AssertionError("assertion failed") + def assertFail(message: => Any): Nothing = throw new java.lang.AssertionError("assertion failed: " + message) inline final def implicitly[T](implicit ev: T): T = ev diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 636e2cbd6e9d..28ac8e519637 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -52,5 +52,15 @@ package object compiletime { */ inline def summonFrom[T](f: Nothing => T) <: T = ??? - type S[X <: Int] <: Int + /** Succesor of a natural number where zero is the type 0 and successors are reduced as if the definition was + * + * type S[N <: Int] <: Int = N match { + * case 0 => 1 + * case 1 => 2 + * case 2 => 3 + * ... + * case 2147483646 => 2147483647 + * } + */ + type S[N <: Int] <: Int } diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index b4007c4460ed..39f21f5b66b9 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -283,6 +283,7 @@ trait CompilerInterface { def Term_tpe(self: Term)(given ctx: Context): Type def Term_underlyingArgument(self: Term)(given ctx: Context): Term def Term_underlying(self: Term)(given ctx: Context): Term + def Term_etaExpand(term: Term): Term /** Tree representing a reference to definition */ type Ref <: Term diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index 8a7eeb22936c..5f0e91f35ec0 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -172,6 +172,7 @@ trait TreeOps extends Core { def tpe(given ctx: Context): Type = internal.Term_tpe(self) def underlyingArgument(given ctx: Context): Term = internal.Term_underlyingArgument(self) def underlying(given ctx: Context): Term = internal.Term_underlying(self) + def etaExpand(given ctx: Context): Term = internal.Term_etaExpand(self) /** A unary apply node with given argument: `tree(arg)` */ def appliedTo(arg: Term)(given ctx: Context): Term = diff --git a/project/Build.scala b/project/Build.scala index 13111df45eee..7e51b575ced1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -960,7 +960,7 @@ object Build { ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/lang" ** (("*.scala": FileFilter) -- "StringTest.scala")).get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/io" ** (("*.scala": FileFilter) -- "ByteArrayInputStreamTest.scala" -- "ByteArrayOutputStreamTest.scala" -- "DataInputStreamTest.scala" -- "DataOutputStreamTest.scala" -- "InputStreamTest.scala" -- "OutputStreamWriterTest.scala" -- "PrintStreamTest.scala" -- "CommonStreamsTests.scala")).get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/math" ** "*.scala").get - ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/net" ** (("*.scala": FileFilter) -- "URITest.scala")).get + ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/net" ** "*.scala").get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/security" ** "*.scala").get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex" ** "*.scala").get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent" ** (("*.scala": FileFilter) -- "ConcurrentHashMapTest.scala" -- "ConcurrentLinkedQueueTest.scala" -- "ConcurrentMapTest.scala" -- "ConcurrentSkipListSetTest.scala" -- "CopyOnWriteArrayListTest.scala")).get @@ -979,7 +979,7 @@ object Build { ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niobuffer" ** (("*.scala": FileFilter) -- "ByteBufferTest.scala")).get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niocharset" ** (("*.scala": FileFilter) -- "BaseCharsetTest.scala" -- "Latin1Test.scala" -- "USASCIITest.scala" -- "UTF16Test.scala" -- "UTF8Test.scala")).get ++ (dir / "shared/src/test/scala/org/scalajs/testsuite/scalalib" ** (("*.scala": FileFilter) -- "ArrayBuilderTest.scala" -- "ClassTagTest.scala" -- "EnumerationTest.scala" -- "RangesTest.scala" -- "SymbolTest.scala")).get - ++ (dir / "shared/src/test/require-sam" ** (("*.scala": FileFilter) -- "SAMTest.scala")).get + ++ (dir / "shared/src/test/require-sam" ** "*.scala").get ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter) -- "DefaultMethodsTest.scala")).get ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/lang" ** "*.scala").get ++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/util" ** (("*.scala": FileFilter) -- "CollectionsOnCopyOnWriteArrayListTestOnJDK8.scala")).get diff --git a/sbt-dotty/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/compilerReporter/simple/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/discovery/test-discovery/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/abstract-override/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/abstract-override/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/abstract-override/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/abstract-override/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/abstract-type-override/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/abstract-type-override/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/abstract-type-override/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/abstract-type-override/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/abstract-type/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/abstract-type/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/abstract-type/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/abstract-type/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/added/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/added/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/added/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/added/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/anon-class-java-depends-on-scala/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/anon-class-java-depends-on-scala/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/anon-class-java-depends-on-scala/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/anon-class-java-depends-on-scala/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/anon-java-scala-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/anon-java-scala-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/anon-java-scala-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/anon-java-scala-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/as-seen-from-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/as-seen-from-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/as-seen-from-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/as-seen-from-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/as-seen-from-b/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/as-seen-from-b/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/as-seen-from-b/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/as-seen-from-b/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/backtick-quoted-names/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/backtick-quoted-names/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/backtick-quoted-names/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/backtick-quoted-names/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/binary/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/binary/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/binary/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/binary/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/by-name/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/by-name/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/by-name/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/by-name/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/canon/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/canon/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/canon/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/canon/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/changedTypeOfChildOfSealed/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/changedTypeOfChildOfSealed/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/changedTypeOfChildOfSealed/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/changedTypeOfChildOfSealed/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/check-classes/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/check-classes/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/check-classes/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/check-classes/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/check-dependencies/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/check-dependencies/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/check-dependencies/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/check-dependencies/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/check-products/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/check-products/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/check-products/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/check-products/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/check-recompilations/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/check-recompilations/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/check-recompilations/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/check-recompilations/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/class-based-inheritance/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/class-based-inheritance/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/class-based-inheritance/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/class-based-inheritance/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/class-based-memberRef/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/class-based-memberRef/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/class-based-memberRef/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/class-based-memberRef/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/compactify/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/compactify/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/compactify/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/compactify/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/constants/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/constants/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/constants/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/constants/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/constructors-unrelated/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/constructors-unrelated/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/constructors-unrelated/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/constructors-unrelated/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/continuations/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/continuations/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/continuations/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/continuations/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/cross-source/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/cross-source/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/cross-source/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/cross-source/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/default-params/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/default-params/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/default-params/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/default-params/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/dup-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/dup-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/dup-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/dup-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/empty-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/empty-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/empty-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/empty-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/empty-modified-names/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/empty-modified-names/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/empty-modified-names/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/empty-modified-names/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/empty-package/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/empty-package/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/empty-package/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/empty-package/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/erasure/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/erasure/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/erasure/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/erasure/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/expanded-type-projection/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/expanded-type-projection/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/expanded-type-projection/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/expanded-type-projection/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/export-jars/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/export-jars/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/export-jars/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/export-jars/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/false-error/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/false-error/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/false-error/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/false-error/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/fbounded-existentials/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/fbounded-existentials/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/fbounded-existentials/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/fbounded-existentials/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/implicit-params/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/implicit-params/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/implicit-params/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/implicit-params/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/implicit-search-companion-scope/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/implicit-search-companion-scope/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/implicit-search-companion-scope/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/implicit-search-companion-scope/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/implicit-search-higher-kinded/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/implicit-search-higher-kinded/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/implicit-search-higher-kinded/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/implicit-search-higher-kinded/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/implicit-search/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/implicit-search/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/implicit-search/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/implicit-search/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/implicit/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/implicit/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/implicit/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/implicit/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/import-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/import-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/import-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/import-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/import-package/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/import-package/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/import-package/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/import-package/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/inherited-deps-java/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/inherited-deps-java/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inherited-deps-java/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inherited-deps-java/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/inherited_type_params/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/inherited_type_params/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inherited_type_params/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inherited_type_params/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/inner-class-java-depends-on-scala/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/inner-class-java-depends-on-scala/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inner-class-java-depends-on-scala/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inner-class-java-depends-on-scala/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/intermediate-error/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/intermediate-error/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/intermediate-error/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/intermediate-error/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-analysis-serialization-error/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-analysis-serialization-error/project/DottyInjectedPlugin.scala index ce3d46d79921..6de08a769899 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-analysis-serialization-error/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-analysis-serialization-error/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2CompatCompat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-anonymous/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-anonymous/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-anonymous/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-anonymous/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-basic/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-basic/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-basic/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-basic/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-enum/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-enum/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-enum/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-enum/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-generic-workaround/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-generic-workaround/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-generic-workaround/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-generic-workaround/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-inner/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-inner/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-inner/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-inner/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-lambda-typeparams/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-lambda-typeparams/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-lambda-typeparams/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-lambda-typeparams/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-mixed/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-mixed/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-mixed/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-mixed/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-name-with-dollars/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-name-with-dollars/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-name-with-dollars/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-name-with-dollars/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/java-static/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/java-static/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/java-static/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/java-static/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/lazy-val/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/lazy-val/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/lazy-val/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/lazy-val/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/less-inter-inv-java/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/less-inter-inv-java/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/less-inter-inv-java/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/less-inter-inv-java/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/less-inter-inv/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/less-inter-inv/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/less-inter-inv/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/less-inter-inv/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/linearization/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/linearization/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/linearization/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/linearization/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance-from-java/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance-from-java/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance-from-java/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance-from-java/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/local-class-inheritance/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/malformed-class-name-with-dollar/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/malformed-class-name-with-dollar/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/malformed-class-name-with-dollar/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/malformed-class-name-with-dollar/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/malformed-class-name/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/malformed-class-name/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/malformed-class-name/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/malformed-class-name/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/named/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/named/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/named/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/named/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/nested-case-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/nested-case-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/nested-case-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/nested-case-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/nested-type-params/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/nested-type-params/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/nested-type-params/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/nested-type-params/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/new-cyclic/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/new-cyclic/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/new-cyclic/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/new-cyclic/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/new-pkg-dep/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/new-pkg-dep/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/new-pkg-dep/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/new-pkg-dep/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/override/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/override/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/override/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/override/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/package-object-implicit/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/package-object-implicit/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/package-object-implicit/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/package-object-implicit/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/package-object-name/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/package-object-name/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/package-object-name/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/package-object-name/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/package-object-nested-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/package-object-nested-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/package-object-nested-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/package-object-nested-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/packageobject-and-traits/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/packageobject-and-traits/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/packageobject-and-traits/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/packageobject-and-traits/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/parent-change/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/parent-change/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/parent-change/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/parent-change/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/parent-member-change/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/parent-member-change/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/parent-member-change/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/parent-member-change/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/pkg-private-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/pkg-private-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/pkg-private-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/pkg-private-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/pkg-self/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/pkg-self/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/pkg-self/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/pkg-self/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/qualified-access/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/qualified-access/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/qualified-access/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/qualified-access/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/recorded-products/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/recorded-products/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/recorded-products/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/recorded-products/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/relative-source-error/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/relative-source-error/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/relative-source-error/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/relative-source-error/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/remove-test-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/remove-test-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/remove-test-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/remove-test-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/remove-test-b/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/remove-test-b/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/remove-test-b/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/remove-test-b/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/repeated-parameters/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/repeated-parameters/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/repeated-parameters/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/repeated-parameters/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/replace-test-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/replace-test-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/replace-test-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/replace-test-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/resident-java/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/resident-java/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/resident-java/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/resident-java/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/resident-package-object/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/resident-package-object/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/resident-package-object/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/resident-package-object/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/restore-classes/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/restore-classes/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/restore-classes/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/restore-classes/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/same-file-used-names/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/same-file-used-names/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/same-file-used-names/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/same-file-used-names/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/sealed/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/sealed/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/sealed/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/sealed/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/signature-change/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/signature-change/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/signature-change/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/signature-change/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/specialized/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/specialized/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/specialized/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/specialized/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/stability-change/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/stability-change/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/stability-change/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/stability-change/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/struct-projection/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/struct-projection/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/struct-projection/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/struct-projection/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/struct-usage/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/struct-usage/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/struct-usage/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/struct-usage/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/struct/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/struct/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/struct/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/struct/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/synthetic-companion/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/synthetic-companion/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/synthetic-companion/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/synthetic-companion/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/trait-member-modified/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/trait-member-modified/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/trait-member-modified/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/trait-member-modified/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/trait-private-object/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/trait-private-object/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/trait-private-object/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/trait-private-object/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/trait-private-val/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/trait-private-val/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/trait-private-val/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/trait-private-val/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/trait-private-var/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/trait-private-var/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/trait-private-var/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/trait-private-var/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/trait-super/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/trait-super/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/trait-super/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/trait-super/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-b/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-b/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-b/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-b/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-inherit-java/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-inherit-java/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-inherit-java/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-inherit-java/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-inherit/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-inherit/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-inherit/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-inherit/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/transitive-memberRef/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/transitive-memberRef/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/transitive-memberRef/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/transitive-memberRef/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/type-alias/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/type-alias/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/type-alias/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/type-alias/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/type-member-nested-object/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/type-member-nested-object/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/type-member-nested-object/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/type-member-nested-object/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/type-parameter/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/type-parameter/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/type-parameter/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/type-parameter/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/typeargref/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/typeargref/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/typeargref/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/typeargref/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/typeref-only/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/typeref-only/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/typeref-only/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/typeref-only/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/typeref-return/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/typeref-return/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/typeref-return/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/typeref-return/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-a/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-a/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-a/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-a/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-b/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-b/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-b/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/types-in-used-names-b/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/unexpanded-names/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/unexpanded-names/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/unexpanded-names/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/unexpanded-names/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/value-class-underlying/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/value-class-underlying/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/value-class-underlying/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/value-class-underlying/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/value-class/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/value-class/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/value-class/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/value-class/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/var/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/var/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/var/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/var/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/sbt-dotty/sbt-test/source-dependencies/variance/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/variance/project/DottyInjectedPlugin.scala index ce3d46d79921..6a77cb33121e 100644 --- a/sbt-dotty/sbt-test/source-dependencies/variance/project/DottyInjectedPlugin.scala +++ b/sbt-dotty/sbt-test/source-dependencies/variance/project/DottyInjectedPlugin.scala @@ -7,6 +7,6 @@ object DottyInjectedPlugin extends AutoPlugin { override val projectSettings = Seq( scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-language:Scala2" + scalacOptions += "-language:Scala2Compat" ) } diff --git a/tests/neg/i6606.scala b/tests/neg/i6606.scala new file mode 100644 index 000000000000..74847e2e6f53 --- /dev/null +++ b/tests/neg/i6606.scala @@ -0,0 +1,8 @@ +val t0: Int = 0 +val t1: scala.compiletime.S[-1] = 0 // error +val t2: scala.compiletime.S[Int] = 1 +val t3: scala.compiletime.S[Int] = 0 // error +val t4: scala.compiletime.S[Int] = t0 // error + +val t5: scala.compiletime.S[2147483647] = -2147483648 // error +val t6: scala.compiletime.S[-2147483648] = -2147483647 // error diff --git a/tests/neg/i6762.check b/tests/neg/i6762.check new file mode 100644 index 000000000000..315acb67198a --- /dev/null +++ b/tests/neg/i6762.check @@ -0,0 +1,5 @@ +-- [E007] Type Mismatch Error: tests/neg/i6762.scala:5:72 -------------------------------------------------------------- +5 |def f(word: String)(given QuoteContext): Expr[Foo[G[String]]] = '{Foo(${word.toExpr})} // error + | ^^^^ + | Found: quoted.Expr[String] + | Required: quoted.Expr[G[String]] diff --git a/tests/neg/i6762.scala b/tests/neg/i6762.scala new file mode 100644 index 000000000000..3ef78e4d8d11 --- /dev/null +++ b/tests/neg/i6762.scala @@ -0,0 +1,5 @@ +import scala.quoted.{_, given} + +type G[X] +case class Foo[T](x: T) +def f(word: String)(given QuoteContext): Expr[Foo[G[String]]] = '{Foo(${word.toExpr})} // error diff --git a/tests/neg/i6762b.check b/tests/neg/i6762b.check new file mode 100644 index 000000000000..54d75a793056 --- /dev/null +++ b/tests/neg/i6762b.check @@ -0,0 +1,5 @@ +-- [E007] Type Mismatch Error: tests/neg/i6762b.scala:4:39 ------------------------------------------------------------- +4 |def f(word: String): Expr[G[String]] = word.toExpr // error + | ^^^^ + | Found: Expr[String] + | Required: Expr[G[String]] diff --git a/tests/neg/i6762b.scala b/tests/neg/i6762b.scala new file mode 100644 index 000000000000..9a12fffd6dbe --- /dev/null +++ b/tests/neg/i6762b.scala @@ -0,0 +1,13 @@ +type Expr[T] +type G[X] + +def f(word: String): Expr[G[String]] = word.toExpr // error + +def splice[T](e: Expr[T]): T = ??? + +type Liftable +given Liftable = ??? + +implicit object ExprOps { + def (x: T) toExpr[T](given Liftable): Expr[T] = ??? +} diff --git a/tests/neg/i6779.check b/tests/neg/i6779.check new file mode 100644 index 000000000000..07c3473349d2 --- /dev/null +++ b/tests/neg/i6779.check @@ -0,0 +1,15 @@ +-- [E007] Type Mismatch Error: tests/neg/i6779.scala:9:30 -------------------------------------------------------------- +9 |def g1[T](x: T): F[G[T]] = x.f(given summon[Stuff]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: F[T] + | Required: F[G[T]] +-- [E007] Type Mismatch Error: tests/neg/i6779.scala:11:27 ------------------------------------------------------------- +11 |def g2[T](x: T): F[G[T]] = x.f // error + | ^ + | Found: F[T] + | Required: F[G[T]] +-- [E007] Type Mismatch Error: tests/neg/i6779.scala:13:31 ------------------------------------------------------------- +13 |def g3[T](x: T): F[G[T]] = f(x)(given summon[Stuff]) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: F[T] + | Required: F[G[T]] diff --git a/tests/neg/i6779.scala b/tests/neg/i6779.scala new file mode 100644 index 000000000000..5a24e0763317 --- /dev/null +++ b/tests/neg/i6779.scala @@ -0,0 +1,13 @@ +type F[T] +type G[T] +type Stuff +given Stuff = ??? + +def (x: T) f[T](given Stuff): F[T] = ??? + + +def g1[T](x: T): F[G[T]] = x.f(given summon[Stuff]) // error + +def g2[T](x: T): F[G[T]] = x.f // error + +def g3[T](x: T): F[G[T]] = f(x)(given summon[Stuff]) // error diff --git a/tests/neg/i7359-b.check b/tests/neg/i7359-b.check new file mode 100644 index 000000000000..273c0415b660 --- /dev/null +++ b/tests/neg/i7359-b.check @@ -0,0 +1,4 @@ +-- [E105] Syntax Error: tests/neg/i7359-b.scala:3:6 -------------------------------------------------------------------- +3 | def notifyAll(): Unit // error + | ^ + | Traits cannot redefine final method notifyAll from class AnyRef. diff --git a/tests/neg/i7359-b.scala b/tests/neg/i7359-b.scala new file mode 100644 index 000000000000..b0c9c9c48e86 --- /dev/null +++ b/tests/neg/i7359-b.scala @@ -0,0 +1,3 @@ +trait SAMTrait + def first(): String + def notifyAll(): Unit // error diff --git a/tests/neg/i7359-c.check b/tests/neg/i7359-c.check new file mode 100644 index 000000000000..9f173d1e5782 --- /dev/null +++ b/tests/neg/i7359-c.check @@ -0,0 +1,4 @@ +-- [E105] Syntax Error: tests/neg/i7359-c.scala:3:6 -------------------------------------------------------------------- +3 | def wait(): Unit // error + | ^ + | Traits cannot redefine final method wait from class AnyRef. diff --git a/tests/neg/i7359-c.scala b/tests/neg/i7359-c.scala new file mode 100644 index 000000000000..7989def06092 --- /dev/null +++ b/tests/neg/i7359-c.scala @@ -0,0 +1,3 @@ +trait SAMTrait + def first(): String + def wait(): Unit // error diff --git a/tests/neg/i7359-d.check b/tests/neg/i7359-d.check new file mode 100644 index 000000000000..67a7786e9a6a --- /dev/null +++ b/tests/neg/i7359-d.check @@ -0,0 +1,4 @@ +-- [E105] Syntax Error: tests/neg/i7359-d.scala:3:6 -------------------------------------------------------------------- +3 | def wait(a: Long): Unit // error + | ^ + | Traits cannot redefine final method wait from class AnyRef. diff --git a/tests/neg/i7359-d.scala b/tests/neg/i7359-d.scala new file mode 100644 index 000000000000..974ec6c48cc3 --- /dev/null +++ b/tests/neg/i7359-d.scala @@ -0,0 +1,3 @@ +trait SAMTrait + def first(): String + def wait(a: Long): Unit // error diff --git a/tests/neg/i7359-e.check b/tests/neg/i7359-e.check new file mode 100644 index 000000000000..6e602727b2a7 --- /dev/null +++ b/tests/neg/i7359-e.check @@ -0,0 +1,4 @@ +-- [E105] Syntax Error: tests/neg/i7359-e.scala:3:6 -------------------------------------------------------------------- +3 | def wait(a: Long, n: Int): Unit // error + | ^ + | Traits cannot redefine final method wait from class AnyRef. diff --git a/tests/neg/i7359-e.scala b/tests/neg/i7359-e.scala new file mode 100644 index 000000000000..4dad03b75756 --- /dev/null +++ b/tests/neg/i7359-e.scala @@ -0,0 +1,3 @@ +trait SAMTrait + def first(): String + def wait(a: Long, n: Int): Unit // error diff --git a/tests/neg/i7359-f.check b/tests/neg/i7359-f.check new file mode 100644 index 000000000000..1d3da8681cf2 --- /dev/null +++ b/tests/neg/i7359-f.check @@ -0,0 +1,7 @@ +-- [E120] Duplicate Symbol Error: tests/neg/i7359-f.scala:1:6 ---------------------------------------------------------- +1 |trait SAMTrait // error + | ^ + | Name clash between inherited members: + | def equals: [T >: Boolean <: Boolean](obj: Any): T in trait SAMTrait at line 3 and + | def equals(x$0: Any): Boolean in class Any + | have the same type after erasure. diff --git a/tests/neg/i7359-f.scala b/tests/neg/i7359-f.scala new file mode 100644 index 000000000000..a2cd5d7effdf --- /dev/null +++ b/tests/neg/i7359-f.scala @@ -0,0 +1,3 @@ +trait SAMTrait // error + def first(): String + def equals[T >: Boolean <: Boolean](obj: Any): T diff --git a/tests/neg/i7359-g.check b/tests/neg/i7359-g.check new file mode 100644 index 000000000000..9c579c57186b --- /dev/null +++ b/tests/neg/i7359-g.check @@ -0,0 +1,5 @@ +-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:25 ------------------------------------------------------------ +5 |val m : SAMTrait = () => "Hello" // error + | ^^^^^^^ + | Found: () => String + | Required: SAMTrait diff --git a/tests/neg/i7359-g.scala b/tests/neg/i7359-g.scala new file mode 100644 index 000000000000..b9c45310e015 --- /dev/null +++ b/tests/neg/i7359-g.scala @@ -0,0 +1,5 @@ +trait SAMTrait + def first(): String + def equals(obj: Int): Boolean + +val m : SAMTrait = () => "Hello" // error diff --git a/tests/neg/i7359.check b/tests/neg/i7359.check new file mode 100644 index 000000000000..45c02370c038 --- /dev/null +++ b/tests/neg/i7359.check @@ -0,0 +1,4 @@ +-- [E105] Syntax Error: tests/neg/i7359.scala:3:6 ---------------------------------------------------------------------- +3 | def notify(): Unit // error + | ^ + | Traits cannot redefine final method notify from class AnyRef. diff --git a/tests/neg/i7359.scala b/tests/neg/i7359.scala new file mode 100644 index 000000000000..65c8029284e6 --- /dev/null +++ b/tests/neg/i7359.scala @@ -0,0 +1,3 @@ +trait SAMTrait + def first(): String + def notify(): Unit // error diff --git a/tests/pos-macros/i7513/Macro_1.scala b/tests/pos-macros/i7513/Macro_1.scala new file mode 100644 index 000000000000..05193fbc7fc6 --- /dev/null +++ b/tests/pos-macros/i7513/Macro_1.scala @@ -0,0 +1,12 @@ +import scala.quoted._ + +trait Quoted { + def foo: Int +} +inline def quote: Quoted = ${ quoteImpl } + +def quoteImpl(given qctx: QuoteContext): Expr[Quoted] = '{ + new Quoted { + def foo = ??? + } +} diff --git a/tests/pos-macros/i7513/Test_2.scala b/tests/pos-macros/i7513/Test_2.scala new file mode 100644 index 000000000000..c8b382b6f42a --- /dev/null +++ b/tests/pos-macros/i7513/Test_2.scala @@ -0,0 +1,3 @@ +object Test { + quote.foo +} diff --git a/tests/pos-macros/i7513b/Macro_1.scala b/tests/pos-macros/i7513b/Macro_1.scala new file mode 100644 index 000000000000..ba1f60dfbf93 --- /dev/null +++ b/tests/pos-macros/i7513b/Macro_1.scala @@ -0,0 +1,12 @@ +import scala.quoted._ + +trait Quoted { + val foo: Int +} +inline def quote: Quoted = ${ quoteImpl } + +def quoteImpl(given qctx: QuoteContext): Expr[Quoted] = '{ + new Quoted { + val foo = ??? + } +} diff --git a/tests/pos-macros/i7513b/Test_2.scala b/tests/pos-macros/i7513b/Test_2.scala new file mode 100644 index 000000000000..c8b382b6f42a --- /dev/null +++ b/tests/pos-macros/i7513b/Test_2.scala @@ -0,0 +1,3 @@ +object Test { + quote.foo +} diff --git a/tests/pos-macros/i7513c/Macro_1.scala b/tests/pos-macros/i7513c/Macro_1.scala new file mode 100644 index 000000000000..671918f348f5 --- /dev/null +++ b/tests/pos-macros/i7513c/Macro_1.scala @@ -0,0 +1,14 @@ +import scala.quoted._ + +object Macros { + trait Quoted { + def foo: Int + } + inline def quote: Quoted = ${ quoteImpl } + + def quoteImpl(given qctx: QuoteContext): Expr[Quoted] = '{ + new Quoted { + def foo = ??? + } + } +} diff --git a/tests/pos-macros/i7513c/Test_2.scala b/tests/pos-macros/i7513c/Test_2.scala new file mode 100644 index 000000000000..b178088229dc --- /dev/null +++ b/tests/pos-macros/i7513c/Test_2.scala @@ -0,0 +1,3 @@ +object Test { + Macros.quote.foo +} diff --git a/tests/pos-special/notNull.scala b/tests/pos-special/notNull.scala new file mode 100644 index 000000000000..bcc0154b0a80 --- /dev/null +++ b/tests/pos-special/notNull.scala @@ -0,0 +1,21 @@ +object Test with + def notNull[A](x: A | Null): x.type & A = + assert(x != null) + x.asInstanceOf // TODO: drop the .asInstanceOf when explicit nulls are implemented + + locally { + val x: (Int | Null) = ??? + val y = x; val _: Int | Null = y + } + locally { + val x: Int | Null = ??? + val y = notNull(identity(x)); val yc: Int = y + val z = notNull(x); val zc: Int = z + } + class C { type T } + locally { + val x: C { type T = Int } = new C { type T = Int } + val xnn: x.type & C { type T = Int } = notNull(x) + val y: xnn.T = 33 + val z = y; val zc: Int = z + } diff --git a/tests/pos-special/nullable.scala b/tests/pos-special/nullable.scala new file mode 100644 index 000000000000..72b3e5e60adb --- /dev/null +++ b/tests/pos-special/nullable.scala @@ -0,0 +1,67 @@ +trait T { def f: Int } +def impossible(x: Any): Unit = + val y = x + +def test: Unit = + val x, x2, x3, x4 = "" + + if x != null then + if x == null then impossible(new T{}) + + if x == null then () + else + if x == null then impossible(new T{}) + + if x == null || { + if x == null then impossible(new T{}) + true + } + then () + + if x != null && { + if x == null then impossible(new T{}) + true + } + then () + + if !(x == null) && { + if x == null then impossible(new T{}) + true + } + then () + + x match + case _: String => + if x == null then impossible(new T{}) + + val y: Any = List(x) + y match + case y1 :: ys => if y == null then impossible(new T{}) + case Some(_) | Seq(_: _*) => if y == null then impossible(new T{}) + + x match + case null => + case _ => if x == null then impossible(new T{}) + + if x == null then return + if x == null then impossible(new T{}) + + if x2 == null then throw AssertionError() + if x2 == null then impossible(new T{}) + + if !(x3 != null) then throw AssertionError() + if x3 == null then impossible(new T{}) + + assert(x4 != null) + if x4 == null then impossible(new T{}) + + // TODO: uncomment the test below after implementing `notNull` + // class C(val x: Int, val next: C) + // var xs: C = C(1, C(2, null)) + // while xs != null do + // if xs == null then println("?") + // // looking at this with -Xprint-frontend -Xprint-types shows that the + // // type of `xs == null` is indeed `false`. We cannot currently use this in a test + // // since `xs == null` is not technically a pure expression since `xs` is not a path. + // // We should test variable tracking once this is integrated with explicit not null types. + // xs = xs.next diff --git a/tests/pos/i6475.scala b/tests/pos/i6475.scala new file mode 100644 index 000000000000..f0ebdf5de493 --- /dev/null +++ b/tests/pos/i6475.scala @@ -0,0 +1,4 @@ +object Foo1 { type T[+A] = (A, Int) } +object Foo2 { type T[+A] = [+B] =>> (A, B) } +object Foo3 { type T[+A] = [+B] =>> [C] =>> (A, B) } +object Foo4 { type T = [+A] =>> [+B] =>> [C] =>> (A, B) } diff --git a/tests/pos/i7359.scala b/tests/pos/i7359.scala new file mode 100644 index 000000000000..911984f01bcc --- /dev/null +++ b/tests/pos/i7359.scala @@ -0,0 +1,135 @@ +package test + +trait ObjectInterface + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMPlain + def first(): String + +trait SAMPlainWithOverriddenObjectMethods + def first(): String + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMPlainWithExtends extends ObjectInterface + def first(): String + +trait SAMPlainWithExtendsAndOverride extends ObjectInterface + def first(): String + override def equals(obj: Any): Boolean + override def hashCode(): Int + override def toString(): String + +trait SAMPlainCovariantOut[+O] + def first(): O + +trait SAMCovariantOut[+O] + def first(): O + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMCovariantOutExtends[+O] extends ObjectInterface + def first(): O + +trait SAMCovariantOutExtendsAndOverride[+O] extends ObjectInterface + def first(): O + override def equals(obj: Any): Boolean + override def hashCode(): Int + override def toString(): String + +trait SAMPlainContravariantIn[-I] + def first(in: I): Unit + +trait SAMContravariantIn[-I] + def first(in: I): Unit + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMContravariantInExtends[-I] extends ObjectInterface + def first(in: I): Unit + +trait SAMContravariantInExtendsAndOverride[-I] extends ObjectInterface + def first(in: I): Unit + override def equals(obj: Any): Boolean + override def hashCode(): Int + override def toString(): String + +trait SAMPlainInvariant[T] + def first(in: T): T + +trait SAMInvariant[T] + def first(in: T): T + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMInvariantExtends[T] extends ObjectInterface + def first(in: T): T + +trait SAMInvariantExtendsAndOverride[T] extends ObjectInterface + def first(in: T): T + override def equals(obj: Any): Boolean + override def hashCode(): Int + override def toString(): String + +trait SAMPlainInOut[-I, +O] + def first(in: I): O + +trait SAMInOut[-I, +O] + def first(in: I): O + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMInOutExtends[-I, +O] extends ObjectInterface + def first(in: I): O + +trait SAMInOutExtendsAndOverride[-I, +O] extends ObjectInterface + def first(in: I): O + override def equals(obj: Any): Boolean + override def hashCode(): Int + override def toString(): String + +type CustomString = String +type CustomBoolean = Boolean +type CustomInt = Int + +trait SAMWithCustomAliases + def first(): String + def equals(obj: Any): CustomBoolean + def hashCode(): CustomInt + def toString(): CustomString + +object Main + def main(args: Array[String]) = + val samPlain : SAMPlain = () => "Hello, World!" + val samPlainWithOverriddenObjectMethods: SAMPlainWithOverriddenObjectMethods = () => "Hello, World!" + val samPlainWithExtends : SAMPlainWithExtends = () => "Hello, World!" + val samPlainWithExtendsAndOverride : SAMPlainWithExtendsAndOverride = () => "Hello, World!" + + val samPlainCovariantOut : SAMPlainCovariantOut[_] = () => "Hello, World!" + val samCovariantOut : SAMCovariantOut[_] = () => "Hello, World!" + val samCovariantOutExtends : SAMCovariantOutExtends[_] = () => "Hello, World!" + val samCovariantOutExtendsAndOverride : SAMCovariantOutExtendsAndOverride[_] = () => "Hello, World!" + + val samPlainContravariantIn : SAMPlainContravariantIn[Int] = (x: Int) => () + val samContravariantIn : SAMContravariantIn[Int] = (x: Int) => () + val samContravariantInExtends : SAMContravariantInExtends[Int] = (x: Int) => () + val samContravariantInExtendsAndOverride : SAMContravariantInExtendsAndOverride[Int] = (x: Int) => () + + val samPlainInvariant : SAMPlainInvariant[String] = (x: String) => x + val samInvariant : SAMInvariant[String] = (x: String) => x + val samInvariantExtends : SAMInvariantExtends[String] = (x: String) => x + val samInvariantExtendsAndOverride : SAMInvariantExtendsAndOverride[String] = (x: String) => x + + val samPlainInOut : SAMPlainInOut[Int, String] = (x: Int) => x.toString + val samInOut : SAMInOut[Int, String] = (x: Int) => x.toString + val samInOutExtends : SAMInOutExtends[Int, String] = (x: Int) => x.toString + val samInOutExtendsAndOverride : SAMInOutExtendsAndOverride[Int, String] = (x: Int) => x.toString + + val samWithCustomAliases : SAMWithCustomAliases = () => "Hello, World!" diff --git a/tests/pos/i7519.scala b/tests/pos/i7519.scala new file mode 100644 index 000000000000..d05787834b3a --- /dev/null +++ b/tests/pos/i7519.scala @@ -0,0 +1,13 @@ +import scala.quoted._ +import scala.annotation.StaticAnnotation + +object Test { + class Annot extends StaticAnnotation + + class Quoted[T] + + inline def quote[T]: Quoted[T] = ${ quoteImpl[T] } + def quoteImpl[T: Type](given qctx: QuoteContext): Expr[Quoted[T]] = '{ + new Quoted[T @Annot] + } +} diff --git a/tests/pos/i7519b.scala b/tests/pos/i7519b.scala new file mode 100644 index 000000000000..f47c3ba0b8b1 --- /dev/null +++ b/tests/pos/i7519b.scala @@ -0,0 +1,13 @@ +import scala.quoted._ +import scala.annotation.StaticAnnotation + +class Annot(in: Int) extends StaticAnnotation + +class Quoted[T] + +inline def quote[T]: Quoted[T] = ${ quoteImpl[T] } + +def quoteImpl[T: Type](given qctx: QuoteContext): Expr[Quoted[T]] = { + val value: Expr[Int] = '{ 42 } + '{ new Quoted[T @Annot($value)] } +} diff --git a/tests/pos/i7525/Interface.java b/tests/pos/i7525/Interface.java new file mode 100644 index 000000000000..2b5b38e292c7 --- /dev/null +++ b/tests/pos/i7525/Interface.java @@ -0,0 +1,5 @@ +// Test static methods in interface can also have body. + +public interface Interface { + static void s() {} +} \ No newline at end of file diff --git a/tests/pos/i7532.scala b/tests/pos/i7532.scala new file mode 100644 index 000000000000..173dc7da8d9d --- /dev/null +++ b/tests/pos/i7532.scala @@ -0,0 +1,18 @@ + +class Tasty { + type Term + type Select <: Term + + given scala.reflect.ClassTag[Term] = ??? + given scala.reflect.ClassTag[Select] = ??? + object Select { + def unapply(x: Select): Boolean = ??? + } +} + +object Foo { + def impl(given tasty: Tasty): Unit = { + import tasty.{_, given} + val Select() = (??? : Term) + } +} \ No newline at end of file diff --git a/tests/pos/t3236/AnnotationTest.scala b/tests/pos/t3236/AnnotationTest.scala new file mode 100644 index 000000000000..c2f9ae7837f8 --- /dev/null +++ b/tests/pos/t3236/AnnotationTest.scala @@ -0,0 +1,33 @@ +trait AnnotationTest { + @BooleanAnnotation(Constants.BooleanTrue) + @ByteAnnotation(Constants.Byte) + @CharAnnotation(Constants.Char) + @ShortAnnotation(Constants.Short) + @IntAnnotation(Constants.Int) + @LongAnnotation(Constants.Long) + @FloatAnnotation(Constants.Float) + @DoubleAnnotation(Constants.Double) + @StringAnnotation(Constants.String) + def test1: Unit + + @BooleanAnnotation(Constants.InvertedBoolean) + @ByteAnnotation(Constants.NegativeByte) + @ShortAnnotation(Constants.NegativeShort) + @IntAnnotation(Constants.NegativeInt) + @LongAnnotation(Constants.NegativeLong) + @FloatAnnotation(Constants.NegativeFloat) + @DoubleAnnotation(Constants.NegativeDouble) + @StringAnnotation(Constants.NegativeString) + def test2: Unit + + @BooleanAnnotation(Constants.BooleanFalse) + @ByteAnnotation(Constants.LiteralCharAsByte) + @CharAnnotation(Constants.LiteralChar) + @ShortAnnotation(Constants.LiteralCharAsShort) + @IntAnnotation(Constants.LiteralCharAsInt) + @LongAnnotation(Constants.LiteralCharAsLong) + def test3: Unit + + @LongAnnotation(Constants.LiteralIntAsLong) + def test4: Unit +} diff --git a/tests/pos/t3236/BooleanAnnotation.java b/tests/pos/t3236/BooleanAnnotation.java new file mode 100644 index 000000000000..7e57a5e0dbd9 --- /dev/null +++ b/tests/pos/t3236/BooleanAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface BooleanAnnotation { + boolean value(); +} diff --git a/tests/pos/t3236/ByteAnnotation.java b/tests/pos/t3236/ByteAnnotation.java new file mode 100644 index 000000000000..c986fa5d27d5 --- /dev/null +++ b/tests/pos/t3236/ByteAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ByteAnnotation { + byte value(); +} diff --git a/tests/pos/t3236/CharAnnotation.java b/tests/pos/t3236/CharAnnotation.java new file mode 100644 index 000000000000..1715f1b7de39 --- /dev/null +++ b/tests/pos/t3236/CharAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CharAnnotation { + char value(); +} diff --git a/tests/pos/t3236/Constants.java b/tests/pos/t3236/Constants.java new file mode 100644 index 000000000000..16b4001f7694 --- /dev/null +++ b/tests/pos/t3236/Constants.java @@ -0,0 +1,34 @@ +public class Constants { + public static final boolean BooleanTrue = true; + public static final boolean BooleanFalse = false; + public static final boolean InvertedBoolean = !true; + + public static final byte Byte = 23; + public static final byte NegativeByte = -42; + public static final byte LiteralCharAsByte = 'a'; + + public static final char Char = 33; + public static final char LiteralChar = 'b'; + + public static final short Short = 0x1234; + public static final short NegativeShort= -0x5678; + public static final short LiteralCharAsShort = 'c'; + + public static final int Int = 0xabcdef; + public static final int NegativeInt = -12345678; + public static final int LiteralCharAsInt = 'd'; + + public static final long Long = 0x1234567890abcdefL; + public static final long NegativeLong = -0xfedcba09876L; + public static final long LiteralCharAsLong = 'e'; + public static final long LiteralIntAsLong = 0x12345678; + + public static final float Float = 42.232323f; + public static final float NegativeFloat = -3.1415f; + + public static final double Double = 23.4243598374594d; + public static final double NegativeDouble = -42.2324358934589734859d; + + public static final String String = "testConstant"; + public static final String NegativeString = "!#!$!grml%!%!$#@@@"; +} diff --git a/tests/pos/t3236/DoubleAnnotation.java b/tests/pos/t3236/DoubleAnnotation.java new file mode 100644 index 000000000000..1eb8223f4e58 --- /dev/null +++ b/tests/pos/t3236/DoubleAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface DoubleAnnotation { + double value(); +} diff --git a/tests/pos/t3236/FloatAnnotation.java b/tests/pos/t3236/FloatAnnotation.java new file mode 100644 index 000000000000..c723a25fada5 --- /dev/null +++ b/tests/pos/t3236/FloatAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface FloatAnnotation { + float value(); +} diff --git a/tests/pos/t3236/IntAnnotation.java b/tests/pos/t3236/IntAnnotation.java new file mode 100644 index 000000000000..2ffad8890cd3 --- /dev/null +++ b/tests/pos/t3236/IntAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface IntAnnotation { + int value(); +} diff --git a/tests/pos/t3236/LongAnnotation.java b/tests/pos/t3236/LongAnnotation.java new file mode 100644 index 000000000000..9f80b4139859 --- /dev/null +++ b/tests/pos/t3236/LongAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface LongAnnotation { + long value(); +} diff --git a/tests/pos/t3236/ShortAnnotation.java b/tests/pos/t3236/ShortAnnotation.java new file mode 100644 index 000000000000..f0a35892c750 --- /dev/null +++ b/tests/pos/t3236/ShortAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ShortAnnotation { + short value(); +} diff --git a/tests/pos/t3236/StringAnnotation.java b/tests/pos/t3236/StringAnnotation.java new file mode 100644 index 000000000000..0fdc1ead3815 --- /dev/null +++ b/tests/pos/t3236/StringAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface StringAnnotation { + String value(); +} diff --git a/tests/pos/t3236/Test.scala b/tests/pos/t3236/Test.scala new file mode 100644 index 000000000000..efb24119d92c --- /dev/null +++ b/tests/pos/t3236/Test.scala @@ -0,0 +1,44 @@ +import scala.reflect.Selectable.reflectiveSelectable + +object Test extends App { + val theClass = classOf[AnnotationTest] + + def annotation[T <: java.lang.annotation.Annotation](annotationClass: Class[T], methodName: String): T = + theClass.getDeclaredMethod(methodName) + .getAnnotation[T](annotationClass) + + def check[T, U <: java.lang.annotation.Annotation & { def value(): T } ](annotationClass: Class[U], methodName: String, expected: T): Unit = { + val a = annotation(annotationClass, methodName) + assert(a != null, s"No annotation of type $annotationClass found on method $methodName") + assert(a.value() == expected, s"Actual value of annotation $a on $methodName was not of expected value $expected") + } + + check(classOf[BooleanAnnotation], "test1", Constants.BooleanTrue) + check(classOf[ByteAnnotation], "test1", Constants.Byte) + check(classOf[CharAnnotation], "test1", Constants.Char) + check(classOf[ShortAnnotation], "test1", Constants.Short) + check(classOf[IntAnnotation], "test1", Constants.Int) + check(classOf[LongAnnotation], "test1", Constants.Long) + check(classOf[FloatAnnotation], "test1", Constants.Float) + check(classOf[DoubleAnnotation], "test1", Constants.Double) + check(classOf[StringAnnotation], "test1", Constants.String) + + check(classOf[BooleanAnnotation], "test2", Constants.InvertedBoolean) + check(classOf[ByteAnnotation], "test2", Constants.NegativeByte) + // no negative char possible + check(classOf[ShortAnnotation], "test2", Constants.NegativeShort) + check(classOf[IntAnnotation], "test2", Constants.NegativeInt) + check(classOf[LongAnnotation], "test2", Constants.NegativeLong) + check(classOf[FloatAnnotation], "test2", Constants.NegativeFloat) + check(classOf[DoubleAnnotation], "test2", Constants.NegativeDouble) + check(classOf[StringAnnotation], "test2", Constants.NegativeString) + + check(classOf[BooleanAnnotation], "test3", Constants.BooleanFalse) + check(classOf[ByteAnnotation], "test3", Constants.LiteralCharAsByte) + check(classOf[CharAnnotation], "test3", Constants.LiteralChar) + check(classOf[ShortAnnotation], "test3", Constants.LiteralCharAsShort) + check(classOf[IntAnnotation], "test3", Constants.LiteralCharAsInt) + check(classOf[LongAnnotation], "test3", Constants.LiteralCharAsLong) + + check(classOf[LongAnnotation], "test4", Constants.LiteralIntAsLong) +} diff --git a/tests/run-macros/i7519c.check b/tests/run-macros/i7519c.check new file mode 100644 index 000000000000..0d41584485bd --- /dev/null +++ b/tests/run-macros/i7519c.check @@ -0,0 +1 @@ +new Quoted[scala.Int @Annot(42)]() diff --git a/tests/run-macros/i7519c/Macro_1.scala b/tests/run-macros/i7519c/Macro_1.scala new file mode 100644 index 000000000000..586a6d39006e --- /dev/null +++ b/tests/run-macros/i7519c/Macro_1.scala @@ -0,0 +1,13 @@ +import scala.quoted._ +import scala.annotation.StaticAnnotation + +class Annot(in: Int) extends StaticAnnotation + +class Quoted[T] + +inline def quote[T]: String = ${ quoteImpl[T] } + +def quoteImpl[T: Type](given qctx: QuoteContext): Expr[String] = { + val value: Expr[Int] = '{ 42 } + Expr(('{ new Quoted[T @Annot($value)] }).show) +} diff --git a/tests/run-macros/i7519c/Test_2.scala b/tests/run-macros/i7519c/Test_2.scala new file mode 100644 index 000000000000..9e77d5871ebe --- /dev/null +++ b/tests/run-macros/i7519c/Test_2.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + println(quote[Int]) + } +} diff --git a/tests/run-macros/tasty-seal-method/quoted_1.scala b/tests/run-macros/tasty-seal-method/quoted_1.scala index c8c000fdf21a..f20333392d3f 100644 --- a/tests/run-macros/tasty-seal-method/quoted_1.scala +++ b/tests/run-macros/tasty-seal-method/quoted_1.scala @@ -14,10 +14,10 @@ object Asserts { fn.tpe.widen match { case IsMethodType(_) => args.size match { - case 0 => Expr.betaReduce(fn.seal.cast[() => Int])() - case 1 => Expr.betaReduce(fn.seal.cast[Int => Int])('{0}) - case 2 => Expr.betaReduce(fn.seal.cast[(Int, Int) => Int])('{0}, '{0}) - case 3 => Expr.betaReduce(fn.seal.cast[(Int, Int, Int) => Int])('{0}, '{0}, '{0}) + case 0 => Expr.betaReduce(fn.etaExpand.seal.cast[() => Int])() + case 1 => Expr.betaReduce(fn.etaExpand.seal.cast[Int => Int])('{0}) + case 2 => Expr.betaReduce(fn.etaExpand.seal.cast[(Int, Int) => Int])('{0}, '{0}) + case 3 => Expr.betaReduce(fn.etaExpand.seal.cast[(Int, Int, Int) => Int])('{0}, '{0}, '{0}) } } case _ => x @@ -35,10 +35,10 @@ object Asserts { case Apply(fn, args) => val pre = rec(fn) args.size match { - case 0 => Expr.betaReduce(pre.seal.cast[() => Any])().unseal - case 1 => Expr.betaReduce(pre.seal.cast[Int => Any])('{0}).unseal - case 2 => Expr.betaReduce(pre.seal.cast[(Int, Int) => Any])('{0}, '{0}).unseal - case 3 => Expr.betaReduce(pre.seal.cast[(Int, Int, Int) => Any])('{0}, '{0}, '{0}).unseal + case 0 => Expr.betaReduce(pre.etaExpand.seal.cast[() => Any])().unseal + case 1 => Expr.betaReduce(pre.etaExpand.seal.cast[Int => Any])('{0}).unseal + case 2 => Expr.betaReduce(pre.etaExpand.seal.cast[(Int, Int) => Any])('{0}, '{0}).unseal + case 3 => Expr.betaReduce(pre.etaExpand.seal.cast[(Int, Int, Int) => Any])('{0}, '{0}, '{0}).unseal } case _ => term } diff --git a/tests/run/i5094.scala b/tests/run/i5094.scala new file mode 100644 index 000000000000..556a1f0f07df --- /dev/null +++ b/tests/run/i5094.scala @@ -0,0 +1,17 @@ +trait IO { + def c(x: Int): Int = ??? +} +trait SO extends IO { + override final def c(x: Int): Int = ??? +} +trait SOIO extends IO { + override def c(x: Int): Int = ??? +} +trait SOSO extends SOIO with SO +abstract class AS extends SO +class L extends AS with SOSO +object Test { + def main(args: Array[String]): Unit = { + new L + } +} diff --git a/tests/run/i7359.check b/tests/run/i7359.check new file mode 100644 index 000000000000..5e552d403755 --- /dev/null +++ b/tests/run/i7359.check @@ -0,0 +1,6 @@ +o +o +() +i +10 +o diff --git a/tests/run/i7359.scala b/tests/run/i7359.scala new file mode 100644 index 000000000000..ac2a2769162b --- /dev/null +++ b/tests/run/i7359.scala @@ -0,0 +1,46 @@ +trait ObjectInterface + def equals(obj: Any): Boolean + def hashCode(): Int + def toString(): String + +trait SAMPlainWithExtends extends ObjectInterface + def first(): String + +trait SAMCovariantOutExtends[+O] extends ObjectInterface + def first(): O + +trait SAMContravariantInExtends[-I] extends ObjectInterface + def first(in: I): Unit + +trait SAMInvariantExtends[T] extends ObjectInterface + def first(in: T): T + +trait SAMInOutExtends[-I, +O] extends ObjectInterface + def first(in: I): O + +type CustomString = String +type CustomBoolean = Boolean +type CustomInt = Int + +trait SAMWithCustomAliases + def first(): String + def equals(obj: Any): CustomBoolean + def hashCode(): CustomInt + def toString(): CustomString + +object Test + + def main(args: Array[String]): Unit = + val samPlainWithExtends : SAMPlainWithExtends = () => "o" + val samCovariantOutExtends : SAMCovariantOutExtends[_] = () => "o" + val samContravariantInExtends : SAMContravariantInExtends[Int] = (x: Int) => () + val samInvariantExtends : SAMInvariantExtends[String] = (x: String) => x + val samInOutExtends : SAMInOutExtends[Int, String] = (x: Int) => x.toString + val samWithCustomAliases : SAMWithCustomAliases = () => "o" + + println(samPlainWithExtends.first()) + println(samCovariantOutExtends.first()) + println(samContravariantInExtends.first(10)) + println(samInvariantExtends.first("i")) + println(samInOutExtends.first(10)) + println(samWithCustomAliases.first()) From 49a35e3a96760750f1b52aca3edb7a732b4298ad Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Nov 2019 16:47:05 -0500 Subject: [PATCH 05/39] rewrite widenUnion --- .../src/dotty/tools/dotc/core/Types.scala | 53 ++++++------------- .../pos/dont-widen-singleton.scala | 9 ---- .../pos/widen-nullable-union.scala | 36 +++++++++++++ 3 files changed, 51 insertions(+), 47 deletions(-) delete mode 100644 tests/explicit-nulls/pos/dont-widen-singleton.scala create mode 100644 tests/explicit-nulls/pos/widen-nullable-union.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f1b3beaae39a..6cc279d69566 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1105,44 +1105,18 @@ object Types { def widenUnion(implicit ctx: Context): Type = { widen match { 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 - } - - // 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. - // 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 { - val lhsWiden = lhs.widenUnion - 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 - } + tp match { + case OrNull(tp1) => + // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + val tp1Widen = tp1.widenUnion + if (tp1Widen.isRef(defn.AnyClass)) tp1Widen + else tp.derivedOrType(tp1Widen, defn.NullType) + case _ => + ctx.typeComparer.lub(lhs.widenUnion, rhs.widenUnion, canConstrain = true) match { + case union: OrType => union.join + case res => res + } } - else defaultJoin(lhs.widenUnion, rhs.widenUnion) case tp @ AndType(tp1, tp2) => tp derived_& (tp1.widenUnion, tp2.widenUnion) case tp: RefinedType => @@ -2965,9 +2939,12 @@ object Types { object OrNull { def apply(tp: Type)(given Context) = OrType(tp, defn.NullType) - def unapply(tp: Type)(given Context): Option[Type] = + def unapply(tp: Type)(given ctx: Context): Option[Type] = + if (ctx.explicitNulls) { val tp1 = tp.stripNull if tp1 ne tp then Some(tp1) else None + } + else None } object OrJavaNull { diff --git a/tests/explicit-nulls/pos/dont-widen-singleton.scala b/tests/explicit-nulls/pos/dont-widen-singleton.scala deleted file mode 100644 index bcd3df969e23..000000000000 --- a/tests/explicit-nulls/pos/dont-widen-singleton.scala +++ /dev/null @@ -1,9 +0,0 @@ - -// 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/widen-nullable-union.scala b/tests/explicit-nulls/pos/widen-nullable-union.scala new file mode 100644 index 000000000000..04bec3ab3024 --- /dev/null +++ b/tests/explicit-nulls/pos/widen-nullable-union.scala @@ -0,0 +1,36 @@ +// Test that we correctly handle nullable unions when widening. +// We keep nullable after widening. +class Test { + class A + class B + class C extends A + + locally { + val x: String|Null = ??? + val y = x // String|Null is inferred, this used to crash the compiler + } + + locally { + val x: (Int | Null) | String = ??? + val y = x + val _: Any = y + } + + locally { + val x: (A | Null) | B = ??? + val y = x + val _: AnyRef | Null = y + } + + locally { + val x: A | (Null | C) = ??? + val y = x // after simplification before widenUnion, the type of x would become A | Null + val _: A | Null = y + } + + locally { + val x: (A | Null) | (Null | B) = ??? + val y = x + val _: AnyRef | Null = y + } +} From 2df913f689f46b046237102cc220b63c7b3329ec Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Nov 2019 17:06:54 -0500 Subject: [PATCH 06/39] Update types in DottyPredef --- library/src/dotty/DottyPredef.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 046ae8e6a739..a82f8afa680f 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -48,22 +48,21 @@ object DottyPredef { * * Note that `.nn` performs a checked cast, so if invoked on a null value it'll throw an NPE. */ - def[T] (x: T|Null) nn: T = + def[T] (x: T|Null) nn: x.type & T = if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") - else x.asInstanceOf[T] + else x.asInstanceOf[x.type & 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)) + x.asInstanceOf[AnyRef] eq y.asInstanceOf[AnyRef] /** 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)) - + x.asInstanceOf[AnyRef] ne y.asInstanceOf[AnyRef] } From f009775f4c6fc67f6a62ab67db23cbbecb0a281a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2019 15:49:56 -0500 Subject: [PATCH 07/39] add comments for extractors --- .../tools/dotc/core/NullOpsDecorator.scala | 3 --- compiler/src/dotty/tools/dotc/core/Types.scala | 18 +++++++++++++++--- .../tools/dotc/interactive/Completion.scala | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 81f5b5d2a125..b87a78f0c154 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -8,9 +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) - /** Is this type exactly `JavaNull` (no vars, aliases, refinements etc allowed)? */ def isJavaNullType(implicit ctx: Context): Boolean = { assert(ctx.explicitNulls) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6cc279d69566..66a12197d389 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1445,9 +1445,7 @@ object Types { } /** Is this (an alias of) the `scala.Null` type? */ - final def isNull(given Context) = - isRef(defn.NullClass) - || classSymbol.name == tpnme.Null // !!! temporary kludge for being able to test without the explicit nulls PR + final def isNullType(given Context) = isRef(defn.NullClass) /** The resultType of a LambdaType, or ExprType, the type itself for others */ def resultType(implicit ctx: Context): Type = this @@ -2936,6 +2934,13 @@ object Types { else apply(tp1, tp2) } + /** An extractor object to pattern match against a nullable union. + * e.g. + * + * (tp: Type) match + * case OrNull(tp1) => // tp had the form `tp1 | Null` + * case _ => // tp was not a nullable union + */ object OrNull { def apply(tp: Type)(given Context) = OrType(tp, defn.NullType) @@ -2947,6 +2952,13 @@ object Types { else None } + /** An extractor object to pattern match against a Java-nullable union. + * e.g. + * + * (tp: Type) match + * case OrJavaNull(tp1) => // tp had the form `tp1 | JavaNull` + * case _ => // tp was not a Java-nullable union + */ object OrJavaNull { def apply(tp: Type)(given Context) = OrType(tp, defn.JavaNullAliasType) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index ef573fb7a1df..1785164eff8f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -207,7 +207,7 @@ object Completion { def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = if (!qual.tpe.widenDealias.isBottomType) { addAccessibleMembers(qual.tpe) - if (!mode.is(Mode.Import) && !qual.tpe.isNull) + if (!mode.is(Mode.Import) && !qual.tpe.isNullType) // Implicit conversions do not kick in when importing // and for `NullClass` they produce unapplicable completions (for unclear reasons) implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) From 56cdadccd6f0126b49fd7b17f01240a03e9a179d Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2019 16:15:56 -0500 Subject: [PATCH 08/39] optimize widenUnion --- .../dotty/tools/dotc/core/Definitions.scala | 14 ++---- .../src/dotty/tools/dotc/core/Types.scala | 48 +++++++++---------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 52ae52b857ea..62f335e89cd2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -295,16 +295,8 @@ class Definitions { @tu lazy val AnyRefAlias: TypeSymbol = enterAliasType(tpnme.AnyRef, ObjectType) def AnyRefType: TypeRef = AnyRefAlias.typeRef - @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. - 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. - enterMethod(ObjectClass, nme.ne, methOfAnyRefOrNull(BooleanType), Final) - } + @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_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) @@ -839,7 +831,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.maybeNullable), tp) + def methOfAnyRef(tp: Type): MethodType = MethodType(List(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 66a12197d389..94b38252eacf 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1102,30 +1102,30 @@ 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(lhs, rhs) => - tp match { - case OrNull(tp1) => - // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. - val tp1Widen = tp1.widenUnion - if (tp1Widen.isRef(defn.AnyClass)) tp1Widen - else tp.derivedOrType(tp1Widen, defn.NullType) - case _ => - ctx.typeComparer.lub(lhs.widenUnion, rhs.widenUnion, canConstrain = true) match { - case union: OrType => union.join - case res => res - } - } - 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 - } + def widenUnion(implicit ctx: Context): Type = widen match { + case tp @ OrNull(tp1): OrType => + // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. + val tp1Widen = tp1.widenUnion + if (tp1Widen.isRef(defn.AnyClass)) tp1Widen + else tp.derivedOrType(tp1Widen, defn.NullType) + case tp => + tp.widenUnionWithoutNull + } + + def widenUnionWithoutNull(implicit ctx: Context): Type = widen match { + case tp @ OrType(lhs, rhs) => + ctx.typeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { + case union: OrType => union.join + case res => res + } + case tp @ AndType(tp1, tp2) => + tp derived_& (tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull) + 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 5c7c312019babd77e836d30c3949ed477aa45232 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2019 17:07:20 -0500 Subject: [PATCH 09/39] modify eq tests --- library/src/dotty/DottyPredef.scala | 14 ---------- tests/explicit-nulls/neg/eq.scala | 17 ++++++++---- tests/explicit-nulls/neg/eq2.scala | 9 ++++--- tests/explicit-nulls/pos/ref-eq.scala | 31 ---------------------- tests/explicit-nulls/run/eq.scala | 24 +++++++++++++++++ tests/explicit-nulls/run/subtype-any.scala | 28 ------------------- 6 files changed, 42 insertions(+), 81 deletions(-) delete mode 100644 tests/explicit-nulls/pos/ref-eq.scala create mode 100644 tests/explicit-nulls/run/eq.scala delete mode 100644 tests/explicit-nulls/run/subtype-any.scala diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index a82f8afa680f..69040aef4961 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -51,18 +51,4 @@ object DottyPredef { def[T] (x: T|Null) nn: x.type & T = if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") else x.asInstanceOf[x.type & 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.asInstanceOf[AnyRef] eq y.asInstanceOf[AnyRef] - - /** 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.asInstanceOf[AnyRef] ne y.asInstanceOf[AnyRef] } diff --git a/tests/explicit-nulls/neg/eq.scala b/tests/explicit-nulls/neg/eq.scala index 970f4ec811a8..210c77f4d2a3 100644 --- a/tests/explicit-nulls/neg/eq.scala +++ b/tests/explicit-nulls/neg/eq.scala @@ -3,6 +3,7 @@ class Foo { // Null itself val x0: Null = null x0 == null + x0 != null null == x0 null == null @@ -10,17 +11,23 @@ class Foo { val x1: String|Null = null x1 == null null == x1 + x1 == x0 + x1 != x0 + x0 == x1 // Reference types, even non-nullable ones: OK. // Allowed as an escape hatch. val x2: String = "hello" - x2 != null + x2 != null x2 == null null == x2 // Value types: not allowed. - 1 == null // error - null == 1 // error - true == null // error - null == true // error + 1 == null // error + null != 0 // error + null == 0 // error + true == null // error + null == false // error + 'a' == null // error + null == 'b' // error } diff --git a/tests/explicit-nulls/neg/eq2.scala b/tests/explicit-nulls/neg/eq2.scala index 13f3e4e7f46a..8c730407daa4 100644 --- a/tests/explicit-nulls/neg/eq2.scala +++ b/tests/explicit-nulls/neg/eq2.scala @@ -1,15 +1,18 @@ - // 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 (foo == null) {} // error: Values of types Null and Foo cannot be compared if (null == foo) {} // error + 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 (foo2 == null) {} if (null == foo2) {} + if (foo2 != null) {} + if (null != foo2) {} } diff --git a/tests/explicit-nulls/pos/ref-eq.scala b/tests/explicit-nulls/pos/ref-eq.scala deleted file mode 100644 index 340c687c7d70..000000000000 --- a/tests/explicit-nulls/pos/ref-eq.scala +++ /dev/null @@ -1,31 +0,0 @@ -// 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/run/eq.scala b/tests/explicit-nulls/run/eq.scala new file mode 100644 index 000000000000..f116a1f365c1 --- /dev/null +++ b/tests/explicit-nulls/run/eq.scala @@ -0,0 +1,24 @@ + +object Test { + + def main(args: Array[String]): Unit = { + val x: String|Null = "" + val y: String|Null = null + val z: String = "" + + assert(x == x) + assert(x == "") + assert("" == x) + assert(x == z) + assert(z == x) + assert(x != "xx") + assert(x != y) + assert(y == y) + + assert(x != null) + assert(null != x) + assert(y == null) + assert(null == y) + assert(null == null) + } +} diff --git a/tests/explicit-nulls/run/subtype-any.scala b/tests/explicit-nulls/run/subtype-any.scala deleted file mode 100644 index 31a5bb66f092..000000000000 --- a/tests/explicit-nulls/run/subtype-any.scala +++ /dev/null @@ -1,28 +0,0 @@ - -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)) - } -} From 5ddcab68d0abd84b1b78b6db9507006e09c48d90 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2019 17:37:30 -0500 Subject: [PATCH 10/39] remove redundent test --- tests/pos/notNull.scala | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 tests/pos/notNull.scala diff --git a/tests/pos/notNull.scala b/tests/pos/notNull.scala deleted file mode 100644 index 3d46fe658948..000000000000 --- a/tests/pos/notNull.scala +++ /dev/null @@ -1,22 +0,0 @@ -trait Null extends Any -object Test with - def notNull[A](x: A | Null): x.type & A = - assert(x != null) - x.asInstanceOf // TODO: drop the .asInstanceOf when explicit nulls are implemented - - locally { - val x: (Int | Null) = ??? - val y = x; val _: Int | Null = y - } - locally { - val x: Int | Null = ??? - val y = notNull(identity(x)); val yc: Int = y - val z = notNull(x); val zc: Int = z - } - class C { type T } - locally { - val x: C { type T = Int } = new C { type T = Int } - val xnn: x.type & C { type T = Int } = notNull(x) - val y: xnn.T = 33 - val z = y; val zc: Int = z - } From 334a4305438e0203cd6a4df0e176389483397b90 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 21 Nov 2019 17:22:25 -0500 Subject: [PATCH 11/39] fix normalizing nullable intersection type --- .../tools/dotc/core/NullOpsDecorator.scala | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index b87a78f0c154..c682237e78e3 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -31,19 +31,31 @@ object NullOpsDecorator { * have been removed. Then the result is `self2 | Null` (`self2 | JavaNull`). */ def normNullableUnion(implicit ctx: Context): Type = { - var isUnion = false var hasNull = false var hasJavaNull = false def strip(tp: Type): Type = tp match { 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 tp.derivedOrType(llhs, rrhs) case tp @ AndType(tp1, tp2) => - tp.derivedAndType(strip(tp1), strip(tp2)) + // We cannot `tp.derivedAndType(strip(tp1), strip(tp2))` directly, + // since `normNullableUnion((A | Null) & B)` would produce the wrong + // result `(A & B) | Null`. + val oldHN = hasNull + val oldHJN = hasJavaNull + val tp1s = strip(tp1) + val tp2s = strip(tp2) + if((tp1s ne tp1) && (tp2s ne tp2)) + tp.derivedAndType(tp1s, tp2s) + else + // If tp1 or tp2 is not nullable, we should revert the change of + // `hasNull` and `hasJavaNull` and return the original tp. + hasNull = oldHN + hasJavaNull = oldHJN + tp case _ => if (tp.isNullType) { if (tp.isJavaNullType) hasJavaNull = true @@ -52,7 +64,7 @@ object NullOpsDecorator { tp } val tp = strip(self) - if (!isUnion) self + if (tp eq self) self else if (hasJavaNull) OrType(tp, defn.JavaNullAliasType) else if (hasNull) OrType(tp, defn.NullType) else self From b9cbc1ff034dba260913eb7478a8d64c6a4f8a82 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 28 Nov 2019 16:23:41 -0500 Subject: [PATCH 12/39] remove JavaEnumValue from AfterLoadFlags --- .../src/dotty/tools/dotc/core/Flags.scala | 7 +------ .../tools/dotc/core/JavaNullInterop.scala | 4 ++-- .../dotc/core/classfile/ClassfileParser.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../pos-separate/interop-enum-src/Day_1.java | 6 ++++++ .../interop-enum-src/Planet_2.java | 19 +++++++++++++++++++ .../pos-separate/interop-enum-src/S_3.scala | 6 ++++++ .../pos/widen-nullable-union.scala | 6 ++++++ 8 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 tests/explicit-nulls/pos-separate/interop-enum-src/Day_1.java create mode 100644 tests/explicit-nulls/pos-separate/interop-enum-src/Planet_2.java create mode 100644 tests/explicit-nulls/pos-separate/interop-enum-src/S_3.scala diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 4b1e30157005..67abc50fc18e 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -450,12 +450,7 @@ object Flags { * is completed) */ val AfterLoadFlags: FlagSet = commonFlags( - FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, - // 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) + FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined) /** 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/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 5137fe213828..10a2b4b78a02 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -52,12 +52,12 @@ object JavaNullInterop { * * But the selection can throw an NPE if the returned value is `null`. */ - def nullifyMember(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { + def nullifyMember(sym: Symbol, tp: Type, isEnum: Boolean)(implicit ctx: Context): Type = { assert(ctx.explicitNulls) assert(sym.is(JavaDefined), "can only nullify java-defined members") // Some special cases when nullifying the type - if (sym.name == nme.TYPE_ || sym.isAllOf(Flags.JavaEnumValue)) + if (isEnum || sym.name == nme.TYPE_) // Don't nullify the `TYPE` field in every class and Java enum instances tp else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym)) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 59c49dabb5d3..b76810680db9 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -287,7 +287,7 @@ 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) + if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info, isEnum) // seal java enums if (isEnum) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index abc607a94374..63da5f461310 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1421,7 +1421,7 @@ class Namer { typer: Typer => } val memTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) - JavaNullInterop.nullifyMember(sym, memTpe) + JavaNullInterop.nullifyMember(sym, memTpe, mdef.mods.isAllOf(JavaEnumValue)) else memTpe } diff --git a/tests/explicit-nulls/pos-separate/interop-enum-src/Day_1.java b/tests/explicit-nulls/pos-separate/interop-enum-src/Day_1.java new file mode 100644 index 000000000000..b5d96e446fa8 --- /dev/null +++ b/tests/explicit-nulls/pos-separate/interop-enum-src/Day_1.java @@ -0,0 +1,6 @@ + +public enum Day_1 { + SUN, + MON, + TUE +} diff --git a/tests/explicit-nulls/pos-separate/interop-enum-src/Planet_2.java b/tests/explicit-nulls/pos-separate/interop-enum-src/Planet_2.java new file mode 100644 index 000000000000..c46e92d13bea --- /dev/null +++ b/tests/explicit-nulls/pos-separate/interop-enum-src/Planet_2.java @@ -0,0 +1,19 @@ +public enum Planet_2 { + 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_2(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-separate/interop-enum-src/S_3.scala b/tests/explicit-nulls/pos-separate/interop-enum-src/S_3.scala new file mode 100644 index 000000000000..3c5c8cd451ae --- /dev/null +++ b/tests/explicit-nulls/pos-separate/interop-enum-src/S_3.scala @@ -0,0 +1,6 @@ + +// Verify that enum values aren't nullified. +class S { + val d: Day_1 = Day_1.MON + val p: Planet_2 = Planet_2.MARS +} diff --git a/tests/explicit-nulls/pos/widen-nullable-union.scala b/tests/explicit-nulls/pos/widen-nullable-union.scala index 04bec3ab3024..76cb13507154 100644 --- a/tests/explicit-nulls/pos/widen-nullable-union.scala +++ b/tests/explicit-nulls/pos/widen-nullable-union.scala @@ -33,4 +33,10 @@ class Test { val y = x val _: AnyRef | Null = y } + + locally { + val x: (A | Null) & (Null | B) = ??? + val y = x + val _: (A & B) | Null = y + } } From 2a8cc49deac667ca06e042911117d59fccb1e505 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2019 16:09:56 -0500 Subject: [PATCH 13/39] Disallow comparison between object and null --- .../tools/dotc/core/JavaNullInterop.scala | 4 +- .../dotty/tools/dotc/typer/Implicits.scala | 19 ++++-- .../dotty/tools/dotc/CompilationTests.scala | 1 - tests/explicit-nulls/neg/eq.scala | 33 ++++++--- tests/explicit-nulls/run/eq.scala | 2 + tests/pos-special/nullable.scala | 67 ------------------- 6 files changed, 41 insertions(+), 85 deletions(-) delete mode 100644 tests/pos-special/nullable.scala diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 10a2b4b78a02..94f43ef8091c 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -52,12 +52,12 @@ object JavaNullInterop { * * But the selection can throw an NPE if the returned value is `null`. */ - def nullifyMember(sym: Symbol, tp: Type, isEnum: Boolean)(implicit ctx: Context): Type = { + def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(implicit ctx: Context): Type = { assert(ctx.explicitNulls) assert(sym.is(JavaDefined), "can only nullify java-defined members") // Some special cases when nullifying the type - if (isEnum || sym.name == nme.TYPE_) + if (isEnumValueDef || sym.name == nme.TYPE_) // Don't nullify the `TYPE` field in every class and Java enum instances tp else if (sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym)) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 084af34ddfc1..ae9d1b7129a3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -813,9 +813,20 @@ trait Implicits { self: Typer => else if (cls2.isPrimitiveValueClass) cmpWithBoxed(cls2, cls1) else if (cls1 == defn.NullClass) - cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) - else if (cls2 == defn.NullClass) - cls1.derivesFrom(defn.ObjectClass) + cls1 == cls2 + else if (!ctx.explicitNulls) + // If explicit nulls is enabled, we want to disallow comparison between Object and Null. + // If a nullable value has a non-nullable type, we can still cast it to nullable type + // then compare. + // + // Example: + // val x: String = null.asInstanceOf[String] + // if (x == null) {} // error: x is non-nullable + // if (x.asInstanceOf[String|Null] == null) {} // ok + if (cls1 == defn.NullClass) + cls2.derivesFrom(defn.ObjectClass) + else + cls2 == defn.NullClass && cls1.derivesFrom(defn.ObjectClass) else false } @@ -1464,7 +1475,7 @@ trait Implicits { self: Typer => case alt1: SearchSuccess => var diff = compareCandidate(alt1, alt2.ref, alt2.level) assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank` - + if diff == 0 then // Fall back: if both results are extension method applications, // compare the extension methods instead of their wrappers. diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index afe4d6ed8dee..6d64ad07ced5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -59,7 +59,6 @@ class CompilationTests extends ParallelTesting { compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40")), compileFile("tests/pos-special/indent-colons.scala", defaultOptions.and("-Yindent-colons")), compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")), - compileFile("tests/pos-special/nullable.scala", defaultOptions.and("-Yexplicit-nulls")), compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")), compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")), compileFile("tests/pos-special/i7575.scala", defaultOptions.and("-language:dynamics")), diff --git a/tests/explicit-nulls/neg/eq.scala b/tests/explicit-nulls/neg/eq.scala index 210c77f4d2a3..3fab8ba4bf8a 100644 --- a/tests/explicit-nulls/neg/eq.scala +++ b/tests/explicit-nulls/neg/eq.scala @@ -2,25 +2,36 @@ class Foo { // Null itself val x0: Null = null + x0 != x0 x0 == null x0 != null null == x0 null == null + null != null - // Nullable types: OK - val x1: String|Null = null - x1 == null - null == x1 - x1 == x0 - x1 != x0 - x0 == x1 + // Non-nullable types: error + val x1: String = "hello" + x1 != null // error + x1 == null // error + null == x1 // error + null != x1 // error + x1 == x0 // error + x0 != x1 // error + x1.asInstanceOf[String|Null] == null + x1.asInstanceOf[String|Null] == x0 + x1.asInstanceOf[Any] == null + x1.asInstanceOf[Any] == x0 - // Reference types, even non-nullable ones: OK. - // Allowed as an escape hatch. - val x2: String = "hello" - x2 != null + // Nullable types: OK + val x2: String|Null = null x2 == null null == x2 + x2 == x0 + x2 != x0 + x0 == x2 + x2 == x1 + x2 != x1 + x1 == x2 // Value types: not allowed. 1 == null // error diff --git a/tests/explicit-nulls/run/eq.scala b/tests/explicit-nulls/run/eq.scala index f116a1f365c1..42f0a86a1a3e 100644 --- a/tests/explicit-nulls/run/eq.scala +++ b/tests/explicit-nulls/run/eq.scala @@ -14,6 +14,8 @@ object Test { assert(x != "xx") assert(x != y) assert(y == y) + assert(z.asInstanceOf[String|Null] != null) + assert(z.asInstanceOf[Any] != null) assert(x != null) assert(null != x) diff --git a/tests/pos-special/nullable.scala b/tests/pos-special/nullable.scala deleted file mode 100644 index 72b3e5e60adb..000000000000 --- a/tests/pos-special/nullable.scala +++ /dev/null @@ -1,67 +0,0 @@ -trait T { def f: Int } -def impossible(x: Any): Unit = - val y = x - -def test: Unit = - val x, x2, x3, x4 = "" - - if x != null then - if x == null then impossible(new T{}) - - if x == null then () - else - if x == null then impossible(new T{}) - - if x == null || { - if x == null then impossible(new T{}) - true - } - then () - - if x != null && { - if x == null then impossible(new T{}) - true - } - then () - - if !(x == null) && { - if x == null then impossible(new T{}) - true - } - then () - - x match - case _: String => - if x == null then impossible(new T{}) - - val y: Any = List(x) - y match - case y1 :: ys => if y == null then impossible(new T{}) - case Some(_) | Seq(_: _*) => if y == null then impossible(new T{}) - - x match - case null => - case _ => if x == null then impossible(new T{}) - - if x == null then return - if x == null then impossible(new T{}) - - if x2 == null then throw AssertionError() - if x2 == null then impossible(new T{}) - - if !(x3 != null) then throw AssertionError() - if x3 == null then impossible(new T{}) - - assert(x4 != null) - if x4 == null then impossible(new T{}) - - // TODO: uncomment the test below after implementing `notNull` - // class C(val x: Int, val next: C) - // var xs: C = C(1, C(2, null)) - // while xs != null do - // if xs == null then println("?") - // // looking at this with -Xprint-frontend -Xprint-types shows that the - // // type of `xs == null` is indeed `false`. We cannot currently use this in a test - // // since `xs == null` is not technically a pure expression since `xs` is not a path. - // // We should test variable tracking once this is integrated with explicit not null types. - // xs = xs.next From 423ef595a378ddc6093f7ae196e9a790474d07c7 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 2 Dec 2019 13:12:06 -0500 Subject: [PATCH 14/39] Fix case process --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ae9d1b7129a3..1545bffbc52f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -812,8 +812,8 @@ trait Implicits { self: Typer => cmpWithBoxed(cls1, cls2) else if (cls2.isPrimitiveValueClass) cmpWithBoxed(cls2, cls1) - else if (cls1 == defn.NullClass) - cls1 == cls2 + else if (cls1 == defn.NullClass && cls1 == cls2) + true else if (!ctx.explicitNulls) // If explicit nulls is enabled, we want to disallow comparison between Object and Null. // If a nullable value has a non-nullable type, we can still cast it to nullable type From b12415fe705345f7233fe399ba585df27bfb3420 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 2 Dec 2019 18:08:30 -0500 Subject: [PATCH 15/39] Move explicit null case --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1545bffbc52f..5468b71cf22f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -812,9 +812,7 @@ trait Implicits { self: Typer => cmpWithBoxed(cls1, cls2) else if (cls2.isPrimitiveValueClass) cmpWithBoxed(cls2, cls1) - else if (cls1 == defn.NullClass && cls1 == cls2) - true - else if (!ctx.explicitNulls) + else if (ctx.explicitNulls) // If explicit nulls is enabled, we want to disallow comparison between Object and Null. // If a nullable value has a non-nullable type, we can still cast it to nullable type // then compare. @@ -823,10 +821,11 @@ trait Implicits { self: Typer => // val x: String = null.asInstanceOf[String] // if (x == null) {} // error: x is non-nullable // if (x.asInstanceOf[String|Null] == null) {} // ok - if (cls1 == defn.NullClass) - cls2.derivesFrom(defn.ObjectClass) - else - cls2 == defn.NullClass && cls1.derivesFrom(defn.ObjectClass) + cls1 == defn.NullClass && cls1 == cls2 + else if (cls1 == defn.NullClass) + cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) + else if (cls2 == defn.NullClass) + cls1.derivesFrom(defn.ObjectClass) else false } From d12a9f11003ab64d77a2a3795a381ef3a40d97d8 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 20 Nov 2019 15:24:06 -0500 Subject: [PATCH 16/39] add notNull to tree --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 39 +++++++++++++------ library/src/scala/compiletime/package.scala | 7 ++++ .../pos/widen-nullable-union.scala | 2 +- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 13399c615e52..8173f6ac5920 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -224,6 +224,7 @@ class Definitions { @tu lazy val Compiletime_constValueOpt: Symbol = CompiletimePackageObject.requiredMethod("constValueOpt") @tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code") @tu lazy val Compiletime_summonFrom : Symbol = CompiletimePackageObject.requiredMethod("summonFrom") + @tu lazy val Compiletime_notNull : Symbol = CompiletimePackageObject.requiredMethod("$notNull") @tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package") @tu lazy val CompiletimeTesting_typeChecks: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks") @tu lazy val CompiletimeTesting_typeCheckErrors: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeCheckErrors") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b8e262d2d239..a79cdc859414 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -41,6 +41,7 @@ import transform.SymUtils._ import transform.TypeUtils._ import reporting.trace import Nullables.{NotNullInfo, given} +import NullOpsDecorator._ object Typer { @@ -417,7 +418,20 @@ class Typer extends Namer tree.withType(ownType) } - checkStableIdentPattern(tree1, pt) + val tree2 = ownType match { + case ot: TermRef + if ctx.explicitNulls && + pt != AssignProto && // Ensure it is not the lhs of Assign + ctx.notNullInfos.impliesNotNull(ot) => + Apply( + TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(ownType.stripNull) :: Nil), + tree1 :: Nil) + case _ => + tree1 + } + + + checkStableIdentPattern(tree2, pt) } /** Check that a stable identifier pattern is indeed stable (SLS 8.1.5) @@ -1554,16 +1568,6 @@ class Typer extends Namer typed(annot, defn.AnnotationClass.typeRef) def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = { - sym.infoOrCompleter match - case completer: Namer#Completer - if completer.creationContext.notNullInfos ne ctx.notNullInfos => - // The RHS of a val def should know about not null facts established - // in preceding statements (unless the ValDef is completed ahead of time, - // then it is impossible). - vdef.symbol.info = Completer(completer.original)( - given completer.creationContext.withNotNullInfos(ctx.notNullInfos)) - case _ => - val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym) @@ -2223,6 +2227,19 @@ class Typer extends Namer ctx // all preceding statements will have been executed in this case case _ => ctx.withNotNullInfos(initialNotNullInfos) + // We have to check the Completer of symbol befor typedValDef, + // otherwise the symbol is already completed using creation context. + mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match { + case Some((sym, completer: Namer#Completer)) + if completer.creationContext.notNullInfos ne defCtx.notNullInfos => + // The RHS of a val def should know about not null facts established + // in preceding statements (unless the ValDef is completed ahead of time, + // then it is impossible). + sym.info = Completer(completer.original)( + given completer.creationContext.withNotNullInfos(defCtx.notNullInfos)) + case _ => + } + typed(mdef)(given defCtx) match { case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 28ac8e519637..9bd514ac4464 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -63,4 +63,11 @@ package object compiletime { * } */ type S[N <: Int] <: Int + + /** Strip the Null type from x + * + * val x: String|Null = ??? + * val _:String = $notNull[String](x) + */ + inline def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf } diff --git a/tests/explicit-nulls/pos/widen-nullable-union.scala b/tests/explicit-nulls/pos/widen-nullable-union.scala index 76cb13507154..9ffa767b84e5 100644 --- a/tests/explicit-nulls/pos/widen-nullable-union.scala +++ b/tests/explicit-nulls/pos/widen-nullable-union.scala @@ -35,7 +35,7 @@ class Test { } locally { - val x: (A | Null) & (Null | B) = ??? + val x: (A | Null) & (B | Null) = ??? val y = x val _: (A & B) | Null = y } From b49aca9aaacf3c8c34b90178729602753a15aafd Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 22 Nov 2019 17:12:44 -0500 Subject: [PATCH 17/39] add null check for paths --- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../dotty/tools/dotc/typer/ConstFold.scala | 15 ++-- .../dotty/tools/dotc/typer/Nullables.scala | 34 ++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 68 ++++++++++--------- library/src/scala/compiletime/package.scala | 9 ++- 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8173f6ac5920..ec4d9046e032 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -320,7 +320,10 @@ class Definitions { /** Methods in Object and Any that do not have a side effect */ @tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode, - Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne) + Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne, + // Even through Compiletime_notNull is not pure (contains asInstanceOf), as it is removed later, + // we can consider it as pure. + Compiletime_notNull.asInstanceOf[TermSymbol]) @tu lazy val AnyKindClass: ClassSymbol = { val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil) diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 0fd7174dc774..b8e53ad1bb79 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -12,6 +12,7 @@ import Names._ import StdNames._ import Contexts._ import Nullables.{CompareNull, TrackedRef} +import NullOpsDecorator._ object ConstFold { @@ -20,10 +21,16 @@ object ConstFold { /** If tree is a constant operation, replace with result. */ def apply[T <: Tree](tree: T)(implicit ctx: Context): T = finish(tree) { tree match { - case CompareNull(TrackedRef(ref), testEqual) - if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) => - // TODO maybe drop once we have general Nullability? - Constant(!testEqual) + // TODO: fix it later + // case CompareNull(operand, testEqual) + // if ctx.explicitNulls && !operand.typeOpt.isNullableUnion => + // println(tree.show) + // Constant(!testEqual) + // case CompareNull(TrackedRef(ref), testEqual) + // if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) => + // //throw new NullPointerException() + // // TODO maybe drop once we have general Nullability? + // Constant(!testEqual) case Apply(Select(xt, op), yt :: Nil) => xt.tpe.widenTermRefExpr.normalized match case ConstantType(x) => diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index bb5cc87f281b..7901bbbac387 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -95,9 +95,7 @@ object Nullables with /** An extractor for null-trackable references */ object TrackedRef - def unapply(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match - case ref: TermRef if isTracked(ref) => Some(ref) - case _ => None + def unapply(tree: Tree)(given Context): Option[TermRef] = isTracked(tree) end TrackedRef /** Is given reference tracked for nullability? @@ -105,16 +103,28 @@ object Nullables with * to a local mutable variable where all assignments to the variable are _reachable_ * (in the sense of how it is defined in assignmentSpans). */ - def isTracked(ref: TermRef)(given Context) = - ref.isStable + def isTracked(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match + case ref: TermRef + if ref.isStable + || isTrackedNotNull(tree) || { val sym = ref.symbol - sym.is(Mutable) - && sym.owner.isTerm - && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod - && sym.span.exists - && curCtx.compilationUnit != null // could be null under -Ytest-pickler - && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) - } + sym.is(Mutable) + && sym.owner.isTerm + && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod + && sym.span.exists + && curCtx.compilationUnit != null // could be null under -Ytest-pickler + && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) + } => + Some(ref) + case _ => None + + def isTrackedNotNull(tree: Tree)(given Context) = tree match + case Select(Apply(TypeApply(f: Ident, _), x :: Nil), _) => + f.symbol == defn.Compiletime_notNull + && tree.symbol.isStableMember + && isTracked(x).isDefined + case _ => + false /** The nullability context to be used after a case that matches pattern `pat`. * If `pat` is `null`, this will assert that the selector `sel` is not null afterwards. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a79cdc859414..4c9e343b86e5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -348,6 +348,16 @@ class Typer extends Namer findRefRecur(NoType, BindingPrec.NothingBound, NoContext) } + def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match + case tp @ OrNull(tpnn) : TermRef + if pt != AssignProto && // Ensure it is not the lhs of Assign + ctx.notNullInfos.impliesNotNull(tp) => + Apply( + TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(tpnn) :: Nil), + tree :: Nil) + case _ => + tree + /** Attribute an identifier consisting of a simple name or wildcard * * @param tree The tree representing the identifier. @@ -418,18 +428,7 @@ class Typer extends Namer tree.withType(ownType) } - val tree2 = ownType match { - case ot: TermRef - if ctx.explicitNulls && - pt != AssignProto && // Ensure it is not the lhs of Assign - ctx.notNullInfos.impliesNotNull(ot) => - Apply( - TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(ownType.stripNull) :: Nil), - tree1 :: Nil) - case _ => - tree1 - } - + val tree2 = toNotNullTermRef(tree1, pt) checkStableIdentPattern(tree2, pt) } @@ -456,8 +455,11 @@ 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 = toNotNullTermRef(select, pt) + + if (select1.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select1, pt)) + else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select1 else typedDynamicSelect(tree, Nil, pt) } @@ -2220,27 +2222,31 @@ class Typer extends Namer case Some(xtree) => traverse(xtree :: rest) case none => - val defCtx = mdef match + def defCtx = ctx.withNotNullInfos(initialNotNullInfos) + val newCtx = if (ctx.owner.isTerm) { // Keep preceding not null facts in the current context only if `mdef` // cannot be executed out-of-sequence. - case _: ValDef if !mdef.mods.is(Lazy) && ctx.owner.isTerm => - ctx // all preceding statements will have been executed in this case - case _ => - ctx.withNotNullInfos(initialNotNullInfos) - // We have to check the Completer of symbol befor typedValDef, - // otherwise the symbol is already completed using creation context. - mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match { - case Some((sym, completer: Namer#Completer)) - if completer.creationContext.notNullInfos ne defCtx.notNullInfos => - // The RHS of a val def should know about not null facts established - // in preceding statements (unless the ValDef is completed ahead of time, - // then it is impossible). - sym.info = Completer(completer.original)( - given completer.creationContext.withNotNullInfos(defCtx.notNullInfos)) - case _ => + // We have to check the Completer of symbol befor typedValDef, + // otherwise the symbol is already completed using creation context. + mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match { + case Some((sym, completer: Namer#Completer)) => + if (completer.creationContext.notNullInfos ne ctx.notNullInfos) + // The RHS of a val def should know about not null facts established + // in preceding statements (unless the DefTree is completed ahead of time, + // then it is impossible). + sym.info = Completer(completer.original)( + given completer.creationContext.withNotNullInfos(ctx.notNullInfos)) + ctx // all preceding statements will have been executed in this case + case _ => + // If it has been completed, then it must be because there is a forward reference + // to the definition in the program. Hence, we don't Keep preceding not null facts + // in the current context. + defCtx + } } + else defCtx - typed(mdef)(given defCtx) match { + typed(mdef)(given newCtx) 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 diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 9bd514ac4464..945333d4ff38 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -64,10 +64,15 @@ package object compiletime { */ type S[N <: Int] <: Int - /** Strip the Null type from x + /** Strip the Null type from `x` * + * ``` * val x: String|Null = ??? * val _:String = $notNull[String](x) + * ``` + * + * Since `$notNull` is erased later, if `x` is a stable path, + * `$notNull(x)` is also a stable path. */ - inline def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf + def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf } From 6a321add72bd3287f39bb301e7ebb4fa13496c0a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 25 Nov 2019 15:28:47 -0500 Subject: [PATCH 18/39] fix long stable path --- .../src/dotty/tools/dotc/core/Types.scala | 1 + .../dotty/tools/dotc/typer/Nullables.scala | 34 +++++++------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 695a38006dd3..5d2b913cbc06 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -163,6 +163,7 @@ object Types { case tp: RefinedOrRecType => tp.parent.isStable case tp: ExprType => tp.resultType.isStable case tp: AnnotatedType => tp.parent.isStable + case tp: AndType => tp.tp1.isStable || tp.tp2.isStable case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 7901bbbac387..42098daebf8d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -95,7 +95,9 @@ object Nullables with /** An extractor for null-trackable references */ object TrackedRef - def unapply(tree: Tree)(given Context): Option[TermRef] = isTracked(tree) + def unapply(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match + case ref: TermRef if isTracked(ref) => println("tr: " + ref); Some(ref) + case _ => None end TrackedRef /** Is given reference tracked for nullability? @@ -103,28 +105,16 @@ object Nullables with * to a local mutable variable where all assignments to the variable are _reachable_ * (in the sense of how it is defined in assignmentSpans). */ - def isTracked(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match - case ref: TermRef - if ref.isStable - || isTrackedNotNull(tree) + def isTracked(ref: TermRef)(given Context) = + ref.isStable || { val sym = ref.symbol - sym.is(Mutable) - && sym.owner.isTerm - && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod - && sym.span.exists - && curCtx.compilationUnit != null // could be null under -Ytest-pickler - && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) - } => - Some(ref) - case _ => None - - def isTrackedNotNull(tree: Tree)(given Context) = tree match - case Select(Apply(TypeApply(f: Ident, _), x :: Nil), _) => - f.symbol == defn.Compiletime_notNull - && tree.symbol.isStableMember - && isTracked(x).isDefined - case _ => - false + sym.is(Mutable) + && sym.owner.isTerm + && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod + && sym.span.exists + && curCtx.compilationUnit != null // could be null under -Ytest-pickler + && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) + } /** The nullability context to be used after a case that matches pattern `pat`. * If `pat` is `null`, this will assert that the selector `sel` is not null afterwards. From b81343920c59bbee8565a146abdf0213ea7d1246 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 26 Nov 2019 17:30:42 -0500 Subject: [PATCH 19/39] better return type for Var; inline without extra val assign; better notNullInfo after assign --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 -- compiler/src/dotty/tools/dotc/typer/Nullables.scala | 9 ++++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 12 +++++++++--- library/src/scala/compiletime/package.scala | 11 +++++++---- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ec4d9046e032..1e66f4e8b89d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -321,8 +321,6 @@ class Definitions { /** Methods in Object and Any that do not have a side effect */ @tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode, Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne, - // Even through Compiletime_notNull is not pure (contains asInstanceOf), as it is removed later, - // we can consider it as pure. Compiletime_notNull.asInstanceOf[TermSymbol]) @tu lazy val AnyKindClass: ClassSymbol = { diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 42098daebf8d..1640729a1a6a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -96,7 +96,7 @@ object Nullables with /** An extractor for null-trackable references */ object TrackedRef def unapply(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match - case ref: TermRef if isTracked(ref) => println("tr: " + ref); Some(ref) + case ref: TermRef if isTracked(ref) => Some(ref) case _ => None end TrackedRef @@ -253,8 +253,11 @@ object Nullables with given assignOps: (tree: Assign) def computeAssignNullable()(given Context): tree.type = tree.lhs match - case TrackedRef(ref) => - tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) // TODO: refine with nullability type info + case TrackedRef(ref) => tree.rhs.typeOpt match + // If the type of rhs is `T|Null`, then the nullability of the lhs variable is no longer + // trackable. We don't need to check whether the type `T` is correct here. + case OrNull(_) => tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) + case _ => tree case _ => tree private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4c9e343b86e5..43eedc46c09d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -349,11 +349,17 @@ class Typer extends Namer } def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match - case tp @ OrNull(tpnn) : TermRef + case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign - ctx.notNullInfos.impliesNotNull(tp) => + ctx.notNullInfos.impliesNotNull(ref) => + // def $notNull[A, B](x: A | Null): B = x.asInstanceOf + // For a TermRef x: T|Null, + // if x is inmutable: $notNull[T, x.type & T](x), + // if x is mutable: $notNull[T, T](x). + val tpeA = TypeTree(tpnn) + val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn)) Apply( - TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(tpnn) :: Nil), + TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil), tree :: Nil) case _ => tree diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 945333d4ff38..79133e96e3d2 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -68,11 +68,14 @@ package object compiletime { * * ``` * val x: String|Null = ??? - * val _:String = $notNull[String](x) + * val _: String = $notNull[String, x.type & String](x) + * + * var y: String|Null = ??? + * val _: String = $notNull[String, String](x) * ``` * - * Since `$notNull` is erased later, if `x` is a stable path, - * `$notNull(x)` is also a stable path. + * Since `$notNull` is erased later, if `x.type` is a stable path, + * the type of `$notNull(x)` is also a stable path. */ - def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf + inline def $notNull[A, B](x: => A | Null): B = x.asInstanceOf } From ef5864ea90db84c79b383510ab7251da135328af Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2019 14:09:19 -0500 Subject: [PATCH 20/39] add flow 'tests --- .../src/dotty/tools/dotc/typer/Typer.scala | 16 +- .../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/flow5.scala | 66 +++++++ tests/explicit-nulls/neg/flow6.scala | 58 ++++++ tests/explicit-nulls/neg/flow7.scala | 11 ++ tests/explicit-nulls/neg/nullnull.scala | 18 ++ tests/explicit-nulls/neg/strip.scala | 27 +++ 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 | 14 ++ tests/explicit-nulls/pos/flow4.scala | 24 +++ tests/explicit-nulls/pos/flow6.scala | 74 +++++++ .../explicit-nulls/pos/opaque-nullable.scala | 25 +++ tests/explicit-nulls/pos/tref-caching.scala | 19 ++ tests/explicit-nulls/run/flow.check | 1 + tests/explicit-nulls/run/flow.scala | 30 +++ 20 files changed, 820 insertions(+), 8 deletions(-) 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/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/nullnull.scala create mode 100644 tests/explicit-nulls/neg/strip.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/flow6.scala create mode 100644 tests/explicit-nulls/pos/opaque-nullable.scala create mode 100644 tests/explicit-nulls/pos/tref-caching.scala create mode 100644 tests/explicit-nulls/run/flow.check create mode 100644 tests/explicit-nulls/run/flow.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 43eedc46c09d..c56d5d9cfc37 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -352,15 +352,15 @@ class Typer extends Namer case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) => - // def $notNull[A, B](x: A | Null): B = x.asInstanceOf - // For a TermRef x: T|Null, - // if x is inmutable: $notNull[T, x.type & T](x), - // if x is mutable: $notNull[T, T](x). val tpeA = TypeTree(tpnn) - val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn)) - Apply( - TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil), - tree :: Nil) + val tpeB = TypeTree(AndType(ref, tpnn)) + //val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn)) + val newTree = TypeApply(Select(tree, defn.Any_typeCast.namedType), tpeB :: Nil) + //newTree.symbol.setFlag(Erased) + // val newTree = Apply( + // TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil), + // tree :: Nil) + newTree case _ => tree 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..0feb037210c7 --- /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 = b2.s + } + } + + 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/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..6890a43018dd --- /dev/null +++ b/tests/explicit-nulls/neg/flow6.scala @@ -0,0 +1,58 @@ +// Test forward references handled with flow typing +// Currently, the flow typing will not be applied to definitions forwardly referred. +class Foo { + + def test0(): Unit = { + def z: Int = y + val x: String|Null = ??? + if (x == null) return () + def y: Int = x.length // error: x: String|Null inferred + } + + + def test1(): Unit = { + val x: String|Null = ??? + if (x == null) return () + 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: Int = y + if (x == null) return () + def y: Int = x.length // error: x: String|Null is inferred + () + } + + // Since y is referred before definition, flow typing is not used here. + def test3(): Unit = { + val x: String|Null = ??? + lazy val z = y + if (x == null) return () + lazy val y = x.length // error: x: String|Null is inferred + () + } + + // 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 = { + 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/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/nullnull.scala b/tests/explicit-nulls/neg/nullnull.scala new file mode 100644 index 000000000000..1ebb25bf6238 --- /dev/null +++ b/tests/explicit-nulls/neg/nullnull.scala @@ -0,0 +1,18 @@ +// 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 = { + val x: Null | Null | Null = ??? + 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 + } +} diff --git a/tests/explicit-nulls/neg/strip.scala b/tests/explicit-nulls/neg/strip.scala new file mode 100644 index 000000000000..670320be9b46 --- /dev/null +++ b/tests/explicit-nulls/neg/strip.scala @@ -0,0 +1,27 @@ +// Test we are correctly striping nulls from nullable unions. + +class Foo { + + class B1 + class B2 + locally { + val x: (Null | String) | Null | (B1 | (Null | B2)) = ??? + if (x != null) { + val _: String | B1 | B2 = x // ok: can remove all nullable unions + } + } + + locally { + val x: (Null | String) & (Null | B1) = ??? + if (x != null) { + val _: String & B1 = x // ok: can remove null from embedded intersection + } + } + + locally { + val x: (Null | B1) & B2 = ??? + if (x != null) { + val _: B1 & B2 = x // error: the type of x is not a nullable union, so we cannot remove the Null + } + } +} 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..b5d49fa8d70a --- /dev/null +++ b/tests/explicit-nulls/pos/flow3.scala @@ -0,0 +1,14 @@ + +// 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) { + val y = x.length + } + + 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..10334446221d --- /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 == 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/flow6.scala b/tests/explicit-nulls/pos/flow6.scala new file mode 100644 index 000000000000..555e24335c26 --- /dev/null +++ b/tests/explicit-nulls/pos/flow6.scala @@ -0,0 +1,74 @@ + +// 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 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) { + } else { + def f = x.length // ok + def f2: Int = x.length // ok + } + } +} diff --git a/tests/explicit-nulls/pos/opaque-nullable.scala b/tests/explicit-nulls/pos/opaque-nullable.scala new file mode 100644 index 000000000000..4b6f4f3f88aa --- /dev/null +++ b/tests/explicit-nulls/pos/opaque-nullable.scala @@ -0,0 +1,25 @@ +// 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 + + s1.isEmpty + s1.flatMap((x) => true) +} 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.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 + } +} From 7d40d5c19d55c76b2e9b36cdfaa1591328033fb4 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 29 Nov 2019 17:54:03 -0500 Subject: [PATCH 21/39] Fix isStable; fix var track in lazy val --- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../src/dotty/tools/dotc/core/Types.scala | 5 +- .../dotty/tools/dotc/typer/Implicits.scala | 9 ++-- .../dotty/tools/dotc/typer/Nullables.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 11 ++-- tests/explicit-nulls/neg/after-assign.scala | 13 +++++ tests/explicit-nulls/neg/eq.scala | 2 +- tests/explicit-nulls/neg/simple-var.scala | 50 +++++++++++++++++++ tests/explicit-nulls/neg/strip.scala | 4 +- tests/explicit-nulls/pos/flow3.scala | 3 +- tests/explicit-nulls/pos/flow4.scala | 2 +- tests/explicit-nulls/pos/stable-path.scala | 11 ++++ tests/explicit-nulls/pos/while-loop.scala | 19 +++++++ tests/explicit-nulls/run/eq.scala | 1 - 14 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 tests/explicit-nulls/neg/after-assign.scala create mode 100644 tests/explicit-nulls/neg/simple-var.scala create mode 100644 tests/explicit-nulls/pos/stable-path.scala create mode 100644 tests/explicit-nulls/pos/while-loop.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1e66f4e8b89d..bcf4d326b201 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -270,7 +270,7 @@ class Definitions { @tu lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final) @tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact) @tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable) - // generated by pattern matcher, eliminated by erasure + // generated by pattern matcher and exlicit nulls, eliminated by erasure /** def getClass[A >: this.type](): Class[? <: A] */ @tu lazy val Any_getClass: TermSymbol = @@ -320,8 +320,7 @@ class Definitions { /** Methods in Object and Any that do not have a side effect */ @tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode, - Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne, - Compiletime_notNull.asInstanceOf[TermSymbol]) + Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne) @tu lazy val AnyKindClass: ClassSymbol = { val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5d2b913cbc06..0d1d5ba867cd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -17,6 +17,7 @@ import SymDenotations._ import Decorators._ import Denotations._ import Periods._ +import CheckRealizable._ import util.Stats._ import util.SimpleIdentitySet import reporting.diagnostic.Message @@ -163,7 +164,9 @@ object Types { case tp: RefinedOrRecType => tp.parent.isStable case tp: ExprType => tp.resultType.isStable case tp: AnnotatedType => tp.parent.isStable - case tp: AndType => tp.tp1.isStable || tp.tp2.isStable + case tp: AndType => + tp.tp1.isStable && (realizability(tp.tp2) eq Realizable) || + tp.tp2.isStable && (realizability(tp.tp1) eq Realizable) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5468b71cf22f..2836479fe597 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -823,9 +823,12 @@ trait Implicits { self: Typer => // if (x.asInstanceOf[String|Null] == null) {} // ok cls1 == defn.NullClass && cls1 == cls2 else if (cls1 == defn.NullClass) - cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) - else if (cls2 == defn.NullClass) - cls1.derivesFrom(defn.ObjectClass) + cls1 == cls2 + else if (!ctx.explicitNulls) + if (cls1 == defn.NullClass) + cls2.derivesFrom(defn.ObjectClass) + else + cls2 == defn.NullClass && cls1.derivesFrom(defn.ObjectClass) else false } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 1640729a1a6a..11a136c534a8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -110,6 +110,7 @@ object Nullables with || { val sym = ref.symbol sym.is(Mutable) && sym.owner.isTerm + && !curCtx.owner.is(Flags.Lazy) // not a rhs of lazy ValDef && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod && sym.span.exists && curCtx.compilationUnit != null // could be null under -Ytest-pickler diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c56d5d9cfc37..d850e8a1c7c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -352,15 +352,12 @@ class Typer extends Namer case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) => - val tpeA = TypeTree(tpnn) - val tpeB = TypeTree(AndType(ref, tpnn)) - //val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn)) - val newTree = TypeApply(Select(tree, defn.Any_typeCast.namedType), tpeB :: Nil) - //newTree.symbol.setFlag(Erased) - // val newTree = Apply( + // val tpeA = TypeTree(tpnn) + // val tpeB = TypeTree(AndType(ref, tpnn)) + // Apply( // TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil), // tree :: Nil) - newTree + tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) case _ => tree diff --git a/tests/explicit-nulls/neg/after-assign.scala b/tests/explicit-nulls/neg/after-assign.scala new file mode 100644 index 000000000000..79b7d675f3a5 --- /dev/null +++ b/tests/explicit-nulls/neg/after-assign.scala @@ -0,0 +1,13 @@ +class C(val x: Int, val next: C|Null) + +def f = { + var xs: C|Null = C(1, C(2, null)) + while (xs != null) { + val xsx: Int = xs.x + val xscpy: C = xs + xs = xscpy // Since xscpy is non-nullable, after the assign, xs is still non-nullable + val xscpyx: Int = xscpy.x + xs = xs.next // xs.next is nullable, after the assign, xs becomes nullable + val xsnx: Int = xs.x // error + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg/eq.scala b/tests/explicit-nulls/neg/eq.scala index 3fab8ba4bf8a..026fe40550cf 100644 --- a/tests/explicit-nulls/neg/eq.scala +++ b/tests/explicit-nulls/neg/eq.scala @@ -41,4 +41,4 @@ class Foo { null == false // error 'a' == null // error null == 'b' // error -} +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg/simple-var.scala b/tests/explicit-nulls/neg/simple-var.scala new file mode 100644 index 000000000000..e48bb1638a47 --- /dev/null +++ b/tests/explicit-nulls/neg/simple-var.scala @@ -0,0 +1,50 @@ +// Test simple var track + +def nullable[T](x: T): T|Null = x + +def f = { + var x: String|Null = ??? + if (x != null) { + val a: String = x + x = "" + val b: String = x + } + + assert(x != null) + val a: String = x + x = nullable(x) + val b: String = x // error: x might be null +} + +def g = { + var x: String|Null = ??? + lazy val y = { + if (x != null) { + x = null + } + + } + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } +} + +def h = { + var x: String|Null = "???" + lazy val y = { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } +} + +def i = { + var x: String|Null = "???" + def y = { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/neg/strip.scala b/tests/explicit-nulls/neg/strip.scala index 670320be9b46..997d272d008f 100644 --- a/tests/explicit-nulls/neg/strip.scala +++ b/tests/explicit-nulls/neg/strip.scala @@ -20,8 +20,6 @@ class Foo { locally { val x: (Null | B1) & B2 = ??? - if (x != null) { - val _: B1 & B2 = x // error: the type of x is not a nullable union, so we cannot remove the Null - } + if (x != null) {} // error: the type of x is not a nullable union, so we cannot remove the Null } } diff --git a/tests/explicit-nulls/pos/flow3.scala b/tests/explicit-nulls/pos/flow3.scala index b5d49fa8d70a..df56f8e85822 100644 --- a/tests/explicit-nulls/pos/flow3.scala +++ b/tests/explicit-nulls/pos/flow3.scala @@ -1,6 +1,7 @@ - +// TODO: broken // 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" diff --git a/tests/explicit-nulls/pos/flow4.scala b/tests/explicit-nulls/pos/flow4.scala index 10334446221d..78c26a0bec35 100644 --- a/tests/explicit-nulls/pos/flow4.scala +++ b/tests/explicit-nulls/pos/flow4.scala @@ -1,4 +1,4 @@ - +// TODO: broken // 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 diff --git a/tests/explicit-nulls/pos/stable-path.scala b/tests/explicit-nulls/pos/stable-path.scala new file mode 100644 index 000000000000..ee1ae1a20b61 --- /dev/null +++ b/tests/explicit-nulls/pos/stable-path.scala @@ -0,0 +1,11 @@ +// Test stable path is still stable after notNull + +class C(val x: Int, val next: C|Null) + +def f() = { + val xs: C|Null = C(1, C(2, null)) + assert(xs != null) + val a: xs.x.type = xs.x + assert(xs.next != null) + val b: xs.next.x.type = xs.next.x +} \ No newline at end of file diff --git a/tests/explicit-nulls/pos/while-loop.scala b/tests/explicit-nulls/pos/while-loop.scala new file mode 100644 index 000000000000..b8dc4fcd31c1 --- /dev/null +++ b/tests/explicit-nulls/pos/while-loop.scala @@ -0,0 +1,19 @@ +class C(val x: Int, val next: C|Null) + +def f = { + var xs: C|Null = C(1, C(2, null)) + while (xs != null) { + val xsx: Int = xs.x + // Currently, we can't track a path with a mutable variable prefix, + // even though the variable is trackable, like (xs.next != null). + // TODO: support this in the future? + val xscpy: C = xs + if (xscpy.next != null) { + val _: Int = xscpy.next.x + if (xscpy.next.next != null) { + val _: Int = xscpy.next.next.x + } + } + xs = xs.next + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/run/eq.scala b/tests/explicit-nulls/run/eq.scala index 42f0a86a1a3e..f93cf4245ac9 100644 --- a/tests/explicit-nulls/run/eq.scala +++ b/tests/explicit-nulls/run/eq.scala @@ -18,7 +18,6 @@ object Test { assert(z.asInstanceOf[Any] != null) assert(x != null) - assert(null != x) assert(y == null) assert(null == y) assert(null == null) From ce606b6e2338fd9a6d4c668d89a4cdb7971ba49e Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 2 Dec 2019 14:16:48 -0500 Subject: [PATCH 22/39] Fix closure check --- .../dotty/tools/dotc/typer/Implicits.scala | 9 +-- .../dotty/tools/dotc/typer/Nullables.scala | 21 ++++-- tests/explicit-nulls/neg/simple-var.scala | 32 +++----- .../neg/var-ref-in-closure.scala | 74 +++++++++++++++++++ 4 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 tests/explicit-nulls/neg/var-ref-in-closure.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2836479fe597..5468b71cf22f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -823,12 +823,9 @@ trait Implicits { self: Typer => // if (x.asInstanceOf[String|Null] == null) {} // ok cls1 == defn.NullClass && cls1 == cls2 else if (cls1 == defn.NullClass) - cls1 == cls2 - else if (!ctx.explicitNulls) - if (cls1 == defn.NullClass) - cls2.derivesFrom(defn.ObjectClass) - else - cls2 == defn.NullClass && cls1.derivesFrom(defn.ObjectClass) + cls1 == cls2 || cls2.derivesFrom(defn.ObjectClass) + else if (cls2 == defn.NullClass) + cls1.derivesFrom(defn.ObjectClass) else false } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 11a136c534a8..b050e4f1938f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -110,8 +110,10 @@ object Nullables with || { val sym = ref.symbol sym.is(Mutable) && sym.owner.isTerm - && !curCtx.owner.is(Flags.Lazy) // not a rhs of lazy ValDef - && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod + && (if sym.owner != curCtx.owner then + !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef + && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different DefDef + else true) && sym.span.exists && curCtx.compilationUnit != null // could be null under -Ytest-pickler && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) @@ -254,11 +256,16 @@ object Nullables with given assignOps: (tree: Assign) def computeAssignNullable()(given Context): tree.type = tree.lhs match - case TrackedRef(ref) => tree.rhs.typeOpt match - // If the type of rhs is `T|Null`, then the nullability of the lhs variable is no longer - // trackable. We don't need to check whether the type `T` is correct here. - case OrNull(_) => tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - case _ => tree + case TrackedRef(ref) => + def withoutRef: tree.type = tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) + tree.rhs.typeOpt match + // If the type of rhs is `T|Null`, then the nullability of the lhs variable is no longer + // trackable. We don't need to check whether the type `T` is correct here, as typer will + // check it. + case OrNull(_) => withoutRef + // If the type of rhs is Null, we discard its NotNullInfo. + case tp if tp.isNullType => withoutRef + case _ => tree case _ => tree private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) diff --git a/tests/explicit-nulls/neg/simple-var.scala b/tests/explicit-nulls/neg/simple-var.scala index e48bb1638a47..678413cd7847 100644 --- a/tests/explicit-nulls/neg/simple-var.scala +++ b/tests/explicit-nulls/neg/simple-var.scala @@ -18,33 +18,19 @@ def f = { def g = { var x: String|Null = ??? - lazy val y = { - if (x != null) { - x = null - } - - } if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x + x = null + val b: String = x // error: x is null } } def h = { - var x: String|Null = "???" - lazy val y = { - if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable - } - x - } -} - -def i = { - var x: String|Null = "???" - def y = { - if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable - } - x + var x: String|Null = ??? + if (x != null) { + val a: String = x + val b: String | String = a + x = b + val _: String = x // ok } } \ No newline at end of file diff --git a/tests/explicit-nulls/neg/var-ref-in-closure.scala b/tests/explicit-nulls/neg/var-ref-in-closure.scala new file mode 100644 index 000000000000..bc49d6fbe0b7 --- /dev/null +++ b/tests/explicit-nulls/neg/var-ref-in-closure.scala @@ -0,0 +1,74 @@ +// Test that we don't track variables which is refered in another closure. + +object VarRef { + locally { + var x: String|Null = ??? + val y = { + if (x != null) { + val _: String = x // ok: y doesn't create closure + } + } + if (x != null) { + val a: String = x // ok + } + } + + locally { + var x: String|Null = ??? + lazy val y = { + if (x != null) { + x = null + } + x + } + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + } + + locally { + var x: String|Null = ??? + def y = { + if (x != null) { + x = null + } + x + } + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + } + + + locally { + var x: String|Null = "???" + lazy val y = { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } + } + + locally { + var x: String|Null = "???" + def y = { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } + } + + lazy val lazyblock = { + var x: String|Null = "???" + lazy val y = { + if (x != null) { + // The enclosingMethods of x definition and x reference hare are same + val a: String = x // error: x exists in closure, no longer tackable + } + x + } + } +} + From 50b70cae458ae1000068d4a4b752c04a4215e6f4 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2019 14:50:37 -0500 Subject: [PATCH 23/39] Add while, match tests --- .../dotty/tools/dotc/typer/Nullables.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 5 -- .../neg/var-ref-in-closure.scala | 74 ++++++++++++++++++- tests/explicit-nulls/pos/flow3.scala | 15 ---- tests/explicit-nulls/pos/flow4.scala | 4 +- tests/explicit-nulls/pos/match.scala | 11 +++ tests/explicit-nulls/pos/while-loop.scala | 1 - tests/explicit-nulls/run/while-loop.check | 3 + tests/explicit-nulls/run/while-loop.scala | 12 +++ 9 files changed, 99 insertions(+), 27 deletions(-) delete mode 100644 tests/explicit-nulls/pos/flow3.scala create mode 100644 tests/explicit-nulls/pos/match.scala create mode 100644 tests/explicit-nulls/run/while-loop.check create mode 100644 tests/explicit-nulls/run/while-loop.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index b050e4f1938f..5444a31d2fc0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -111,6 +111,7 @@ object Nullables with sym.is(Mutable) && sym.owner.isTerm && (if sym.owner != curCtx.owner then + // TODO: need to check by-name parameters !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different DefDef else true) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d850e8a1c7c5..66ddc6c36909 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -352,11 +352,6 @@ class Typer extends Namer case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) => - // val tpeA = TypeTree(tpnn) - // val tpeB = TypeTree(AndType(ref, tpnn)) - // Apply( - // TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil), - // tree :: Nil) tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) case _ => tree diff --git a/tests/explicit-nulls/neg/var-ref-in-closure.scala b/tests/explicit-nulls/neg/var-ref-in-closure.scala index bc49d6fbe0b7..e1b50df200bc 100644 --- a/tests/explicit-nulls/neg/var-ref-in-closure.scala +++ b/tests/explicit-nulls/neg/var-ref-in-closure.scala @@ -41,7 +41,7 @@ object VarRef { locally { - var x: String|Null = "???" + var x: String|Null = ??? lazy val y = { if (x != null) { val a: String = x // error: x exists in closure, no longer tackable @@ -51,7 +51,7 @@ object VarRef { } locally { - var x: String|Null = "???" + var x: String|Null = ??? def y = { if (x != null) { val a: String = x // error: x exists in closure, no longer tackable @@ -61,7 +61,7 @@ object VarRef { } lazy val lazyblock = { - var x: String|Null = "???" + var x: String|Null = ??? lazy val y = { if (x != null) { // The enclosingMethods of x definition and x reference hare are same @@ -70,5 +70,71 @@ object VarRef { x } } -} + abstract class F { + def get(): String | Null + } + + locally { + var x: String|Null = ??? + val y: F = new F { + def get() = { + if (x != null) x = null + x + } + } + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + } + + locally { + var x: String|Null = ??? + val y: F = new F { + def get() = { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } + } + } + + def f(x: => String | Null): F = new F { + def get() = x + } + + locally { + var x: String|Null = ??? + val y: F = f { + if (x != null) { + x = null + } + x + } + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + } + + // TODO: not working now + // locally { + // var x: String|Null = ??? + // val y: F = f { + // if (x != null) { + // val a: String = x // err: x exists in closure, no longer tackable + // } + // x + // } + // } + + locally { + var x: String|Null = ??? + val y: String => String|Null = s => { + if (x != null) { + val a: String = x // error: x exists in closure, no longer tackable + } + x + } + } +} diff --git a/tests/explicit-nulls/pos/flow3.scala b/tests/explicit-nulls/pos/flow3.scala deleted file mode 100644 index df56f8e85822..000000000000 --- a/tests/explicit-nulls/pos/flow3.scala +++ /dev/null @@ -1,15 +0,0 @@ -// TODO: broken -// 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) { - val y = x.length - } - - if ((x != null): Boolean) { - val y = x.length - } -} diff --git a/tests/explicit-nulls/pos/flow4.scala b/tests/explicit-nulls/pos/flow4.scala index 78c26a0bec35..837f6bb1ce3f 100644 --- a/tests/explicit-nulls/pos/flow4.scala +++ b/tests/explicit-nulls/pos/flow4.scala @@ -7,9 +7,9 @@ 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 == null) && tree.isInstanceOf[RedTree[_, _]] + private[this] inline def isRedTree(tree: Tree[_, _] | Null) = (tree != null) && tree.isInstanceOf[RedTree[_, _]] - def foo[A, B](tree: Tree[A, B]): Unit = { + def foo[A, B](tree: Tree[A, B] | Null): Unit = { if (isRedTree(tree)) { val key = tree.key val value = tree.value diff --git a/tests/explicit-nulls/pos/match.scala b/tests/explicit-nulls/pos/match.scala new file mode 100644 index 000000000000..0e65b9584328 --- /dev/null +++ b/tests/explicit-nulls/pos/match.scala @@ -0,0 +1,11 @@ +// Test NotNullInfo from non-null cases + +object MatchTest { + locally { + val s: String|Null = ??? + s match { + case _: String => println(s.length) + case _ => println(0) + } + } +} diff --git a/tests/explicit-nulls/pos/while-loop.scala b/tests/explicit-nulls/pos/while-loop.scala index b8dc4fcd31c1..954cc5025e7b 100644 --- a/tests/explicit-nulls/pos/while-loop.scala +++ b/tests/explicit-nulls/pos/while-loop.scala @@ -6,7 +6,6 @@ def f = { val xsx: Int = xs.x // Currently, we can't track a path with a mutable variable prefix, // even though the variable is trackable, like (xs.next != null). - // TODO: support this in the future? val xscpy: C = xs if (xscpy.next != null) { val _: Int = xscpy.next.x diff --git a/tests/explicit-nulls/run/while-loop.check b/tests/explicit-nulls/run/while-loop.check new file mode 100644 index 000000000000..01e79c32a8c9 --- /dev/null +++ b/tests/explicit-nulls/run/while-loop.check @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/tests/explicit-nulls/run/while-loop.scala b/tests/explicit-nulls/run/while-loop.scala new file mode 100644 index 000000000000..8f5686f68e3b --- /dev/null +++ b/tests/explicit-nulls/run/while-loop.scala @@ -0,0 +1,12 @@ + +object Test { + class C(val x: Int, val next: C|Null) + + def main(args: Array[String]): Unit = { + var xs: C|Null = C(1, C(2, C(3, null))) + while (xs != null) { + println(xs.x) + xs = xs.next + } + } +} \ No newline at end of file From 1eed7805698da7a6fa6352f56d4229ae13bbf9b5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2019 15:03:26 -0500 Subject: [PATCH 24/39] Remove unused code --- .../src/dotty/tools/dotc/core/Definitions.scala | 1 - .../src/dotty/tools/dotc/typer/ConstFold.scala | 10 ---------- library/src/scala/compiletime/package.scala | 15 --------------- 3 files changed, 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index bcf4d326b201..2bc21919435b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -224,7 +224,6 @@ class Definitions { @tu lazy val Compiletime_constValueOpt: Symbol = CompiletimePackageObject.requiredMethod("constValueOpt") @tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code") @tu lazy val Compiletime_summonFrom : Symbol = CompiletimePackageObject.requiredMethod("summonFrom") - @tu lazy val Compiletime_notNull : Symbol = CompiletimePackageObject.requiredMethod("$notNull") @tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package") @tu lazy val CompiletimeTesting_typeChecks: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks") @tu lazy val CompiletimeTesting_typeCheckErrors: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeCheckErrors") diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index b8e53ad1bb79..db42fc462da3 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -21,16 +21,6 @@ object ConstFold { /** If tree is a constant operation, replace with result. */ def apply[T <: Tree](tree: T)(implicit ctx: Context): T = finish(tree) { tree match { - // TODO: fix it later - // case CompareNull(operand, testEqual) - // if ctx.explicitNulls && !operand.typeOpt.isNullableUnion => - // println(tree.show) - // Constant(!testEqual) - // case CompareNull(TrackedRef(ref), testEqual) - // if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) => - // //throw new NullPointerException() - // // TODO maybe drop once we have general Nullability? - // Constant(!testEqual) case Apply(Select(xt, op), yt :: Nil) => xt.tpe.widenTermRefExpr.normalized match case ConstantType(x) => diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 79133e96e3d2..28ac8e519637 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -63,19 +63,4 @@ package object compiletime { * } */ type S[N <: Int] <: Int - - /** Strip the Null type from `x` - * - * ``` - * val x: String|Null = ??? - * val _: String = $notNull[String, x.type & String](x) - * - * var y: String|Null = ??? - * val _: String = $notNull[String, String](x) - * ``` - * - * Since `$notNull` is erased later, if `x.type` is a stable path, - * the type of `$notNull(x)` is also a stable path. - */ - inline def $notNull[A, B](x: => A | Null): B = x.asInstanceOf } From c5aab82e01f80398ce26bebf694663340c7a3687 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2019 15:08:21 -0500 Subject: [PATCH 25/39] Update comments --- tests/explicit-nulls/neg/eq.scala | 2 +- tests/explicit-nulls/pos/flow4.scala | 1 - tests/explicit-nulls/pos/stable-path.scala | 2 +- tests/explicit-nulls/pos/while-loop.scala | 2 +- tests/explicit-nulls/run/while-loop.scala | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/explicit-nulls/neg/eq.scala b/tests/explicit-nulls/neg/eq.scala index 026fe40550cf..3fab8ba4bf8a 100644 --- a/tests/explicit-nulls/neg/eq.scala +++ b/tests/explicit-nulls/neg/eq.scala @@ -41,4 +41,4 @@ class Foo { null == false // error 'a' == null // error null == 'b' // error -} \ No newline at end of file +} diff --git a/tests/explicit-nulls/pos/flow4.scala b/tests/explicit-nulls/pos/flow4.scala index 837f6bb1ce3f..8327606bcd13 100644 --- a/tests/explicit-nulls/pos/flow4.scala +++ b/tests/explicit-nulls/pos/flow4.scala @@ -1,4 +1,3 @@ -// TODO: broken // 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 diff --git a/tests/explicit-nulls/pos/stable-path.scala b/tests/explicit-nulls/pos/stable-path.scala index ee1ae1a20b61..0d0dcd967a6a 100644 --- a/tests/explicit-nulls/pos/stable-path.scala +++ b/tests/explicit-nulls/pos/stable-path.scala @@ -8,4 +8,4 @@ def f() = { val a: xs.x.type = xs.x assert(xs.next != null) val b: xs.next.x.type = xs.next.x -} \ No newline at end of file +} diff --git a/tests/explicit-nulls/pos/while-loop.scala b/tests/explicit-nulls/pos/while-loop.scala index 954cc5025e7b..49b8be5ceef6 100644 --- a/tests/explicit-nulls/pos/while-loop.scala +++ b/tests/explicit-nulls/pos/while-loop.scala @@ -15,4 +15,4 @@ def f = { } xs = xs.next } -} \ No newline at end of file +} diff --git a/tests/explicit-nulls/run/while-loop.scala b/tests/explicit-nulls/run/while-loop.scala index 8f5686f68e3b..f4b44d749c3f 100644 --- a/tests/explicit-nulls/run/while-loop.scala +++ b/tests/explicit-nulls/run/while-loop.scala @@ -9,4 +9,4 @@ object Test { xs = xs.next } } -} \ No newline at end of file +} From 465823d0f34d1b0b51392c4412cf2551a72b22ea Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2019 18:00:33 -0500 Subject: [PATCH 26/39] Update doc, WIP --- docs/docs/internals/explicit-nulls.md | 26 +- .../other-new-features/explicit-nulls.md | 292 +++++++++--------- 2 files changed, 163 insertions(+), 155 deletions(-) diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 4900ab48aacf..b0366dd47edf 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -3,7 +3,7 @@ layout: doc-page title: "Explicit Nulls" --- -The "explicit nulls" feature (enabled via a flag) changes the Scala type hierarchy +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`. @@ -13,21 +13,21 @@ The implementation of the feature in dotty can be conceptually divided in severa 3. a "magic" `JavaNull` type (an alias for `Null`) that is recognized by the compiler and allows unsound member selections (trading soundness for usability) -Feature Flag ------------- +## 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 --------------- +## 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 ------------- +## Java Interop + TODO(abeln): add support for recognizing nullability annotations a la https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations @@ -63,8 +63,8 @@ function `n`: 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 + `JavaNull` is just an alias for `Null`, but with magic power. `JavaNull`'s magic (anti-)power is that it's unsound. @@ -79,8 +79,8 @@ The logic to allow member selections is defined in `findMember` in `Types.scala` - 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 ----------------------------- +## 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 @@ -98,3 +98,7 @@ are methods of the `Type` class, so call them with `this` as a receiver: 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 + +TODO diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md index 60f143a15052..9e94cfc5d390 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -3,7 +3,7 @@ layout: doc-page title: "Explicit Nulls" --- -This proposal describes a modification to the Scala type system that makes reference types +Explicit nulls is an opt-in feature that modifies the Scala type system, which makes reference types (anything that extends `AnyRef`) _non-nullable_. This means the following code will no longer typecheck: @@ -12,6 +12,7 @@ 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 ``` @@ -50,58 +51,45 @@ proposed by @liufengyun and @biboudis in [https://github.com/lampepfl/dotty/pull ## 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 -``` +We don't allow the double-equal (`==` and `!=`) and reference (`eq` and `ne`) comparison between +`AnyRef` and `Null` anymore, since a variable with non-nullable type shouldn't have null value. +`null` can only be compared with `Null`, nullable union (`T | Null`), or `Any` type. -### Reference Equality +For some reason, if we really want to compare `null` with non-null values, we can use cast. -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 -``` +val x: String = ??? +val y: String | Null = ??? -We support this case by making the `eq` and `ne` methods in `AnyRef` take a `AnyRef|Null` -as argument. +x == null // error: Values of types String and Null cannot be compared with == or != +x eq null // error +"hello" == null // error -We also need to support -```scala -null.eq("hello") -val x: String|Null = null -x.eq(null) -``` +y == null // ok +y == x // ok -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)) +(x: String | Null) == null // ok +(x: Any) == null // ok ``` ## 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] - } - } + def[T] (x: T|Null) nn: x.type & T = + if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null") + else x.asInstanceOf[x.type & 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`. + Don't use `.nn` on mutable variables directly, which may introduce unknown value into the type. + ## Java Interop The compiler can load Java classes in two ways: from source or from bytecode. In either case, @@ -109,47 +97,39 @@ when a Java class is loaded, we "patch" the type of its members to reflect that 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 -``` +* the type of fields +* the argument type and return type of methods -`JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules for `nf` below with examples. +`JavaNull` is an alias for `Null` with magic properties (see below). We illustrate the rules with following examples: * The first two rules are easy: we nullify reference types but not value types. - ```scala + + ```java class C { String s; int x; } + ``` ==> + ```scala class C { - val s: String|Null + val s: String|JavaNull 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 + * We nullify type parameters because in Java a type parameter is always nullable, so the following code compiles. + + ```java class C { T foo() { return null; } } + ``` ==> - class C[T] { def foo(): T|Null } + ```scala + class C[T] { def foo(): T|JavaNull } ``` Notice this is rule is sometimes too conservative, as witnessed by + ```scala class InScala { val c: C[Bool] = ??? // C as above @@ -157,47 +137,129 @@ We do the patching with a "nullification" function `n` on types: } ``` - * Rule 4 reduces the number of redundant nullable types we need to add. Consider - ```scala + * This reduces the number of redundant nullable types we need to add. Consider + + ```java class Box { T get(); } class BoxFactory { Box makeBox(); } + ``` ==> + ```scala 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. + Suppose we have a `BoxFactory[String]`. Notice that calling `makeBox()` on it returns a + `Box[String]|JavaNull`, not a `Box[String|JavaNull]|JavaNull`. 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`. + + Notice that 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. - 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. + * We will append `JavaNull` to the type arguments if the generic class is defined in Scala. - * Rule 5 is needed because Java code might use a generic that's defined in Scala as opposed to Java. + ```java + class BoxFactory { + Box makeBox(); // Box is Scala-defined + List>> makeCrazyBoxes(); // List is Java-defined + } + ``` + ==> ```scala - class BoxFactory { Box makeBox(); } // Box is Scala defined + class BoxFactory[T] { + def makeBox(): Box[T | JavaNull] | JavaNull + def makeCrazyBoxes(): List[Box[List[T] | JavaNull]] | JavaNull + } + ``` + + In this case, since `Box` is Scala-defined, and we will 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. + + The `List` is Java-defined, so we don't append `JavaNull` to its type argument. But we + still need to nullify its inside. + + * We don't nullify _simple_ literal constant (`final`) fields, since they are known to be non-null + + ```java + class Constants { + final String NAME = "name"; + final int AGE = 0; + final char CHAR = 'a'; + + final String NAME_GENERATED = getNewName(); + final int VALUE = 0 * 2; + } + ``` ==> - class BoxFactory[T] { def makeBox(): Box[T|JavaNull]|JavaNull } + ```scala + class Constants { + val NAME: String("name") = "name" + val AGE: Int(0) = 0 + val CHAR: Char('a') = 'a' + + val NAME_GENERATED: String | Null = ??? + val VALUE: Int = ??? + } + ``` + + * We don't append `JavaNull` to a field and the return type of a method which is annotated with a + `NotNull` annotation. + + ```java + class C { + @NotNull String name; + @NotNull List getNames(String prefix); // List is Java-defined + @NotNull Box getBoxedName(); // Box is Scala-defined + } + ``` + ==> + ```scala + class C { + val name: String + def getNames(prefix: String | JavaNull): List[String] // we still need to nullify the paramter types + def getBoxedName(): Box[String | JavaNull] // we don't append `JavaNull` to the outmost level, but we still need to nullify inside + } ``` - 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. + The annotation must be from the list below to be recognized as NotNull by the compiler. + Check `Definitions.scala` for an updated list. - * 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)`. + ```scala + // 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" :: + "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) + ``` ### JavaNull -To enable method chaining on Java-returned values, we have a special `JavaNull` alias + +To enable method chaining on Java-returned values, we have the special type alias for `Null`: + ```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() {} @@ -210,6 +272,7 @@ However, if `someJavaMethod` were to return `null`, then the first member select would throw a `NPE`. Without `JavaNull`, the chaining becomes too cumbersome + ```scala val ret = someJavaMethod() val s2 = if (ret != null) { @@ -224,73 +287,14 @@ val s2 = if (ret != null) { // Additionally, we need to handle the `else` branches. ``` -## Binary Compatibility +## Flow Typing -Our strategy for binary compatibility with Scala binaries that predate explicit nulls -is to leave the types unchanged and be compatible but unsound. +TODO -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) -``` +## Binary Compatibility -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 = ??? -} +Our strategy for binary compatibility with Scala binaries that predate explicit nulls +and new libraries compiled without `-Yexplicit-nulls` is to leave the types unchanged +and be compatible but unsound. -// User code in explicit-null world -val o: Old = ??? -o.foo(null.asInstanceOf[String]) // ok: cast will succeed at runtime -``` +[More details](../../internals/intersection-types-spec.md) From 49d550e1bd5aa1f25d39c2bb9afb41e19a6630ca Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 4 Dec 2019 15:25:42 -0500 Subject: [PATCH 27/39] Add flow typing to doc --- docs/docs/internals/explicit-nulls.md | 85 +++++----- .../other-new-features/explicit-nulls.md | 154 +++++++++++++++++- 2 files changed, 195 insertions(+), 44 deletions(-) diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index b0366dd47edf..8a6a0341aba5 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -28,9 +28,6 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by: ## 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` @@ -43,25 +40,19 @@ At a high-level: - 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 +The nullification logic lives in `compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala`. + +The entry point is the function +`def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(implicit ctx: Context): Type` +which, given a symbol, its "regular" type, and a boolean whether it is a Enum value definition, +produces what the type of the symbol should be in the explicit nulls world. + +1. If the symbol is a Enum value definition or a `TYPE_` field, we don't nullify the type +2. If it is `toString()` method or the constructor, or it has a `@NotNull` annotation, + we nullify the type, without a `JavaNull` at the outmost level. +3. Otherwise, we nullify the type in regular way. + +See `JavaNullMap` in `JavaNullInterop.scala` for more details about how we nullify different types. ## JavaNull @@ -73,7 +64,7 @@ val s: String|JavaNull = "hello" s.length // allowed, but might throw NPE ``` -`JavaNull` is defined as `JavaNullAlias` in `Definitions`. +`JavaNull` is defined as `JavaNullAlias` in `Definitions.scala`. 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) @@ -81,24 +72,38 @@ The logic to allow member selections is defined in `findMember` in `Types.scala` ## Working with Nullable Unions -Within `Types.scala`, we defined a few utility methods to work with nullable unions. All of these +Within `Types.scala`, we defined some extractors to work with nullable unions: +`OrNull` and `OrJavaNull`. + +```scala +(tp: Type) match { + case OrNull(tp1) => // if tp is a nullable union: tp1 | Null + case _ => // otherwise +} +``` + +These extractor will call utility methods in `NullOpsDecorator.scala`. 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. + +- `normNullableUnion` normalizes unions so that the `Null` type (or aliases to `Null`) + appears to the right of all other types. + +- `isNullableUnion` determines whether `this` is a nullable union. +- `isJavaNullableUnion` determines whether `this` is syntactically a union of the form + `T|JavaNull` +- `stripNull` syntactically strips all `Null` types in the union: + e.g. `String|Null => String`. +- `stripAllJavaNull` is like `stripNull` but only removes `JavaNull` from the union. + This is needed when we want to "revert" the Java nullification function. ## Flow Typing -TODO +`NotNullInfo`s are collected as we typing each statements, see `Nullables.scala` for more +details about how we compute `NotNullInfo`s. + +When we type an identity or a select tree (in `typedIdent` and `typedSelect`), we will +call `toNotNullTermRef` on the tree before reture the result. If the tree `x` has nullable +type `T|Null` and it is known to be not null according to the `NotNullInfo` and it is not +on the lhs of assignment, then we cast it to `x.type & T` using `defn.Any_typeCast`. The +reason to have a `TermRef(x)` in the `AndType` is that we can track the new result as well and +use it as a path. diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md index 9e94cfc5d390..38b19073b9c7 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -192,7 +192,6 @@ Specifically, we patch final char CHAR = 'a'; final String NAME_GENERATED = getNewName(); - final int VALUE = 0 * 2; } ``` ==> @@ -203,7 +202,6 @@ Specifically, we patch val CHAR: Char('a') = 'a' val NAME_GENERATED: String | Null = ??? - val VALUE: Int = ??? } ``` @@ -289,7 +287,155 @@ val s2 = if (ret != null) { ## Flow Typing -TODO +We added a simple form of flow-sensitive type inference. The idea is that if `p` is a +stable path or a trackable variable, then we can know that `p` is non-null if it's compared +with the `null`. 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 + +assert(x != null) +// s: String +``` + +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 +} +``` + +`==` and `!=` is considered a comparison for the purposes of the flow inference. + +### 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 +} +``` + +### Match Case + +The non-null cases can be detected in match statements. + +```scala +val s: String|Null = ??? + +s match { + case _: String => // s: String + case _ => +} +``` + +### Mutable Variable + +A mutable vriable is trackable with following restrictions: + +1. All the assignment must in the same closure as the definition (more strictly, + reachable by the definition). +2. We only analyze the comparisons and use the facts in the same closure as + the definition. + +```scala +class C(val x: Int, val next: C|Null) + +var xs: C|Null = C(1, C(2, null)) +// xs is trackable, since all assignments are in the same mathod +while (xs != null) { + // xs: C + val xsx: Int = xs.x + val xscpy: C = xs + xs = xscpy // since xscpy is non-null, xs still has type C after this line + // xs: C + xs = xs.next // after this assignment, xs can be null again + // xs: C | Null +} +``` + +```scala +var x: String|Null = ??? +def y = { + x = null +} +if (x != null) { + // y can be called here + val a: String = x // error: x is captured and mutated by the closure, not tackable +} +``` + +```scala +var x: String|Null = ??? +def y = { + if (x != null) { + // not safe to use the fact (x != null) here + // since y can be executed at the same time as the outer block + val _: String = x + } +} +if (x != null) { + val a: String = x // ok to use the fact here + x = null +} +``` + +Currently, we are unable to track `x.a` if `x` is mutable. + +### Unsupported Idioms + +We don't support: + +- 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 + } + ``` ## Binary Compatibility @@ -297,4 +443,4 @@ Our strategy for binary compatibility with Scala binaries that predate explicit and new libraries compiled without `-Yexplicit-nulls` is to leave the types unchanged and be compatible but unsound. -[More details](../../internals/intersection-types-spec.md) +[More details](../../internals/explicit-nulls.md) From bdbde1d5433d303d38e68801a3125814a25c22ee Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 4 Dec 2019 15:40:12 -0500 Subject: [PATCH 28/39] Simplify case --- .../src/dotty/tools/dotc/typer/Nullables.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 5444a31d2fc0..25e7487a2764 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -10,6 +10,7 @@ import util.Property import Names.Name import util.Spans.Span import Flags.Mutable +import NullOpsDecorator._ import collection.mutable /** Operations for implementing a flow analysis for nullability */ @@ -258,15 +259,13 @@ object Nullables with given assignOps: (tree: Assign) def computeAssignNullable()(given Context): tree.type = tree.lhs match case TrackedRef(ref) => - def withoutRef: tree.type = tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - tree.rhs.typeOpt match - // If the type of rhs is `T|Null`, then the nullability of the lhs variable is no longer - // trackable. We don't need to check whether the type `T` is correct here, as typer will - // check it. - case OrNull(_) => withoutRef - // If the type of rhs is Null, we discard its NotNullInfo. - case tp if tp.isNullType => withoutRef - case _ => tree + val rhstp = tree.rhs.typeOpt + if (rhstp.isNullType || rhstp.isNullableUnion) + // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the + // lhs variable is no longer trackable. We don't need to check whether the type `T` + // is correct here, as typer will check it. + tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) + else tree case _ => tree private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) From 58db0c0ce8dca2f2c0c3cce3fc6f34bceb7cbe84 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 4 Dec 2019 16:21:12 -0500 Subject: [PATCH 29/39] Refine comments --- .../dotty/tools/dotc/typer/Nullables.scala | 43 ++++++++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 2 + docs/docs/internals/explicit-nulls.md | 49 +++++++++++++++---- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 25e7487a2764..2eafe65c7df9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -105,17 +105,50 @@ object Nullables with * This is the case if the reference is a path to an immutable val, or if it refers * to a local mutable variable where all assignments to the variable are _reachable_ * (in the sense of how it is defined in assignmentSpans). + * + * A mutable vriable is trackable with following restrictions: + * 1. All the assignment must be reachable by the definition. + * 2. We only analyze the comparisons and use the facts in the same closure as + * the definition. + * + * ```scala + * var x: String|Null = ??? + * def y = { + * x = null + * } + * if (x != null) { + * // y can be called here + * val a: String = x // error: x is captured and mutated by the closure, not tackable + * } + * ``` + * + * ```scala + * var x: String|Null = ??? + * def y = { + * if (x != null) { + * // not safe to use the fact (x != null) here + * // since y can be executed at the same time as the outer block + * val _: String = x + * } + * } + * if (x != null) { + * val a: String = x // ok to use the fact here + * x = null + * } + * ``` + * + * See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`. */ def isTracked(ref: TermRef)(given Context) = ref.isStable || { val sym = ref.symbol sym.is(Mutable) && sym.owner.isTerm - && (if sym.owner != curCtx.owner then - // TODO: need to check by-name parameters - !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef - && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different DefDef - else true) + && ( sym.owner == curCtx.owner + || !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef + && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different methods + // TODO: need to check by-name paramter + ) && sym.span.exists && curCtx.compilationUnit != null // could be null under -Ytest-pickler && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 66ddc6c36909..4d3fd2072d98 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -348,6 +348,8 @@ class Typer extends Namer findRefRecur(NoType, BindingPrec.NothingBound, NoContext) } + // If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then + // cast away `tree`s nullability. Otherwise, `tree` remains unchanged. def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 8a6a0341aba5..4e8c41abb537 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -90,7 +90,7 @@ are methods of the `Type` class, so call them with `this` as a receiver: - `isNullableUnion` determines whether `this` is a nullable union. - `isJavaNullableUnion` determines whether `this` is syntactically a union of the form - `T|JavaNull` + `T|JavaNull`. - `stripNull` syntactically strips all `Null` types in the union: e.g. `String|Null => String`. - `stripAllJavaNull` is like `stripNull` but only removes `JavaNull` from the union. @@ -98,12 +98,43 @@ are methods of the `Type` class, so call them with `this` as a receiver: ## Flow Typing -`NotNullInfo`s are collected as we typing each statements, see `Nullables.scala` for more -details about how we compute `NotNullInfo`s. +As typing happens, we accumulate a set of `NotNullInfo`s in the `Context` (see +`Contexts.scala`). A `NotNullInfo` contains the set of `TermRef`s that are known to +be non-null at the current program point. See `Nullables.scala` for how `NotNullInfo`s +are computed. -When we type an identity or a select tree (in `typedIdent` and `typedSelect`), we will -call `toNotNullTermRef` on the tree before reture the result. If the tree `x` has nullable -type `T|Null` and it is known to be not null according to the `NotNullInfo` and it is not -on the lhs of assignment, then we cast it to `x.type & T` using `defn.Any_typeCast`. The -reason to have a `TermRef(x)` in the `AndType` is that we can track the new result as well and -use it as a path. +During type-checking, when we type an identity or a select tree (in `typedIdent` and +`typedSelect`), we will call `toNotNullTermRef` on the tree before return the typed tree. +If the tree `x` has nullable type `T|Null` and it is known to be not null according to +the `NotNullInfo` and it is not on the lhs of assignment, then we cast it to `x.type & T` +using `defn.Any_typeCast`. + +The reason for casting to `x.type & T`, as opposed to just `T`, is that it allows us to +support flow typing for paths of length greater than one. + +```scala +abstract class Node { + val x: String + val next: Node | Null +} + +def f = { + val l: Node|Null = ??? + if (l != null && l.next != null) { + val third: l.next.next.type = l.next.next + } +} +``` + +After typing, `f` becomes: + +```scala +def f = { + val l: Node|Null = ??? + if (l != null && l.$asInstanceOf$[l.type & Node].next != null) { + val third: + l.$asInstanceOf$[l.type & Node].next.$asInstanceOf$[(l.type & Node).next.type & Node].next.type = + l.$asInstanceOf$[l.type & Node].next.$asInstanceOf$[(l.type & Node).next.type & Node].next + } +} +``` From 91b54aa25f6832ea75497e4e384f1b246b786ec2 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 4 Dec 2019 16:28:56 -0500 Subject: [PATCH 30/39] fix assert --- compiler/src/dotty/tools/dotc/typer/Nullables.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 2eafe65c7df9..61008c87de59 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -293,7 +293,7 @@ object Nullables with def computeAssignNullable()(given Context): tree.type = tree.lhs match case TrackedRef(ref) => val rhstp = tree.rhs.typeOpt - if (rhstp.isNullType || rhstp.isNullableUnion) + if (rhstp.isNullType || (curCtx.explicitNulls && rhstp.isNullableUnion)) // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the // lhs variable is no longer trackable. We don't need to check whether the type `T` // is correct here, as typer will check it. From 61ca5e662b9bbf262c72452d8a51262a67715b26 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 4 Dec 2019 17:07:47 -0500 Subject: [PATCH 31/39] Add more comments and examples --- .../dotty/tools/dotc/typer/Nullables.scala | 59 +++++++------- docs/docs/internals/explicit-nulls.md | 2 + .../other-new-features/explicit-nulls.md | 78 +++++++++++-------- 3 files changed, 80 insertions(+), 59 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 61008c87de59..3f0c19bc8505 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -106,36 +106,43 @@ object Nullables with * to a local mutable variable where all assignments to the variable are _reachable_ * (in the sense of how it is defined in assignmentSpans). * - * A mutable vriable is trackable with following restrictions: - * 1. All the assignment must be reachable by the definition. - * 2. We only analyze the comparisons and use the facts in the same closure as - * the definition. + * When dealing with local mutable variables, there are two questions: * - * ```scala - * var x: String|Null = ??? - * def y = { - * x = null - * } - * if (x != null) { - * // y can be called here - * val a: String = x // error: x is captured and mutated by the closure, not tackable - * } - * ``` + * 1. Whether to track a local mutable variable during flow typing. + * We track a local mutable variable iff the variable is not assigned in a closure. + * For example, in the following code `x` is assigned to by the closure `y`, so we do not + * do flow typing on `x`. + * ```scala + * var x: String|Null = ??? + * def y = { + * x = null + * } + * if (x != null) { + * // y can be called here, which break the fact + * val a: String = x // error: x is captured and mutated by the closure, not tackable + * } + * ``` * - * ```scala - * var x: String|Null = ??? - * def y = { + * 2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable. + * We only want to do flow typing on a use that belongs to the same method as the definition + * of the local variable. + * For example, in the following code, even `x` is not assigned to by a closure, but we can only + * use flow typing in one of the occurrences (because the other occurrence happens within a nested + * closure). + * ```scala + * var x: String|Null = ??? + * def y = { + * if (x != null) { + * // not safe to use the fact (x != null) here + * // since y can be executed at the same time as the outer block + * val _: String = x + * } + * } * if (x != null) { - * // not safe to use the fact (x != null) here - * // since y can be executed at the same time as the outer block - * val _: String = x + * val a: String = x // ok to use the fact here + * x = null * } - * } - * if (x != null) { - * val a: String = x // ok to use the fact here - * x = null - * } - * ``` + * ``` * * See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`. */ diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 4e8c41abb537..6f00a9d376eb 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -138,3 +138,5 @@ def f = { } } ``` +Notice that in the example above `(l.type & Node).next.type & Node` is still a stable path, so +we can use it in the type and track it for flow typing. diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md index 38b19073b9c7..48038d9b6d4d 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -371,12 +371,7 @@ s match { ### Mutable Variable -A mutable vriable is trackable with following restrictions: - -1. All the assignment must in the same closure as the definition (more strictly, - reachable by the definition). -2. We only analyze the comparisons and use the facts in the same closure as - the definition. +We are able to detect the nullability of some local mutable variables. A simple example is: ```scala class C(val x: Int, val next: C|Null) @@ -394,33 +389,50 @@ while (xs != null) { } ``` -```scala -var x: String|Null = ??? -def y = { - x = null -} -if (x != null) { - // y can be called here - val a: String = x // error: x is captured and mutated by the closure, not tackable -} -``` - -```scala -var x: String|Null = ??? -def y = { - if (x != null) { - // not safe to use the fact (x != null) here - // since y can be executed at the same time as the outer block - val _: String = x - } -} -if (x != null) { - val a: String = x // ok to use the fact here - x = null -} -``` - -Currently, we are unable to track `x.a` if `x` is mutable. +When dealing with local mutable variables, there are two questions: + +1. Whether to track a local mutable variable during flow typing. + We track a local mutable variable iff the variable is not assigned in a closure. + For example, in the following code `x` is assigned to by the closure `y`, so we do not + do flow typing on `x`. + + ```scala + var x: String|Null = ??? + def y = { + x = null + } + if (x != null) { + // y can be called here, which break the fact + val a: String = x // error: x is captured and mutated by the closure, not tackable + } + ``` + +2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable. + We only want to do flow typing on a use that belongs to the same method as the definition + of the local variable. + For example, in the following code, even `x` is not assigned to by a closure, but we can only + use flow typing in one of the occurrences (because the other occurrence happens within a nested + closure). + + ```scala + var x: String|Null = ??? + def y = { + if (x != null) { + // not safe to use the fact (x != null) here + // since y can be executed at the same time as the outer block + val _: String = x + } + } + if (x != null) { + val a: String = x // ok to use the fact here + x = null + } + ``` + +See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`. + +Currently, we are unable to track paths with a mutable variable prefix. +For example, `x.a` if `x` is mutable. ### Unsupported Idioms From 77df7548c8528bd26bc501d5d99e77e51aa3c564 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 9 Dec 2019 17:33:49 -0500 Subject: [PATCH 32/39] add usedOutOfOrder --- .../dotty/tools/dotc/typer/Nullables.scala | 26 ++++++++++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 1 + .../neg/var-ref-in-closure.scala | 12 +++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 3f15786eb8c5..5ed13bf1b447 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -9,7 +9,7 @@ import StdNames.nme import util.Property import Names.Name import util.Spans.Span -import Flags.Mutable +import Flags._ import NullOpsDecorator._ import collection.mutable @@ -151,11 +151,7 @@ object Nullables with || { val sym = ref.symbol sym.is(Mutable) && sym.owner.isTerm - && ( sym.owner == curCtx.owner - || !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef - && sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different methods - // TODO: need to check by-name paramter - ) + && !ref.usedOutOfOrder // todo: remove && sym.span.exists && curCtx.compilationUnit != null // could be null under -Ytest-pickler && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) @@ -206,6 +202,24 @@ object Nullables with then infos else info :: infos + given refOps: extension (ref: TermRef) with + + def usedOutOfOrder(given Context) = + val refSym = ref.symbol + val refOwner = refSym.owner + + def enclosedInClosure(s: Symbol): Boolean = + s != NoSymbol + && s != refOwner + && (s.isOneOf(Lazy | Method) // not at the rhs of lazy ValDef or in a method (or lambda) + || s.isClass // not in a class + // TODO: need to check by-name paramter + || enclosedInClosure(s.owner)) + + refSym.is(Mutable) + && refSym.owner.isTerm + && enclosedInClosure(curCtx.owner) + given treeOps: extension (tree: Tree) with /* The `tree` with added nullability attachment */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4d3fd2072d98..8db16d9f410d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -353,6 +353,7 @@ class Typer extends Namer def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign + !ref.usedOutOfOrder && ctx.notNullInfos.impliesNotNull(ref) => tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) case _ => diff --git a/tests/explicit-nulls/neg/var-ref-in-closure.scala b/tests/explicit-nulls/neg/var-ref-in-closure.scala index e1b50df200bc..6e2e305f8fef 100644 --- a/tests/explicit-nulls/neg/var-ref-in-closure.scala +++ b/tests/explicit-nulls/neg/var-ref-in-closure.scala @@ -13,6 +13,18 @@ object VarRef { } } + locally { + var x: String|Null = ??? + var y = { + if (x != null) { + val _: String = x // ok: y doesn't create closure + } + } + if (x != null) { + val a: String = x // ok + } + } + locally { var x: String|Null = ??? lazy val y = { From 6bdb9f0b8c38b0e9c927e2fb3d0c756d95e4a7b2 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 10 Dec 2019 14:45:01 -0500 Subject: [PATCH 33/39] Add tests --- .../dotty/tools/dotc/typer/ConstFold.scala | 1 - .../dotty/tools/dotc/typer/Nullables.scala | 11 +++++----- .../neg/var-ref-in-closure.scala | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index db42fc462da3..0a9481bcea05 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -11,7 +11,6 @@ import Constants._ import Names._ import StdNames._ import Contexts._ -import Nullables.{CompareNull, TrackedRef} import NullOpsDecorator._ object ConstFold { diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 5ed13bf1b447..6249426770f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -104,7 +104,9 @@ object Nullables with /** Is given reference tracked for nullability? * This is the case if the reference is a path to an immutable val, or if it refers * to a local mutable variable where all assignments to the variable are _reachable_ - * (in the sense of how it is defined in assignmentSpans). + * (in the sense of how it is defined in assignmentSpans). We use this function to decide + * whether we need to compute the NotNullInfo and add it to the context. This requires the + * use of a mutable variable is not out of order. * * When dealing with local mutable variables, there are two questions: * @@ -149,9 +151,7 @@ object Nullables with def isTracked(ref: TermRef)(given Context) = ref.isStable || { val sym = ref.symbol - sym.is(Mutable) - && sym.owner.isTerm - && !ref.usedOutOfOrder // todo: remove + !ref.usedOutOfOrder && sym.span.exists && curCtx.compilationUnit != null // could be null under -Ytest-pickler && curCtx.compilationUnit.assignmentSpans.contains(sym.span.start) @@ -204,7 +204,8 @@ object Nullables with given refOps: extension (ref: TermRef) with - def usedOutOfOrder(given Context) = + /* Is the use of a mutable variable out of order */ + def usedOutOfOrder(given Context): Boolean = val refSym = ref.symbol val refOwner = refSym.owner diff --git a/tests/explicit-nulls/neg/var-ref-in-closure.scala b/tests/explicit-nulls/neg/var-ref-in-closure.scala index 6e2e305f8fef..a336fa6c795c 100644 --- a/tests/explicit-nulls/neg/var-ref-in-closure.scala +++ b/tests/explicit-nulls/neg/var-ref-in-closure.scala @@ -149,4 +149,24 @@ object VarRef { x } } + + locally { + val x: String|Null = ??? + if (x != null) { + def f = { + val y: String = x // ok, x is a value definition + y + } + } + } + + locally { + var x: String|Null = ??? + if (x != null) { + def f = { + val y: String = x // error: the use of x is out of order + y + } + } + } } From 7dc2e760f978c0aaac62ae8522c577e389f99a40 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 11 Dec 2019 16:14:01 -0500 Subject: [PATCH 34/39] Optimize NullOps; add helper functions --- .../dotty/tools/dotc/core/Definitions.scala | 16 +-- .../tools/dotc/core/JavaNullInterop.scala | 68 +++++----- .../tools/dotc/core/NullOpsDecorator.scala | 123 +++++------------- .../src/dotty/tools/dotc/core/Symbols.scala | 5 +- .../src/dotty/tools/dotc/core/Types.scala | 40 +++--- .../dotty/tools/dotc/typer/ConstFold.scala | 1 - .../dotty/tools/dotc/typer/Nullables.scala | 6 +- .../src/dotty/tools/dotc/typer/Typer.scala | 63 +++++---- docs/docs/internals/explicit-nulls.md | 13 +- .../other-new-features/explicit-nulls.md | 2 +- tests/explicit-nulls/neg/simple-var.scala | 59 +++++---- .../neg/var-ref-in-closure.scala | 20 +-- 12 files changed, 186 insertions(+), 230 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 75d874420ab6..8c27bb6870fb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -459,12 +459,12 @@ class Definitions { @tu lazy val Boolean_|| : Symbol = BooleanClass.requiredMethod(nme.ZOR) @tu lazy val Boolean_== : Symbol = BooleanClass.info.member(nme.EQ).suchThat(_.info.firstParamTypes match { - case List(pt) => (pt isRef BooleanClass) + case List(pt) => pt.isRef(BooleanClass) case _ => false }).symbol @tu lazy val Boolean_!= : Symbol = BooleanClass.info.member(nme.NE).suchThat(_.info.firstParamTypes match { - case List(pt) => (pt isRef BooleanClass) + case List(pt) => pt.isRef(BooleanClass) case _ => false }).symbol @@ -527,7 +527,7 @@ class Definitions { @tu lazy val StringModule: Symbol = StringClass.linkedClass @tu lazy val String_+ : TermSymbol = enterMethod(StringClass, nme.raw.PLUS, methOfAny(StringType), Final) @tu lazy val String_valueOf_Object: Symbol = StringModule.info.member(nme.valueOf).suchThat(_.info.firstParamTypes match { - case List(pt) => (pt isRef AnyClass) || (pt isRef ObjectClass) + case List(pt) => pt.isRef(AnyClass) || pt.isRef(ObjectClass) case _ => false }).symbol @@ -539,15 +539,15 @@ class Definitions { @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) => - val pt1 = if (ctx.explicitNulls) pt.stripNull else pt - pt1 isRef StringClass + 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) => - val pt1 = if (ctx.explicitNulls) pt.stripNull else pt - pt1 isRef StringClass + val pt1 = if (ctx.explicitNulls) pt.stripNull() else pt + pt1.isRef(StringClass) case _ => false }).symbol.asTerm @@ -886,7 +886,7 @@ class Definitions { if (ctx.erasedTypes) JavaArrayType(elem) else ArrayType.appliedTo(elem :: Nil) def unapply(tp: Type)(implicit ctx: Context): Option[Type] = tp.dealias match { - case AppliedType(at, arg :: Nil) if at isRef ArrayType.symbol => Some(arg) + case AppliedType(at, arg :: Nil) if at.isRef(ArrayType.symbol) => Some(arg) case _ => None } } diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 94f43ef8091c..52ba2ac462e4 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -114,43 +114,37 @@ object JavaNullInterop { case _ => true }) - override def apply(tp: Type): Type = { - // Fast version of Type::toJavaNullableUnion that doesn't check whether the type - // is already a union. - def toJavaNullableUnion(tpe: Type): Type = OrType(tpe, defn.JavaNullAliasType) - - tp match { - case tp: TypeRef if needsNull(tp) => toJavaNullableUnion(tp) - case appTp @ AppliedType(tycon, targs) => - val oldOutermostNullable = outermostLevelAlreadyNullable - // We don't make the outmost levels of type arguements nullable if tycon is Java-defined. - // This is because Java classes are _all_ nullified, so both `java.util.List[String]` and - // `java.util.List[String|Null]` contain nullable elements. - outermostLevelAlreadyNullable = tp.classSymbol.is(JavaDefined) - val targs2 = targs map this - outermostLevelAlreadyNullable = oldOutermostNullable - val appTp2 = derivedAppliedType(appTp, tycon, targs2) - if (needsNull(tycon)) toJavaNullableUnion(appTp2) else appTp2 - case ptp: PolyType => - derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) - case mtp: MethodType => - val oldOutermostNullable = outermostLevelAlreadyNullable - outermostLevelAlreadyNullable = false - val paramInfos2 = mtp.paramInfos map this - outermostLevelAlreadyNullable = oldOutermostNullable - derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) - case tp: TypeAlias => mapOver(tp) - case tp: AndType => - // nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add - // duplicate `JavaNull`s at the outermost level inside `A` and `B`. - outermostLevelAlreadyNullable = true - toJavaNullableUnion(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) - case tp: TypeParamRef if needsNull(tp) => toJavaNullableUnion(tp) - // In all other cases, return the type unchanged. - // In particular, if the type is a ConstantType, then we don't nullify it because it is the - // type of a final non-nullable field. - case _ => tp - } + override def apply(tp: Type): Type = tp match { + case tp: TypeRef if needsNull(tp) => OrJavaNull(tp) + case appTp @ AppliedType(tycon, targs) => + val oldOutermostNullable = outermostLevelAlreadyNullable + // We don't make the outmost levels of type arguements nullable if tycon is Java-defined. + // This is because Java classes are _all_ nullified, so both `java.util.List[String]` and + // `java.util.List[String|Null]` contain nullable elements. + outermostLevelAlreadyNullable = tp.classSymbol.is(JavaDefined) + val targs2 = targs map this + outermostLevelAlreadyNullable = oldOutermostNullable + val appTp2 = derivedAppliedType(appTp, tycon, targs2) + if (needsNull(tycon)) OrJavaNull(appTp2) else appTp2 + case ptp: PolyType => + derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) + case mtp: MethodType => + val oldOutermostNullable = outermostLevelAlreadyNullable + outermostLevelAlreadyNullable = false + val paramInfos2 = mtp.paramInfos map this + outermostLevelAlreadyNullable = oldOutermostNullable + derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) + case tp: TypeAlias => mapOver(tp) + case tp: AndType => + // nullify(A & B) = (nullify(A) & nullify(B)) | JavaNull, but take care not to add + // duplicate `JavaNull`s at the outermost level inside `A` and `B`. + outermostLevelAlreadyNullable = true + OrJavaNull(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) + case tp: TypeParamRef if needsNull(tp) => OrJavaNull(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/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index c682237e78e3..30d5f9d81d8b 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -17,101 +17,46 @@ object NullOpsDecorator { 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 | JavaNull` + /** Syntactically strips the nullability from this type. + * If the normalized form (as per `normNullableUnion`) of this type is `T1 | ... | Tn-1 | Tn`, + * and `Tn` references to `Null` (or `JavaNull`), then return `T1 | ... | Tn-1`. + * If this type isn't (syntactically) nullable, then returns the type unchanged. * - * 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`). + * @param onlyJavaNull whether we only remove `JavaNull`, the default value is false */ - def normNullableUnion(implicit ctx: Context): Type = { - var hasNull = false - var hasJavaNull = false + def stripNull(onlyJavaNull: Boolean = false)(implicit ctx: Context): Type = { + assert(ctx.explicitNulls) + + def isNull(tp: Type) = + if (onlyJavaNull) tp.isJavaNullType + else tp.isNullType + def strip(tp: Type): Type = tp match { case tp @ OrType(lhs, rhs) => val llhs = strip(lhs) val rrhs = strip(rhs) - if (rrhs.isNullType) llhs - else if (llhs.isNullType) rrhs + if (isNull(rrhs)) llhs + else if (isNull(llhs)) rrhs else tp.derivedOrType(llhs, rrhs) case tp @ AndType(tp1, tp2) => // We cannot `tp.derivedAndType(strip(tp1), strip(tp2))` directly, // since `normNullableUnion((A | Null) & B)` would produce the wrong // result `(A & B) | Null`. - val oldHN = hasNull - val oldHJN = hasJavaNull val tp1s = strip(tp1) val tp2s = strip(tp2) if((tp1s ne tp1) && (tp2s ne tp2)) tp.derivedAndType(tp1s, tp2s) - else - // If tp1 or tp2 is not nullable, we should revert the change of - // `hasNull` and `hasJavaNull` and return the original tp. - hasNull = oldHN - hasJavaNull = oldHJN - tp - case _ => - if (tp.isNullType) { - if (tp.isJavaNullType) hasJavaNull = true - else hasNull = true - } - tp + else tp + case _ => tp } - val tp = strip(self) - if (tp eq self) self - 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`? */ - def isNullableUnion(implicit ctx: Context): Boolean = { - assert(ctx.explicitNulls) - 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) - self.widenDealias.normNullableUnion match { - case OrType(_, rhs) => rhs.isJavaNullType - case _ => false - } - } - - def maybeNullable(implicit ctx: Context): Type = - if (ctx.explicitNulls) OrType(self, defn.NullType) else self - /** Syntactically strips the nullability from this type. - * If the normalized form (as per `normNullableUnion`) of this type is `T1 | ... | Tn-1 | Tn`, - * 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 - case _ => self - } + val self1 = self.widenDealias + val striped = strip(self1) + if (striped ne self1) striped else self } /** Like `stripNull`, but removes only the `JavaNull`s. */ - def stripJavaNull(implicit ctx: Context): Type = { - assert(ctx.explicitNulls) - self.widenDealias.normNullableUnion match { - case OrType(lhs, rhs) if rhs.isJavaNullType => lhs - case _ => self - } - } + def stripJavaNull(implicit ctx: Context): Type = self.stripNull(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] @@ -119,25 +64,23 @@ object NullOpsDecorator { * If no `JavaNull` unions are found within the type, then returns the input type unchanged. */ def stripAllJavaNull(implicit ctx: Context): Type = { - assert(ctx.explicitNulls) object RemoveNulls extends TypeMap { - override def apply(tp: Type): Type = - tp.normNullableUnion match { - case OrType(lhs, rhs) if rhs.isJavaNullType => - mapOver(lhs) - case _ => mapOver(tp) - } + override def apply(tp: Type): Type = mapOver(tp.stripNull(true)) } - val self1 = self.widenDealias - val rem = RemoveNulls(self1) - if (rem ne self1) rem else self + val rem = RemoveNulls(self) + if (rem ne self) 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) + /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ + def isNullableUnion(implicit ctx: Context): Boolean = { + val striped = self.stripNull() + striped ne self + } + + /** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */ + def isJavaNullableUnion(implicit ctx: Context): Boolean = { + val striped = self.stripNull(true) + striped ne self } } } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index d0226669c9d1..d783780f593d 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -387,10 +387,7 @@ trait Symbols { this: Context => * run or present on classpath. */ def getClassesIfDefined(paths: List[PreName]): List[ClassSymbol] = - paths.foldLeft(List.empty){ case (acc, path) => getClassIfDefined(path) match { - case cls: ClassSymbol => cls :: acc - case _ => acc - }} + paths.map(getClassIfDefined).filter(_.exists).map(_.asInstanceOf[ClassSymbol]) /** Get ClassSymbol if package is either defined in current compilation run * or present on classpath. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 792f4dac1598..42c2e5bebb01 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -605,20 +605,7 @@ object Types { case AndType(l, r) => goAnd(l, r) case tp: OrType => - tp match { - case OrJavaNull(tp1) => - // 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. - tp1.findMember(name, pre.stripJavaNull, required, excluded) - case _ => - // 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) - } + goOr(tp) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, required, excluded) case err: ErrorType => @@ -724,6 +711,21 @@ object Types { def goAnd(l: Type, r: Type) = go(l) & (go(r), pre, safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + def goOr(tp: OrType) = tp match { + case OrJavaNull(tp1) => + // 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. + tp1.findMember(name, pre.stripJavaNull, required, excluded) + case _ => + // 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) + } + val recCount = ctx.base.findMemberCount if (recCount >= Config.LogPendingFindMemberThreshold) ctx.base.pendingMemberSearches = name :: ctx.base.pendingMemberSearches @@ -2950,11 +2952,11 @@ object Types { def apply(tp: Type)(given Context) = OrType(tp, defn.NullType) def unapply(tp: Type)(given ctx: Context): Option[Type] = - if (ctx.explicitNulls) { - val tp1 = tp.stripNull - if tp1 ne tp then Some(tp1) else None - } - else None + if (ctx.explicitNulls) { + val tp1 = tp.stripNull() + if tp1 ne tp then Some(tp1) else None + } + else None } /** An extractor object to pattern match against a Java-nullable union. diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 0a9481bcea05..6e95cb2714e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -11,7 +11,6 @@ import Constants._ import Names._ import StdNames._ import Contexts._ -import NullOpsDecorator._ object ConstFold { diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 6249426770f5..c488720f21d7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -121,7 +121,7 @@ object Nullables with * } * if (x != null) { * // y can be called here, which break the fact - * val a: String = x // error: x is captured and mutated by the closure, not tackable + * val a: String = x // error: x is captured and mutated by the closure, not trackable * } * ``` * @@ -320,7 +320,9 @@ object Nullables with // lhs variable is no longer trackable. We don't need to check whether the type `T` // is correct here, as typer will check it. tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - else tree + else + // otherwise, we know the variable will have a non-null value + tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) case _ => tree private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8db16d9f410d..f46d7bef0713 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -348,8 +348,13 @@ class Typer extends Namer findRefRecur(NoType, BindingPrec.NothingBound, NoContext) } - // If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then - // cast away `tree`s nullability. Otherwise, `tree` remains unchanged. + /** If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then + * cast away `tree`s nullability. Otherwise, `tree` remains unchanged. + * + * Example: + * If x is a trackable reference and we know x is not null at this point, + * (x: T | Null) => x.$asInstanceOf$[x.type & T] + */ def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign @@ -2223,30 +2228,8 @@ class Typer extends Namer case Some(xtree) => traverse(xtree :: rest) case none => - def defCtx = ctx.withNotNullInfos(initialNotNullInfos) - val newCtx = if (ctx.owner.isTerm) { - // Keep preceding not null facts in the current context only if `mdef` - // cannot be executed out-of-sequence. - // We have to check the Completer of symbol befor typedValDef, - // otherwise the symbol is already completed using creation context. - mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match { - case Some((sym, completer: Namer#Completer)) => - if (completer.creationContext.notNullInfos ne ctx.notNullInfos) - // The RHS of a val def should know about not null facts established - // in preceding statements (unless the DefTree is completed ahead of time, - // then it is impossible). - sym.info = Completer(completer.original)( - given completer.creationContext.withNotNullInfos(ctx.notNullInfos)) - ctx // all preceding statements will have been executed in this case - case _ => - // If it has been completed, then it must be because there is a forward reference - // to the definition in the program. Hence, we don't Keep preceding not null facts - // in the current context. - defCtx - } - } - else defCtx - + val newCtx = if (ctx.owner.isTerm && adaptCreationContext(mdef)) ctx + else ctx.withNotNullInfos(initialNotNullInfos) typed(mdef)(given newCtx) match { case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) @@ -2297,6 +2280,34 @@ class Typer extends Namer (stats1, finalCtx) } + /** Tries to adapt NotNullInfos from creation context to the DefTree, + * returns whether the adaption is successed. The adaption only success if the + * DefTree has a symbol and it has not been completed (is not forward referenced). + */ + def adaptCreationContext(mdef: untpd.DefTree)(implicit ctx: Context): Boolean = + // Keep preceding not null facts in the current context only if `mdef` + // cannot be executed out-of-sequence. + // We have to check the Completer of symbol befor typedValDef, + // otherwise the symbol is already completed using creation context. + mdef.getAttachment(SymOfTree) match { + case Some(sym) => sym.infoOrCompleter match { + case completer: Namer#Completer => + if (completer.creationContext.notNullInfos ne ctx.notNullInfos) + // The RHS of a val def should know about not null facts established + // in preceding statements (unless the DefTree is completed ahead of time, + // then it is impossible). + sym.info = Completer(completer.original)( + given completer.creationContext.withNotNullInfos(ctx.notNullInfos)) + true + case _ => + // If it has been completed, then it must be because there is a forward reference + // to the definition in the program. Hence, we don't Keep preceding not null facts + // in the current context. + false + } + case _ => false + } + /** Given an inline method `mdef`, the method rewritten so that its body * uses accessors to access non-public members. * Overwritten in Retyper to return `mdef` unchanged. diff --git a/docs/docs/internals/explicit-nulls.md b/docs/docs/internals/explicit-nulls.md index 6f00a9d376eb..73d7fb9e7178 100644 --- a/docs/docs/internals/explicit-nulls.md +++ b/docs/docs/internals/explicit-nulls.md @@ -85,16 +85,15 @@ Within `Types.scala`, we defined some extractors to work with nullable unions: These extractor will call utility methods in `NullOpsDecorator.scala`. All of these are methods of the `Type` class, so call them with `this` as a receiver: -- `normNullableUnion` normalizes unions so that the `Null` type (or aliases to `Null`) - appears to the right of all other types. - -- `isNullableUnion` determines whether `this` is a nullable union. -- `isJavaNullableUnion` determines whether `this` is syntactically a union of the form - `T|JavaNull`. - `stripNull` syntactically strips all `Null` types in the union: e.g. `String|Null => String`. -- `stripAllJavaNull` is like `stripNull` but only removes `JavaNull` from the union. +- `stripJavaNull` is like `stripNull` but only removes `JavaNull` from the union. This is needed when we want to "revert" the Java nullification function. +- `stripAllJavaNull` collapses all `JavaNull` unions within this type, and not just the outermost + ones (as `stripJavaNull` does). +- `isNullableUnion` determines whether `this` is a nullable union. +- `isJavaNullableUnion` determines whether `this` is syntactically a union of the form + `T|JavaNull`. ## Flow Typing diff --git a/docs/docs/reference/other-new-features/explicit-nulls.md b/docs/docs/reference/other-new-features/explicit-nulls.md index 48038d9b6d4d..907b6d71835f 100644 --- a/docs/docs/reference/other-new-features/explicit-nulls.md +++ b/docs/docs/reference/other-new-features/explicit-nulls.md @@ -403,7 +403,7 @@ When dealing with local mutable variables, there are two questions: } if (x != null) { // y can be called here, which break the fact - val a: String = x // error: x is captured and mutated by the closure, not tackable + val a: String = x // error: x is captured and mutated by the closure, not trackable } ``` diff --git a/tests/explicit-nulls/neg/simple-var.scala b/tests/explicit-nulls/neg/simple-var.scala index 678413cd7847..66ac053a4fbb 100644 --- a/tests/explicit-nulls/neg/simple-var.scala +++ b/tests/explicit-nulls/neg/simple-var.scala @@ -1,36 +1,45 @@ // Test simple var track -def nullable[T](x: T): T|Null = x +class SimpleVar { -def f = { - var x: String|Null = ??? - if (x != null) { - val a: String = x - x = "" - val b: String = x + def nullable[T](x: T): T|Null = x + + locally { + var x: String|Null = ??? + x = "" // x is assigned to a non-null value + val l: Int = x.length // ok, we know x is not null } - assert(x != null) - val a: String = x - x = nullable(x) - val b: String = x // error: x might be null -} + locally { + var x: String|Null = ??? + if (x != null) { + val a: String = x + x = "" + val b: String = x + } -def g = { - var x: String|Null = ??? - if (x != null) { + assert(x != null) val a: String = x - x = null - val b: String = x // error: x is null + x = nullable(x) + val b: String = x // error: x might be null } -} -def h = { - var x: String|Null = ??? - if (x != null) { - val a: String = x - val b: String | String = a - x = b - val _: String = x // ok + locally { + var x: String|Null = ??? + if (x != null) { + val a: String = x + x = null + val b: String = x // error: x is null + } + } + + locally { + var x: String|Null = ??? + if (x != null) { + val a: String = x + val b: String | String = a + x = b + val _: String = x // ok + } } } \ No newline at end of file diff --git a/tests/explicit-nulls/neg/var-ref-in-closure.scala b/tests/explicit-nulls/neg/var-ref-in-closure.scala index a336fa6c795c..a33c32b5922e 100644 --- a/tests/explicit-nulls/neg/var-ref-in-closure.scala +++ b/tests/explicit-nulls/neg/var-ref-in-closure.scala @@ -34,7 +34,7 @@ object VarRef { x } if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } } @@ -47,7 +47,7 @@ object VarRef { x } if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } } @@ -56,7 +56,7 @@ object VarRef { var x: String|Null = ??? lazy val y = { if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } x } @@ -66,7 +66,7 @@ object VarRef { var x: String|Null = ??? def y = { if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } x } @@ -77,7 +77,7 @@ object VarRef { lazy val y = { if (x != null) { // The enclosingMethods of x definition and x reference hare are same - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } x } @@ -96,7 +96,7 @@ object VarRef { } } if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } } @@ -105,7 +105,7 @@ object VarRef { val y: F = new F { def get() = { if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } x } @@ -125,7 +125,7 @@ object VarRef { x } if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } } @@ -134,7 +134,7 @@ object VarRef { // var x: String|Null = ??? // val y: F = f { // if (x != null) { - // val a: String = x // err: x exists in closure, no longer tackable + // val a: String = x // err: x exists in closure, no longer trackable // } // x // } @@ -144,7 +144,7 @@ object VarRef { var x: String|Null = ??? val y: String => String|Null = s => { if (x != null) { - val a: String = x // error: x exists in closure, no longer tackable + val a: String = x // error: x exists in closure, no longer trackable } x } From 0acba328a37597b3b1ef5a6ee91716772956a62a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 11 Dec 2019 16:53:26 -0500 Subject: [PATCH 35/39] Edit comments --- .../dotty/tools/dotc/typer/Nullables.scala | 109 ++++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +- 2 files changed, 66 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index c488720f21d7..e68ff44e8be0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -101,51 +101,31 @@ object Nullables with case _ => None end TrackedRef - /** Is given reference tracked for nullability? - * This is the case if the reference is a path to an immutable val, or if it refers - * to a local mutable variable where all assignments to the variable are _reachable_ - * (in the sense of how it is defined in assignmentSpans). We use this function to decide - * whether we need to compute the NotNullInfo and add it to the context. This requires the - * use of a mutable variable is not out of order. + /** Is the given reference tracked for nullability? * - * When dealing with local mutable variables, there are two questions: + * This is the case if one of the following holds: + * 1) The reference is a path to an immutable `val`. + * 2) The reference is to a mutable variable, in which case all assignments to it must be + * reachable (in the sense of how it is defined in assignmentSpans) _and_ the variable + * must not be used "out of order" (in the sense specified by `usedOutOfOrder`). * - * 1. Whether to track a local mutable variable during flow typing. - * We track a local mutable variable iff the variable is not assigned in a closure. - * For example, in the following code `x` is assigned to by the closure `y`, so we do not - * do flow typing on `x`. - * ```scala - * var x: String|Null = ??? - * def y = { - * x = null - * } - * if (x != null) { - * // y can be called here, which break the fact - * val a: String = x // error: x is captured and mutated by the closure, not trackable - * } - * ``` + * Whether to track a local mutable variable during flow typing? + * We track a local mutable variable iff the variable is not assigned in a closure. + * For example, in the following code `x` is assigned to by the closure `y`, so we do not + * do flow typing on `x`. * - * 2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable. - * We only want to do flow typing on a use that belongs to the same method as the definition - * of the local variable. - * For example, in the following code, even `x` is not assigned to by a closure, but we can only - * use flow typing in one of the occurrences (because the other occurrence happens within a nested - * closure). - * ```scala - * var x: String|Null = ??? - * def y = { - * if (x != null) { - * // not safe to use the fact (x != null) here - * // since y can be executed at the same time as the outer block - * val _: String = x - * } - * } - * if (x != null) { - * val a: String = x // ok to use the fact here - * x = null - * } - * ``` + * ```scala + * var x: String|Null = ??? + * def y = { + * x = null + * } + * if (x != null) { + * // y can be called here, which break the fact + * val a: String = x // error: x is captured and mutated by the closure, not trackable + * } + * ``` * + * Check `usedOutOfOrder` to see the explaination and example of "out of order". * See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`. */ def isTracked(ref: TermRef)(given Context) = @@ -204,22 +184,57 @@ object Nullables with given refOps: extension (ref: TermRef) with - /* Is the use of a mutable variable out of order */ + /** Is the use of a mutable variable out of order + * + * Whether to generate and use flow typing on a specific _use_ of a local mutable variable? + * We only want to do flow typing on a use that belongs to the same method as the definition + * of the local variable. + * For example, in the following code, even `x` is not assigned to by a closure, but we can only + * use flow typing in one of the occurrences (because the other occurrence happens within a nested + * closure). + * ```scala + * var x: String|Null = ??? + * def y = { + * if (x != null) { + * // not safe to use the fact (x != null) here + * // since y can be executed at the same time as the outer block + * val _: String = x + * } + * } + * if (x != null) { + * val a: String = x // ok to use the fact here + * x = null + * } + * ``` + * + * Another example: + * ```scala + * var x: String|Null = ??? + * if (x != null) { + * def f: String = { + * val y: String = x // error: the use of x is out of order + * y + * } + * x = null + * val y: String = f // danger + * } + * ``` + */ def usedOutOfOrder(given Context): Boolean = val refSym = ref.symbol val refOwner = refSym.owner - def enclosedInClosure(s: Symbol): Boolean = + @tailrec def usedWithinClosure(s: Symbol): Boolean = s != NoSymbol && s != refOwner && (s.isOneOf(Lazy | Method) // not at the rhs of lazy ValDef or in a method (or lambda) || s.isClass // not in a class // TODO: need to check by-name paramter - || enclosedInClosure(s.owner)) + || usedWithinClosure(s.owner)) - refSym.is(Mutable) - && refSym.owner.isTerm - && enclosedInClosure(curCtx.owner) + refSym.is(Mutable) // if it is immutable, we don't need to check the rest conditions + && refOwner.isTerm + && usedWithinClosure(curCtx.owner) given treeOps: extension (tree: Tree) with diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f46d7bef0713..31cef84a12c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -358,8 +358,10 @@ class Typer extends Namer def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match case ref @ OrNull(tpnn) : TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign - !ref.usedOutOfOrder && - ctx.notNullInfos.impliesNotNull(ref) => + ctx.notNullInfos.impliesNotNull(ref) && + // If a reference is in the context, it is already trackable at the point we add it. + // Hence, we don't use isTracked in the next line, because checking use out of order is enough. + !ref.usedOutOfOrder => tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) case _ => tree From dea268af1bdc276c9df3555e440fa6c79042e1aa Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 12 Dec 2019 13:43:18 -0500 Subject: [PATCH 36/39] Add suggested NotNull annots --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8c27bb6870fb..4f6e8587fdde 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -822,16 +822,22 @@ class Definitions { // 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" :: + "javax.validation.constraints.NotNull" :: "androidx.annotation.NonNull" :: "android.support.annotation.NonNull" :: "android.annotation.NonNull" :: "com.android.annotations.NonNull" :: "org.eclipse.jdt.annotation.NonNull" :: + "edu.umd.cs.findbugs.annotations.NonNull" :: "org.checkerframework.checker.nullness.qual.NonNull" :: "org.checkerframework.checker.nullness.compatqual.NonNullDecl" :: "org.jetbrains.annotations.NotNull" :: + "org.springframework.lang.NonNull" :: + "org.springframework.lang.NonNullApi" :: + "org.springframework.lang.NonNullFields" :: "lombok.NonNull" :: + "reactor.util.annotation.NonNull" :: + "reactor.util.annotation.NonNullApi" :: "io.reactivex.annotations.NonNull" :: Nil map PreNamedString) // convenient one-parameter method types From 2f42d1b039e0929b81ca8e955548acd32e26258c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 12 Dec 2019 17:25:44 -0500 Subject: [PATCH 37/39] Update comments --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 ++-- .../src/dotty/tools/dotc/core/NullOpsDecorator.scala | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4f6e8587fdde..5a535071f4b3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -269,7 +269,7 @@ class Definitions { @tu lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final) @tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact) @tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable) - // generated by pattern matcher and exlicit nulls, eliminated by erasure + // generated by pattern matcher and explicit nulls, eliminated by erasure /** def getClass[A >: this.type](): Class[? <: A] */ @tu lazy val Any_getClass: TermSymbol = @@ -1144,7 +1144,7 @@ class Definitions { def isTupleType(tp: Type)(implicit ctx: Context): Boolean = { val arity = tp.dealias.argInfos.length - arity <= MaxTupleArity && TupleType(arity) != null && (tp isRef TupleType(arity).symbol) + arity <= MaxTupleArity && TupleType(arity) != null && tp.isRef(TupleType(arity).symbol) } def tupleType(elems: List[Type]): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 30d5f9d81d8b..adbe942c7be2 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -2,7 +2,7 @@ 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} +import dotty.tools.dotc.core.Types._ /** Defines operations on nullable types. */ object NullOpsDecorator { @@ -18,8 +18,8 @@ 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` references to `Null` (or `JavaNull`), then return `T1 | ... | Tn-1`. + * If the type is `T1 | ... | Tn`, and `Ti` references to `Null` (or `JavaNull`), + * then return `T1 | ... | Ti-1 | Ti+1 | ... | Tn`. * If this type isn't (syntactically) nullable, then returns the type unchanged. * * @param onlyJavaNull whether we only remove `JavaNull`, the default value is false @@ -40,7 +40,7 @@ object NullOpsDecorator { else tp.derivedOrType(llhs, rrhs) case tp @ AndType(tp1, tp2) => // We cannot `tp.derivedAndType(strip(tp1), strip(tp2))` directly, - // since `normNullableUnion((A | Null) & B)` would produce the wrong + // since `stripNull((A | Null) & B)` would produce the wrong // result `(A & B) | Null`. val tp1s = strip(tp1) val tp2s = strip(tp2) @@ -59,8 +59,8 @@ object NullOpsDecorator { def stripJavaNull(implicit ctx: Context): Type = self.stripNull(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] + * e.g. (Array[String|JavaNull]|JavaNull).stripJavaNull => Array[String|JavaNull] + * (Array[String|JavaNull]|JavaNull).stripAllJavaNull => Array[String] * If no `JavaNull` unions are found within the type, then returns the input type unchanged. */ def stripAllJavaNull(implicit ctx: Context): Type = { From 9a3a62509abcce7ca1b02ad72b4468692361042c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 13 Dec 2019 17:27:07 -0500 Subject: [PATCH 38/39] Fix typos --- .../src/dotty/tools/dotc/core/NullOpsDecorator.scala | 12 ++++++------ compiler/src/dotty/tools/dotc/typer/Nullables.scala | 12 +++++++----- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index adbe942c7be2..401cce87241d 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -51,8 +51,8 @@ object NullOpsDecorator { } val self1 = self.widenDealias - val striped = strip(self1) - if (striped ne self1) striped else self + val stripped = strip(self1) + if (stripped ne self1) stripped else self } /** Like `stripNull`, but removes only the `JavaNull`s. */ @@ -73,14 +73,14 @@ object NullOpsDecorator { /** Is self (after widening and dealiasing) a type of the form `T | Null`? */ def isNullableUnion(implicit ctx: Context): Boolean = { - val striped = self.stripNull() - striped ne self + val stripped = self.stripNull() + stripped ne self } /** Is self (after widening and dealiasing) a type of the form `T | JavaNull`? */ def isJavaNullableUnion(implicit ctx: Context): Boolean = { - val striped = self.stripNull(true) - striped ne self + val stripped = self.stripNull(true) + stripped ne self } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index e68ff44e8be0..a94d23aff514 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -224,17 +224,17 @@ object Nullables with val refSym = ref.symbol val refOwner = refSym.owner - @tailrec def usedWithinClosure(s: Symbol): Boolean = + @tailrec def recur(s: Symbol): Boolean = s != NoSymbol && s != refOwner && (s.isOneOf(Lazy | Method) // not at the rhs of lazy ValDef or in a method (or lambda) || s.isClass // not in a class // TODO: need to check by-name paramter - || usedWithinClosure(s.owner)) + || recur(s.owner)) refSym.is(Mutable) // if it is immutable, we don't need to check the rest conditions && refOwner.isTerm - && usedWithinClosure(curCtx.owner) + && recur(curCtx.owner) given treeOps: extension (tree: Tree) with @@ -335,9 +335,11 @@ object Nullables with // lhs variable is no longer trackable. We don't need to check whether the type `T` // is correct here, as typer will check it. tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - else - // otherwise, we know the variable will have a non-null value + else if (curCtx.explicitNulls && ref.isNullableUnion) + // If the initial type is nullable and the assigned value is non-null, + // we add it to the NotNull tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) + else tree case _ => tree private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 79a22b45359e..b795ab296467 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2289,7 +2289,7 @@ class Typer extends Namer } /** Tries to adapt NotNullInfos from creation context to the DefTree, - * returns whether the adaption is successed. The adaption only success if the + * returns whether the adaption took place. An adaption only takes place if the * DefTree has a symbol and it has not been completed (is not forward referenced). */ def adaptCreationContext(mdef: untpd.DefTree)(implicit ctx: Context): Boolean = From c41b92664e5ae722f963f6cfcf9fd75e8f534bb2 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 13 Dec 2019 17:39:54 -0500 Subject: [PATCH 39/39] Rewrite the if stat --- .../dotty/tools/dotc/typer/Nullables.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index a94d23aff514..0142b23dcdc0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -330,15 +330,16 @@ object Nullables with def computeAssignNullable()(given Context): tree.type = tree.lhs match case TrackedRef(ref) => val rhstp = tree.rhs.typeOpt - if (rhstp.isNullType || (curCtx.explicitNulls && rhstp.isNullableUnion)) - // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the - // lhs variable is no longer trackable. We don't need to check whether the type `T` - // is correct here, as typer will check it. - tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) - else if (curCtx.explicitNulls && ref.isNullableUnion) - // If the initial type is nullable and the assigned value is non-null, - // we add it to the NotNull - tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) + if curCtx.explicitNulls && ref.isNullableUnion then + if rhstp.isNullType || rhstp.isNullableUnion then + // If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the + // lhs variable is no longer trackable. We don't need to check whether the type `T` + // is correct here, as typer will check it. + tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) + else + // If the initial type is nullable and the assigned value is non-null, + // we add it to the NotNull. + tree.withNotNullInfo(NotNullInfo(Set(ref), Set())) else tree case _ => tree