From 099bf338d86764d76b2fd93c89c9a28ec33f5773 Mon Sep 17 00:00:00 2001 From: HarrisL2 Date: Wed, 14 May 2025 14:24:52 -0400 Subject: [PATCH 1/3] Exclude some explicit nulls negs tests under tasty --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 16 ++++++++++++---- ...icit-nulls-scala2-library-tasty.excludelist | 18 ++++++++++++++++++ compiler/test/dotty/tools/TestSources.scala | 5 +++++ .../dotty/tools/dotc/CompilationTests.scala | 4 ++-- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 compiler/test/dotc/neg-explicit-nulls-scala2-library-tasty.excludelist diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fe0fd908a1fe..c9bdb3fb6be6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -914,6 +914,12 @@ class TreeUnpickler(reader: TastyReader, def ta = ctx.typeAssigner + // If explicit nulls is enabled, and the source file did not have explicit + // nulls enabled, nullify the member to allow for compatibility. + def nullify(sym: Symbol) = + if (ctx.explicitNulls && !explicitNulls) then + sym.info = ImplicitNullInterop.nullifyMember(sym, sym.info, sym.is(Enum)) + val name = readName() pickling.println(s"reading def of $name at $start") val tree: MemberDef = tag match { @@ -930,10 +936,12 @@ class TreeUnpickler(reader: TastyReader, else tpt.tpe sym.info = methodType(paramss, resType) + nullify(sym) DefDef(paramDefss, tpt) case VALDEF => val tpt = readTpt()(using localCtx) sym.info = tpt.tpe + nullify(sym) ValDef(tpt) case TYPEDEF | TYPEPARAM => if (sym.isClass) { @@ -971,6 +979,9 @@ class TreeUnpickler(reader: TastyReader, sym.typeRef.recomputeDenot() // make sure we see the new bounds from now on else sym.info = info + if (tag == TYPEPARAM) { + nullify(sym) + } sym.resetFlag(Provisional) TypeDef(rhs) @@ -979,13 +990,10 @@ class TreeUnpickler(reader: TastyReader, val tpt = readTpt()(using localCtx) assert(nothingButMods(end)) sym.info = tpt.tpe + nullify(sym) ValDef(tpt) } - // If explicit nulls is enabled, and the source file did not have explicit - // nulls enabled, nullify the member to allow for compatibility. - if (ctx.explicitNulls && !explicitNulls) then - sym.info = ImplicitNullInterop.nullifyMember(sym, sym.info, sym.is(Enum)) goto(end) setSpan(start, tree) diff --git a/compiler/test/dotc/neg-explicit-nulls-scala2-library-tasty.excludelist b/compiler/test/dotc/neg-explicit-nulls-scala2-library-tasty.excludelist new file mode 100644 index 000000000000..93636977eb57 --- /dev/null +++ b/compiler/test/dotc/neg-explicit-nulls-scala2-library-tasty.excludelist @@ -0,0 +1,18 @@ +byname-nullables.scala # identity() flexified +varargs.scala # Array type flexified +flow-conservative.scala # .length flexified +nn-basic.scala # .length flexified but trim rejected +i21380c.scala # .length flexified but replaceAll rejected +unsafe-scope.scala # .length flexified +i17467.scala # Singleton type flexified +i7883.scala # Unsure +from-nullable.scala # Option argument flexified +flow-in-block.scala # .length flexified +array.scala # Type arugment of Array flexified +flow-forward-ref.scala # .length flexified, forward reference error +flow-implicitly.scala # Singleton type flexified +nn.scala # Flexified elided error [!] +flow-basic.scala # .length flexified + +unsafe-cast.scala # Array type flexified +unsafe-extensions.scala # Function arguments flexified \ No newline at end of file diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index a2fccd3b35e6..e35b35ae09cf 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -48,6 +48,7 @@ object TestSources { def negScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/neg-scala2-library-tasty.excludelist" def negInitGlobalScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/neg-init-global-scala2-library-tasty.excludelist" + def negExplicitNullsScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/neg-explicit-nulls-scala2-library-tasty.excludelist" def negScala2LibraryTastyExcludelisted: List[String] = if Properties.usingScalaLibraryTasty then loadList(negScala2LibraryTastyExcludelistFile) @@ -55,6 +56,10 @@ object TestSources { def negInitGlobalScala2LibraryTastyExcludelisted: List[String] = if Properties.usingScalaLibraryTasty then loadList(negInitGlobalScala2LibraryTastyExcludelistFile) else Nil + def negExplicitNullsScala2LibraryTastyExcludelisted: List[String] = + if Properties.usingScalaLibraryTasty then loadList(negExplicitNullsScala2LibraryTastyExcludelistFile) + else Nil + // patmat tests lists diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 3158c7d24444..80845527da20 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -201,9 +201,9 @@ class CompilationTests { @Test def explicitNullsNeg: Unit = { implicit val testGroup: TestGroup = TestGroup("explicitNullsNeg") aggregateTests( - compileFilesInDir("tests/explicit-nulls/neg", explicitNullsOptions), + compileFilesInDir("tests/explicit-nulls/neg", explicitNullsOptions, FileFilter.exclude(TestSources.negExplicitNullsScala2LibraryTastyExcludelisted)), compileFilesInDir("tests/explicit-nulls/flexible-types-common", explicitNullsOptions and "-Yno-flexible-types"), - compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-Yno-flexible-types"), + compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-Yno-flexible-types", FileFilter.exclude(TestSources.negExplicitNullsScala2LibraryTastyExcludelisted)), ) }.checkExpectedErrors() From 52d7b22b98a1c7d5c3f30fd52548afe42be3be71 Mon Sep 17 00:00:00 2001 From: HarrisL2 Date: Mon, 16 Jun 2025 17:30:07 -0400 Subject: [PATCH 2/3] Do not flexify higher-kinded types --- community-build/community-projects/scalatest | 2 +- .../tools/dotc/core/ImplicitNullInterop.scala | 23 ++++++++++++------- .../src/dotty/tools/dotc/core/Types.scala | 17 ++++++++++++-- .../flexible-unpickle/Flexible_2.scala | 22 ++++++++++++++++++ .../flexible-unpickle/Unsafe_1.scala | 7 ++++++ .../pos/interop-sam-src/S.scala | 4 ++++ 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index d6eeedbfc1e0..ab674686d089 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit d6eeedbfc1e04f2eff55506f07f93f448cc21407 +Subproject commit ab674686d089f13da2e29c3b78fe6c3ab0211189 diff --git a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala index 783d373f8902..f31aa62b89fa 100644 --- a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala @@ -104,19 +104,26 @@ object ImplicitNullInterop { def needsNull(tp: Type): Boolean = if outermostLevelAlreadyNullable then false else tp match + case tp: TypeRef if !tp.hasSimpleKind => false case tp: TypeRef if // We don't modify value types because they're non-nullable even in Java. tp.symbol.isValueClass // We don't modify unit types. || tp.isRef(defn.UnitClass) // 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|Null)*): Unit`. - // This is because `setNames(null)` passes as argument a single-element array containing the value `null`, - // and not a `null` array. - || !ctx.flexibleTypes && tp.isRef(defn.RepeatedParamClass) => false + || tp.isRef(defn.AnyClass) => false + case _ => true + + // 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|Null)*): Unit`. + // This is because `setNames(null)` passes as argument a single-element array containing the value `null`, + // and not a `null` array. + def tyconNeedsNull(tp: Type): Boolean = + if outermostLevelAlreadyNullable then false + else tp match + case tp: TypeRef + if !ctx.flexibleTypes && tp.isRef(defn.RepeatedParamClass) => false case _ => true override def apply(tp: Type): Type = tp match { @@ -130,7 +137,7 @@ object ImplicitNullInterop { val targs2 = targs map this outermostLevelAlreadyNullable = oldOutermostNullable val appTp2 = derivedAppliedType(appTp, tycon, targs2) - if needsNull(tycon) then nullify(appTp2) else appTp2 + if tyconNeedsNull(tycon) then nullify(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8c28afed1097..fffd5bd666b3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1830,6 +1830,8 @@ object Types extends TypeUtils { t case t @ SAMType(_, _) => t + case ft: FlexibleType => + ft.underlying.findFunctionType case _ => NoType @@ -3401,7 +3403,7 @@ object Types extends TypeUtils { override def underlying(using Context): Type = hi def derivedFlexibleType(hi: Type)(using Context): Type = - if hi eq this.hi then this else FlexibleType(hi) + if hi eq this.hi then this else FlexibleType.make(hi) override def computeHash(bs: Binders): Int = doHash(bs, hi) @@ -3409,7 +3411,9 @@ object Types extends TypeUtils { } object FlexibleType { - def apply(tp: Type)(using Context): FlexibleType = tp match { + def apply(tp: Type)(using Context): FlexibleType = + // assert(tp.isValueType, s"Should not flexify ${tp}") + tp match { case ft: FlexibleType => ft case _ => // val tp1 = tp.stripNull() @@ -3430,6 +3434,15 @@ object Types extends TypeUtils { assert(!tp.isInstanceOf[LazyType]) FlexibleType(OrNull(tp), tp) } + + def make(tp: Type)(using Context): Type = + tp match + case _: FlexibleType => tp + case TypeBounds(lo, hi) => TypeBounds(FlexibleType.make(lo), FlexibleType.make(hi)) + case wt: WildcardType => wt.optBounds match + case tb: TypeBounds => WildcardType(FlexibleType.make(tb).asInstanceOf[TypeBounds]) + case _ => wt + case other => FlexibleType(tp) } // --- AndType/OrType --------------------------------------------------------------- diff --git a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala b/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala index 413e1a5f237c..ea7d75528e99 100644 --- a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala +++ b/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala @@ -1,6 +1,28 @@ import unsafeNulls.Foo.* import unsafeNulls.Unsafe_1 +class Inherit_1 extends Unsafe_1 { + override def foo(s: String): String = s + override def bar[T >: String](s: T): T = s + override def bar2[T >: String | Null](s: T): T = s +} + +class Inherit_2 extends Unsafe_1 { + override def foo(s: String | Null): String | Null = null + override def bar[T >: String](s: T | Null): T | Null = s + override def bar2[T >: String](s: T): T = s +} + +class Inherit_3 extends Unsafe_1 { + override def foo(s: String): String | Null = null + override def bar[T >: String](s: T): T | Null = s +} + +class Inherit_4 extends Unsafe_1 { + override def foo(s: String | Null): String = "non-null string" + override def bar[T >: String](s: T | Null): T = "non-null string" +} + @main def Flexible_2() = val s2: String | Null = "foo" diff --git a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala b/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala index 77c3087fef70..51184d2282f1 100644 --- a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala +++ b/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala @@ -5,6 +5,13 @@ class Unsafe_1 { if (s == null) then "nullString" else s } + def bar[T >: String](s: T): T = { + ??? + } + def bar2[T >: String | Null](s: T): T = { + ??? + } + def bar3[T <: Int => Int](g: T): T = g } object Foo { diff --git a/tests/explicit-nulls/pos/interop-sam-src/S.scala b/tests/explicit-nulls/pos/interop-sam-src/S.scala index c0da89163018..9cc3869c187c 100644 --- a/tests/explicit-nulls/pos/interop-sam-src/S.scala +++ b/tests/explicit-nulls/pos/interop-sam-src/S.scala @@ -7,10 +7,14 @@ def m = { j.g1(f1) j.g1((_: String | Null) => null) + j.g1(_ => null) + j.g1(x => if (x == "") null else null) j.g1(null) j.g2(f2) j.g2((_: Int) => ()) + j.g2(_ => ()) + j.g2(x => if (x == 1) () else ()) j.g2(null) j.h1(f1) From d9f49d1a13acac3a02de23765d09dfe0138bf6ae Mon Sep 17 00:00:00 2001 From: HarrisL2 Date: Tue, 17 Jun 2025 14:18:03 -0400 Subject: [PATCH 3/3] Only nullify tasty files if flexible types are enabled --- ...nterop.scala => ImplicitNullInterop.scala} | 26 +++++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotc/core/classfile/ClassfileParser.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +-- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/test/dotty/tools/TestSources.scala | 1 - .../dotty/tools/dotc/CompilationTests.scala | 14 ++++++++-- .../flexible-unpickle/Flexible_2.scala | 6 +++++ .../flexible-unpickle/Unsafe_1.scala | 4 ++- 9 files changed, 38 insertions(+), 23 deletions(-) rename compiler/src/dotty/tools/dotc/core/{JavaNullInterop.scala => ImplicitNullInterop.scala} (90%) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala similarity index 90% rename from compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala rename to compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala index 44755efac074..9afbd49358ec 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala @@ -35,7 +35,7 @@ import dotty.tools.dotc.core.Decorators.i * to handle the full spectrum of Scala types. Additionally, some kinds of symbols like constructors and * enum instances get special treatment. */ -object JavaNullInterop { +object ImplicitNullInterop { /** 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. @@ -55,11 +55,11 @@ object JavaNullInterop { */ def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type = trace(i"nullifyMember ${sym}, ${tp}"){ assert(ctx.explicitNulls) - assert(sym.is(JavaDefined), "can only nullify java-defined members") // Some special cases when nullifying the type - if isEnumValueDef || sym.name == nme.TYPE_ then - // Don't nullify the `TYPE` field in every class and Java enum instances + if isEnumValueDef || sym.name == nme.TYPE_ // Don't nullify the `TYPE` field in every class and Java enum instances + || sym.is(Flags.ModuleVal) // Don't nullify Modules + then tp else if sym.name == nme.toString_ || sym.isConstructor || hasNotNullAnnot(sym) then // Don't nullify the return type of the `toString` method. @@ -80,14 +80,14 @@ object JavaNullInterop { * but the result type is not nullable. */ private def nullifyExceptReturnType(tp: Type)(using Context): Type = - new JavaNullMap(outermostLevelAlreadyNullable = true)(tp) + new ImplicitNullMap(outermostLevelAlreadyNullable = true)(tp) - /** Nullifies a Java type by adding `| Null` in the relevant places. */ + /** Nullifies a type by adding `| Null` in the relevant places. */ private def nullifyType(tp: Type)(using Context): Type = - new JavaNullMap(outermostLevelAlreadyNullable = false)(tp) + new ImplicitNullMap(outermostLevelAlreadyNullable = false)(tp) - /** A type map that implements the nullification function on types. Given a Java-sourced type, this adds `| Null` - * in the right places to make the nulls explicit in Scala. + /** A type map that implements the nullification function on types. Given a Java-sourced type or an + * implicitly null type, this adds `| Null` in the right places to make the nulls explicit. * * @param outermostLevelAlreadyNullable whether this type is already nullable at the outermost level. * For example, `Array[String] | Null` is already nullable at the @@ -97,17 +97,16 @@ object JavaNullInterop { * This is useful for e.g. constructors, and also so that `A & B` is nullified * to `(A & B) | Null`, instead of `(A | Null & B | Null) | Null`. */ - private class JavaNullMap(var outermostLevelAlreadyNullable: Boolean)(using Context) extends TypeMap { + private class ImplicitNullMap(var outermostLevelAlreadyNullable: Boolean)(using Context) extends TypeMap { def nullify(tp: Type): Type = if ctx.flexibleTypes then FlexibleType(tp) else OrNull(tp) /** Should we nullify `tp` at the outermost level? */ def needsNull(tp: Type): Boolean = if outermostLevelAlreadyNullable then false else tp match - case tp: TypeRef if !tp.hasSimpleKind => false - case tp: TypeRef if + case tp: TypeRef if !tp.hasSimpleKind // We don't modify value types because they're non-nullable even in Java. - tp.symbol.isValueClass + || tp.symbol.isValueClass // We don't modify unit types. || tp.isRef(defn.UnitClass) // We don't modify `Any` because it's already nullable. @@ -147,6 +146,7 @@ object JavaNullInterop { outermostLevelAlreadyNullable = oldOutermostNullable derivedLambdaType(mtp)(paramInfos2, this(mtp.resType)) case tp: TypeAlias => mapOver(tp) + case tp: TypeBounds => mapOver(tp) case tp: AndType => // nullify(A & B) = (nullify(A) & nullify(B)) | Null, but take care not to add // duplicate `Null`s at the outermost level inside `A` and `B`. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 46a67cc6d02c..403dc56e99b4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3407,7 +3407,7 @@ object Types extends TypeUtils { object FlexibleType { def apply(tp: Type)(using Context): FlexibleType = - // assert(tp.isValueType, s"Should not flexify ${tp}") + assert(tp.isValueType, s"Should not flexify ${tp}") tp match { case ft: FlexibleType => ft case _ => diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index a1a4d56abb15..e2cb3fa79676 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -519,7 +519,7 @@ class ClassfileParser( denot.info = translateTempPoly(attrCompleter.complete(denot.info, isVarargs)) if (isConstructor) normalizeConstructorInfo() - if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info, isEnum) + if (ctx.explicitNulls) denot.info = ImplicitNullInterop.nullifyMember(denot.symbol, denot.info, isEnum) // seal java enums if (isEnum) { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9a14d3931c7f..9c22ec6d0309 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -921,7 +921,7 @@ class TreeUnpickler(reader: TastyReader, // If explicit nulls is enabled, and the source file did not have explicit // nulls enabled, nullify the member to allow for compatibility. def nullify(sym: Symbol) = - if (ctx.explicitNulls && !explicitNulls) then + if (ctx.explicitNulls && ctx.flexibleTypes && !explicitNulls) then sym.info = ImplicitNullInterop.nullifyMember(sym, sym.info, sym.is(Enum)) val name = readName() @@ -997,8 +997,6 @@ class TreeUnpickler(reader: TastyReader, nullify(sym) ValDef(tpt) } - - goto(end) setSpan(start, tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 46657d8ee8bb..4f29a9374739 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1907,7 +1907,7 @@ class Namer { typer: Typer => val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe) if (ctx.explicitNulls && mdef.mods.is(JavaDefined)) - JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue)) + ImplicitNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue)) else mbrTpe } diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index e35b35ae09cf..a1ebdcd4c116 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -60,7 +60,6 @@ object TestSources { if Properties.usingScalaLibraryTasty then loadList(negExplicitNullsScala2LibraryTastyExcludelistFile) else Nil - // patmat tests lists def patmatExhaustivityScala2LibraryTastyExcludelistFile: String = "compiler/test/dotc/patmat-exhaustivity-scala2-library-tasty.excludelist" diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 0e66a564f099..edbd45e15540 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -217,8 +217,18 @@ class CompilationTests { compileFilesInDir("tests/explicit-nulls/pos", explicitNullsOptions), compileFilesInDir("tests/explicit-nulls/flexible-types-common", explicitNullsOptions), compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-language:unsafeNulls" and "-Yno-flexible-types"), - ) - }.checkCompile() + ).checkCompile() + + locally { + val tests = List( + compileFile("tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls"), + compileFile("tests/explicit-nulls/flexible-unpickle/Flexible_2.scala", explicitNullsOptions.withClasspath( + defaultOutputDir + testGroup + "/Unsafe_1/flexible-unpickle/Unsafe_1")), + ).map(_.keepOutput.checkCompile()) + + tests.foreach(_.delete()) + } + } @Test def explicitNullsWarn: Unit = { implicit val testGroup: TestGroup = TestGroup("explicitNullsWarn") diff --git a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala b/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala index ea7d75528e99..9948832d2519 100644 --- a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala +++ b/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala @@ -5,12 +5,16 @@ class Inherit_1 extends Unsafe_1 { override def foo(s: String): String = s override def bar[T >: String](s: T): T = s override def bar2[T >: String | Null](s: T): T = s + override def bar3[T <: Function1[String,String]](g: T) = g + override def bar4[HK[_]](i: String | Null): HK[String | Null] = ??? } class Inherit_2 extends Unsafe_1 { override def foo(s: String | Null): String | Null = null override def bar[T >: String](s: T | Null): T | Null = s override def bar2[T >: String](s: T): T = s + override def bar3[T <: Function1[(String|Null),(String|Null)]](g: T) = g + override def bar4[HK[_]](i: String): HK[String] = ??? } class Inherit_3 extends Unsafe_1 { @@ -23,6 +27,8 @@ class Inherit_4 extends Unsafe_1 { override def bar[T >: String](s: T | Null): T = "non-null string" } +case class cc() + @main def Flexible_2() = val s2: String | Null = "foo" diff --git a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala b/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala index 51184d2282f1..c51ac491ecbd 100644 --- a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala +++ b/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala @@ -11,9 +11,11 @@ class Unsafe_1 { def bar2[T >: String | Null](s: T): T = { ??? } - def bar3[T <: Int => Int](g: T): T = g + def bar3[T <: Function1[String,String]](g: T): T = g + def bar4[HK[_]](i: String): HK[String] = ??? } object Foo { def bar = "bar!" + def id[T](t: T): T = t } \ No newline at end of file