diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 2d048befe171..479e61530aa2 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -455,6 +455,7 @@ private sealed trait YSettings: val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-kind-polymorphism", "Disable kind polymorphism. (This flag has no effect)", deprecation = Deprecation.removed()) val YexplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YnoFlexibleTypes: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-flexible-types", "Disable turning nullable Java return types and parameter types into flexible types, which behave like abstract types with a nullable lower bound and non-nullable upper bound.") + val YflexifyTasty: Setting[Boolean] = BooleanSetting(ForkSetting, "Yflexify-tasty", "Apply flexification to Scala code compiled without -Yexplicit-nulls, when reading from tasty.") val YsafeInitGlobal: Setting[Boolean] = BooleanSetting(ForkSetting, "Ysafe-init-global", "Check safe initialization of global objects.") val YrequireTargetName: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.") val YrecheckTest: Setting[Boolean] = BooleanSetting(ForkSetting, "Yrecheck-test", "Run basic rechecking (internal test only).") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 5a0e03330ef2..51b42a3eb5f5 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -480,6 +480,9 @@ object Contexts { /** Is the flexible types option set? */ def flexibleTypes: Boolean = base.settings.YexplicitNulls.value && !base.settings.YnoFlexibleTypes.value + /** Is the flexify tasty option set? */ + def flexifyTasty: Boolean = base.settings.YexplicitNulls.value && base.settings.YflexifyTasty.value + /** Is the best-effort option set? */ def isBestEffort: Boolean = base.settings.YbestEffort.value diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index bb64f4c7ffa0..8906fa2fd263 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1991,13 +1991,11 @@ class Definitions { * - the upper bound of a TypeParamRef in the current constraint */ def asContextFunctionType(tp: Type)(using Context): Type = - tp.stripTypeVar.dealias match + tp.stripNull().stripTypeVar.dealias match case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) => asContextFunctionType(TypeComparer.bounds(tp1).hiBound) case tp1 @ PolyFunctionOf(mt: MethodType) if mt.isContextualMethod => tp1 - case tp1: FlexibleType => - asContextFunctionType(tp1.underlying) case tp1 => if tp1.typeSymbol.name.isContextFunction && isFunctionNType(tp1) then tp1 else NoType diff --git a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala index a72943f2128f..0426665eaca1 100644 --- a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala @@ -2,19 +2,25 @@ package dotty.tools.dotc package core import Contexts.* -import Flags.JavaDefined +import Flags.* import StdNames.nme import Symbols.* import Types.* -import dotty.tools.dotc.reporting.* -import dotty.tools.dotc.core.Decorators.i +import Decorators.i +import reporting.* -/** This module defines methods to interpret types of Java symbols, which are implicitly nullable in Java, - * as Scala types, which are explicitly nullable. +/** This module defines methods to interpret types originating from sources without explicit nulls + * (Java, and Scala code compiled without `-Yexplicit-nulls`) as Scala types with explicit nulls. + * In those sources, reference types are implicitly nullable; here we make that nullability explicit. + * + * 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 | Null): String | Null` * * The transformation is (conceptually) a function `n` that adheres to the following rules: * (1) n(T) = T | Null if T is a reference type - * (2) n(T) = T if T is a value type + * (2) n(T) = T if T is a value type * (3) n(C[T]) = C[T] | Null if C is Java-defined * (4) n(C[T]) = C[n(T)] | Null if C is Scala-defined * (5) n(A|B) = n(A) | n(B) | Null @@ -29,142 +35,187 @@ import dotty.tools.dotc.core.Decorators.i * 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)] | Null`. This is because - * `C` won't be nullified, so we need to indicate that its type argument is nullable. + * Scala-defined classes are not implicitly nullified inside their bodies, so we need to indicate that + * their type arguments are nullable when the defining source did not use explicit nulls. + * + * Why not use subtyping to nullify “exactly”? + * ------------------------------------------------- + * The symbols we nullify here are often still under construction (e.g. during classfile loading or unpickling), + * so we don't always have precise or stable type information available. Using full subtyping checks to determine + * which parts are reference types would either force types prematurely or risk cyclic initializations. Therefore, + * we use a conservative approach that targets concrete reference types without depending on precise subtype + * information. + * + * Scope and limitations + * ------------------------------------------------- + * The transformation is applied to types attached to members coming from Java and from Scala code compiled without + * explicit nulls. The implementation is intentionally conservative and does not attempt to cover the full spectrum + * of Scala types. In particular, we do not nullify type parameters or some complex type forms (e.g., match types, + * or refined types) beyond straightforward mapping; in such cases we typically recurse only into obviously safe + * positions or leave the type unchanged. * - * 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. + * Additionally, some kinds of symbols like constructors and enum instances get special treatment. */ -object ImplicitNullInterop { +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. - * - * 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 | Null): String | Null - * - * If unsafeNulls is enabled, 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`. + /** Transforms the type `tp` of a member `sym` that originates from a source without explicit nulls. + * `tp` is passed explicitly because the type stored in `sym` might not yet be set when this is called. */ - def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type = trace(i"nullifyMember ${sym}, ${tp}"){ + def nullifyMember(sym: Symbol, tp: Type, isEnumValueDef: Boolean)(using Context): Type = trace(i"nullifyMember ${sym}, ${tp}"): assert(ctx.explicitNulls) - // Some special cases when nullifying the type - 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. - // 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) - } + // Skip `TYPE`, enum values, and modules + if isEnumValueDef + || sym.name == nme.TYPE_ + || sym.name == nme.getClass_ + || sym.name == nme.toString_ + || sym.is(Flags.ModuleVal) then + return tp - private def hasNotNullAnnot(sym: Symbol)(using Context): Boolean = - ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) + // Don't nullify result type for `toString`, constructors, and @NotNull methods + val skipResultType = sym.isConstructor || hasNotNullAnnot(sym) + // Don't nullify Given/implicit parameters + val skipCurrentLevel = sym.isOneOf(GivenOrImplicitVal) - /** 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)(using Context): Type = - new ImplicitNullMap(outermostLevelAlreadyNullable = true)(tp) + val map = new ImplicitNullMap( + javaDefined = sym.is(JavaDefined), + skipResultType = skipResultType, + skipCurrentLevel = skipCurrentLevel) + map(tp) - /** Nullifies a type by adding `| Null` in the relevant places. */ - private def nullifyType(tp: Type)(using Context): Type = - new ImplicitNullMap(outermostLevelAlreadyNullable = false)(tp) + private def hasNotNullAnnot(sym: Symbol)(using Context): Boolean = + ctx.definitions.NotNullAnnots.exists(nna => sym.unforcedAnnotation(nna).isDefined) - /** 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. + /** A type map that implements the nullification function on types. Given a Java-sourced type or a type + * coming from Scala code compiled without explicit nulls, this adds `| Null` or `FlexibleType` in the + * right places to make nullability explicit in a conservative way (without forcing incomplete symbols). * - * @param outermostLevelAlreadyNullable whether this type is already nullable at the outermost level. - * For example, `Array[String] | Null` is already nullable at the - * outermost level, but `Array[String | Null]` 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) | Null`, instead of `(A | Null & B | Null) | Null`. + * @param javaDefined whether the type is from Java source, we always nullify type param refs from Java + * @param skipResultType do not nullify the method result type at the outermost level (e.g. for `toString`, + * constructors, or methods annotated as not-null) + * @param skipCurrentLevel do not nullify at the current level (used for implicit/Given parameters, varargs, etc.) */ - private class ImplicitNullMap(var outermostLevelAlreadyNullable: Boolean)(using Context) extends TypeMap { + private class ImplicitNullMap( + val javaDefined: Boolean, + var skipResultType: Boolean = false, + var skipCurrentLevel: Boolean = false + )(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? */ + /** Should we nullify `tp` at the outermost level? + * The symbols are still under construction, so we don't have precise information. + * We purposely do not rely on precise subtyping checks here (e.g., asking whether `tp <:< AnyRef`), + * because doing so could force incomplete symbols or trigger cycles. Instead, we conservatively + * nullify only when we can recognize a concrete reference type or type parameters from Java. + */ def needsNull(tp: Type): Boolean = - if outermostLevelAlreadyNullable then false - else tp match - case tp: TypeRef if !tp.hasSimpleKind + if skipCurrentLevel || !tp.hasSimpleKind then false + else tp.dealias 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 unit types. - || tp.isRef(defn.UnitClass) - // We don't modify `Any` because it's already nullable. - || tp.isRef(defn.AnyClass) => false - case _ => true + val isValueOrSpecialClass = + tp.symbol.isValueClass + || tp.isRef(defn.NullClass) + || tp.isRef(defn.NothingClass) + || tp.isRef(defn.UnitClass) + || tp.isRef(defn.SingletonClass) + || tp.isRef(defn.AnyKindClass) + || tp.isRef(defn.AnyClass) + !isValueOrSpecialClass && (javaDefined || tp.symbol.isNullableClassAfterErasure) + case tp: TypeParamRef => + javaDefined + case _ => false - // We don't nullify Java varargs at the top level. + // We don't nullify varargs (repeated parameters) 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 + if skipCurrentLevel then false else tp match case tp: TypeRef if !ctx.flexibleTypes && tp.isRef(defn.RepeatedParamClass) => false + case tp: TypeRef if defn.isTupleClass(tp.symbol) => false case _ => true - override def apply(tp: Type): Type = tp match { - case tp: TypeRef if needsNull(tp) => nullify(tp) + override def apply(tp: Type): Type = tp match + case tp: TypeRef if needsNull(tp) => + nullify(tp) + case tp: TypeParamRef if needsNull(tp) => + nullify(tp) case appTp @ AppliedType(tycon, targs) => - val oldOutermostNullable = outermostLevelAlreadyNullable - // We don't make the outmost levels of type arguments 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 savedSkipCurrentLevel = skipCurrentLevel + + // If Java-defined tycon, don't nullify outer level of type args (Java classes are fully nullified) + skipCurrentLevel = tp.classSymbol.is(JavaDefined) + val targs2 = targs.map(this) + + skipCurrentLevel = savedSkipCurrentLevel val appTp2 = derivedAppliedType(appTp, tycon, targs2) - if tyconNeedsNull(tycon) then nullify(appTp2) else appTp2 + if tyconNeedsNull(tycon) && tp.hasSimpleKind then nullify(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: 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`. - outermostLevelAlreadyNullable = true - nullify(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) - case tp: TypeParamRef if needsNull(tp) => nullify(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: ExprType => mapOver(tp) - case tp: AnnotatedType => mapOver(tp) - case tp: OrType => - outermostLevelAlreadyNullable = true - nullify(derivedOrType(tp, this(tp.tp1), this(tp.tp2))) + val savedSkipCurrentLevel = skipCurrentLevel + + // Don't nullify param types for implicit/using sections + skipCurrentLevel = mtp.isImplicitMethod + val paramInfos2 = mtp.paramInfos.map(this) + + skipCurrentLevel = skipResultType + val resType2 = this(mtp.resType) + + skipCurrentLevel = savedSkipCurrentLevel + derivedLambdaType(mtp)(paramInfos2, resType2) + case tp: TypeAlias => + mapOver(tp) + case tp: TypeBounds => + mapOver(tp) + case tp: AndOrType => + // For unions/intersections we recurse into both sides. + // If both sides are nullalble, we only add `| Null` once. + // This keeps the result minimal and avoids duplicating `| Null` + // on both sides and at the outer level. + (this(tp.tp1), this(tp.tp2)) match + case (FlexibleType(_, t1), FlexibleType(_, t2)) if ctx.flexibleTypes => + FlexibleType(derivedAndOrType(tp, t1, t2)) + case (OrNull(t1), OrNull(t2)) => + OrNull(derivedAndOrType(tp, t1, t2)) + case (t1, t2) => + derivedAndOrType(tp, t1, t2) + case tp: ExprType => + mapOver(tp) + case tp: AnnotatedType => + // We don't nullify the annotation part. + derivedAnnotatedType(tp, this(tp.underlying), tp.annot) case tp: RefinedType => - outermostLevelAlreadyNullable = true - nullify(mapOver(tp)) - case _ => tp - } - } -} \ No newline at end of file + val savedSkipCurrentLevel = skipCurrentLevel + val savedSkipResultType = skipResultType + + val parent2 = this(tp.parent) + + skipCurrentLevel = false + skipResultType = false + val refinedInfo2 = this(tp.refinedInfo) + + skipCurrentLevel = savedSkipCurrentLevel + skipResultType = savedSkipResultType + + parent2 match + case FlexibleType(_, parent2a) if ctx.flexibleTypes => + FlexibleType(derivedRefinedType(tp, parent2a, refinedInfo2)) + case OrNull(parent2a) => + OrNull(derivedRefinedType(tp, parent2a, refinedInfo2)) + case _ => + derivedRefinedType(tp, parent2, refinedInfo2) + case _ => + // 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. We also deliberately do not attempt to nullify + // complex computed types such as match types here; those remain as-is to avoid forcing + // incomplete information during symbol construction. + tp + end apply + end ImplicitNullMap \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index efc2e200ccc3..0e3ada25eb83 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6253,6 +6253,8 @@ object Types extends TypeUtils { tp.derivedAndType(tp1, tp2) protected def derivedOrType(tp: OrType, tp1: Type, tp2: Type): Type = tp.derivedOrType(tp1, tp2) + protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type): Type = + tp.derivedAndOrType(tp1, tp2) protected def derivedMatchType(tp: MatchType, bound: Type, scrutinee: Type, cases: List[Type]): Type = tp.derivedMatchType(bound, scrutinee, cases) protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation): Type = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 95c9731a0679..3265d98fd859 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -921,10 +921,10 @@ class TreeUnpickler(reader: TastyReader, def ta = ctx.typeAssigner - // If explicit nulls is enabled, and the source file did not have explicit + // If explicit nulls and `Yflexify-tasty` 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 && ctx.flexibleTypes && !explicitNulls) then + if (ctx.flexifyTasty && !explicitNulls) then sym.info = ImplicitNullInterop.nullifyMember(sym, sym.info, sym.is(Enum)) val name = readName() diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index fe8ea2c502d9..0a826e7089bb 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -212,8 +212,18 @@ class CompilationTests { 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", FileFilter.exclude(TestSources.negExplicitNullsScala2LibraryTastyExcludelisted)), - ) - }.checkExpectedErrors() + ).checkExpectedErrors() + + locally { + val unsafeFile = compileFile("tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls") + val flexibleFile = compileFile("tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala", + explicitNullsOptions.and("-Yflexify-tasty").withClasspath(defaultOutputDir + testGroup + "/Unsafe_1/neg/Unsafe_1")) + + flexibleFile.keepOutput.checkExpectedErrors() + + List(unsafeFile, flexibleFile).foreach(_.delete()) + } + } @Ignore @Test def explicitNullsPos: Unit = { @@ -226,9 +236,9 @@ class CompilationTests { 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")), + compileFile("tests/explicit-nulls/flexible-unpickle/pos/Unsafe_1.scala", explicitNullsOptions without "-Yexplicit-nulls"), + compileFile("tests/explicit-nulls/flexible-unpickle/pos/Flexible_2.scala", + explicitNullsOptions.and("-Yflexify-tasty").withClasspath(defaultOutputDir + testGroup + "/Unsafe_1/pos/Unsafe_1")), ).map(_.keepOutput.checkCompile()) tests.foreach(_.delete()) diff --git a/tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala b/tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala new file mode 100644 index 000000000000..6d210cb281a8 --- /dev/null +++ b/tests/explicit-nulls/flexible-unpickle/neg/Flexible_2.scala @@ -0,0 +1,24 @@ +import unsafe.* + +@A[String] class C + +def test = + + val ii = Bar.f[Int](null) // error + val jj = Bar.g[String](null) // error + val jj2 = Bar.g[String | Null](null) // ok + val kk = Bar.g2[String](null) // error + val kk2 = Bar.g2[String | Null](null) // ok + + val bar_x: Int = Bar.x + val bar_y: String | Null = Bar.y.replaceAll(" ","") + + def testUsingFoo(using Foo[Option]) = Bar.h(null) + + val ii2 = Bar2[String]().f(null) // error + val ii3 = Bar2[String | Null]().f(null) // ok + + val a = Bar.ff( + (x: AnyRef) => x.toString, + 42 + ) \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala b/tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala new file mode 100644 index 000000000000..9a682c96663e --- /dev/null +++ b/tests/explicit-nulls/flexible-unpickle/neg/Unsafe_1.scala @@ -0,0 +1,22 @@ +package unsafe + +import scala.annotation.* + +type XtoY = [X] =>> [Y] =>> X => Y + +class Foo[T[_]] + +class A[T] extends Annotation + +object Bar: + def ff(f: AnyRef => String, g: AnyRef ?=> Int): (AnyRef => String) = ??? + var x: Int = 0 + var y: String = "" + def f[T <: Int](x: T): T = x + def g[T <: AnyRef](x: T): T = x + def g2[T >: Null <: AnyRef](x: T): T = x + def h(x: String)(using Foo[Option]): String = x + def h2(a: Foo[XtoY[String]]) = ??? + +class Bar2[T]: + def f(x: T): T = x \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala b/tests/explicit-nulls/flexible-unpickle/pos/Flexible_2.scala similarity index 87% rename from tests/explicit-nulls/flexible-unpickle/Flexible_2.scala rename to tests/explicit-nulls/flexible-unpickle/pos/Flexible_2.scala index 20448d31c3aa..55c5dc90ac78 100644 --- a/tests/explicit-nulls/flexible-unpickle/Flexible_2.scala +++ b/tests/explicit-nulls/flexible-unpickle/pos/Flexible_2.scala @@ -1,6 +1,6 @@ import unsafeNulls.Foo.* import unsafeNulls.Unsafe_1 -import unsafeNulls.{A, B, C, F, G, H, I, J, L, M, S, T, U, expects} +import unsafeNulls.{A, B, C, F, G, H, I, J, L, M, N, S, T, U, expects} import scala.reflect.Selectable.reflectiveSelectable import scala.quoted.* @@ -100,6 +100,11 @@ def Flexible_2() = val m: String = M.test(null) + // i23911 + val n1: List[Map[String, Int]] = ??? + val n2 = new N[List]() + val n3 = n2.accept[Any](n1) + // i23845 transparent inline def typeName[A]: String = ${typeNameMacro[A]} @@ -109,3 +114,9 @@ def Flexible_2() = implicit val givenS: S[A] = ??? expects(alphaTypeNameMacro[A]) } + +// i23935 +opaque type ZArrow[-I, -R, +E, +O] = I => ZIO[R, E, O] +object ZArrow: + def fromZIOAttempt[I, R, E, O](f: I => ZIO[R, E, O]): ZArrow[I, R, Throwable | E, O] = + (in: I) => ZIO.attempt(f(in)).flatten \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala b/tests/explicit-nulls/flexible-unpickle/pos/Unsafe_1.scala similarity index 76% rename from tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala rename to tests/explicit-nulls/flexible-unpickle/pos/Unsafe_1.scala index 6bec3dabf302..c35e05e4e2c4 100644 --- a/tests/explicit-nulls/flexible-unpickle/Unsafe_1.scala +++ b/tests/explicit-nulls/flexible-unpickle/pos/Unsafe_1.scala @@ -73,9 +73,24 @@ object M { def test(input: => String): String = "foo " + input } + +class N[F[_]] { + def accept[A](arg: F[A]): Nothing = ??? +} + class S[X] object S { def show[X] = "dummyStr" } class T class U[Y](a: Y) def expects(take: (T) ?=> U[String]) = ??? +// i23935 +object ZIO: + def attempt[A](code: => A): ZIO[Any, Throwable, A] = ??? + +trait ZIO[-R, +E, +A]: + final def flatten[R1 <: R, E1 >: E, B](using A IsSubtypeOfOutput ZIO[R1, E1, B]): ZIO[R1, E1, B] = ??? + +infix sealed abstract class IsSubtypeOfOutput[-A, +B] +object IsSubtypeOfOutput: + given [A, B](using A <:< B): IsSubtypeOfOutput[A, B] = ??? diff --git a/tests/explicit-nulls/pos/i23933.scala b/tests/explicit-nulls/pos/i23933.scala new file mode 100644 index 000000000000..e8529a94c801 --- /dev/null +++ b/tests/explicit-nulls/pos/i23933.scala @@ -0,0 +1,14 @@ +enum FormatPattern: + case AsInt + case AsLong + +// some basic operations with enum: +def test = + val p1 = FormatPattern.AsInt + val p2 = FormatPattern.AsLong + val p3 = FormatPattern.valueOf("AsInt") + val p4 = FormatPattern.values(0) + val ord1 = p1.ordinal + val ord2 = p2.ordinal + val str1 = p1.toString() + val str2 = p2.toString() \ No newline at end of file diff --git a/tests/explicit-nulls/pos/i23936.scala b/tests/explicit-nulls/pos/i23936.scala new file mode 100644 index 000000000000..041e358c87f0 --- /dev/null +++ b/tests/explicit-nulls/pos/i23936.scala @@ -0,0 +1,8 @@ +//> using options -Yexplicit-nulls + +sealed abstract class IsSubtypeOfOutput[-A, +B] extends (A => B) +object IsSubtypeOfOutput: + private val instance: IsSubtypeOfOutput[Any, Any] = new IsSubtypeOfOutput[Any, Any] { def apply(a: Any): Any = a } + +sealed trait DerivationAnnotation +class Rename(name: String) extends DerivationAnnotation