diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a71f28f49410..5277bf200e20 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -410,6 +410,7 @@ private sealed trait YSettings: // Experimental language features val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Disable kind polymorphism.") val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") + val YflexibleTypes: Setting[Boolean] = BooleanSetting("-Yflexible-types", "Make Java return types and parameter types use flexible types, which have a nullable lower bound and non-null upper bound.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects.") val YcheckInitGlobal: Setting[Boolean] = BooleanSetting("-Ysafe-init-global", "Check safe initialization of global objects.") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index dec73f7243e8..8cf8326caba5 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -472,6 +472,9 @@ object Contexts { /** Is the explicit nulls option set? */ def explicitNulls: Boolean = base.settings.YexplicitNulls.value + /** Is the flexible types option set? */ + def flexibleTypes: Boolean = base.settings.YexplicitNulls.value && base.settings.YflexibleTypes.value + /** A fresh clone of this context embedded in this context. */ def fresh: FreshContext = freshOver(this) diff --git a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala index 6244923cfb52..8e1a7624918b 100644 --- a/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala @@ -78,11 +78,11 @@ object JavaNullInterop { * but the result type is not nullable. */ private def nullifyExceptReturnType(tp: Type)(using Context): Type = - new JavaNullMap(true)(tp) + new JavaNullMap(outermostLevelAlreadyNullable = true)(tp) /** Nullifies a Java type by adding `| Null` in the relevant places. */ private def nullifyType(tp: Type)(using Context): Type = - new JavaNullMap(false)(tp) + new JavaNullMap(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. @@ -96,25 +96,29 @@ object JavaNullInterop { * to `(A & B) | Null`, instead of `(A | Null & B | Null) | Null`. */ private class JavaNullMap(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 = - !outermostLevelAlreadyNullable && (tp match { + !(outermostLevelAlreadyNullable || (tp match { case tp: TypeRef => // We don't modify value types because they're non-nullable even in Java. - !tp.symbol.isValueClass && + 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) && + || 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. - !tp.isRef(defn.RepeatedParamClass) - case _ => true - }) + || !ctx.flexibleTypes && tp.isRef(defn.RepeatedParamClass) + case _ => false + })) override def apply(tp: Type): Type = tp match { - case tp: TypeRef if needsNull(tp) => OrNull(tp) + case tp: TypeRef 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. @@ -124,7 +128,7 @@ object JavaNullInterop { val targs2 = targs map this outermostLevelAlreadyNullable = oldOutermostNullable val appTp2 = derivedAppliedType(appTp, tycon, targs2) - if needsNull(tycon) then OrNull(appTp2) else appTp2 + if needsNull(tycon) then nullify(appTp2) else appTp2 case ptp: PolyType => derivedLambdaType(ptp)(ptp.paramInfos, this(ptp.resType)) case mtp: MethodType => @@ -138,12 +142,12 @@ object JavaNullInterop { // 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 - OrNull(derivedAndType(tp, this(tp.tp1), this(tp.tp2))) - case tp: TypeParamRef if needsNull(tp) => OrNull(tp) + 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 } } -} +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 4f22f9d31e36..19baacfc70bd 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -8,6 +8,12 @@ import Types.* object NullOpsDecorator: extension (self: Type) + def stripFlexible(using Context): Type = { + self match { + case FlexibleType(tp) => tp + case _ => self + } + } /** Syntactically strips the nullability from this type. * If the type is `T1 | ... | Tn`, and `Ti` references to `Null`, * then return `T1 | ... | Ti-1 | Ti+1 | ... | Tn`. @@ -33,6 +39,7 @@ object NullOpsDecorator: if (tp1s ne tp1) && (tp2s ne tp2) then tp.derivedAndType(tp1s, tp2s) else tp + case tp: FlexibleType => tp.hi case tp @ TypeBounds(lo, hi) => tp.derivedTypeBounds(strip(lo), strip(hi)) case tp => tp diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index e11ac26ef93c..bc9659ab794e 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -564,6 +564,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case CapturingType(parent, refs) => val parent1 = recur(parent) if parent1 ne parent then tp.derivedCapturingType(parent1, refs) else tp + case tp: FlexibleType => + val underlying = recur(tp.underlying) + if underlying ne tp.underlying then tp.derivedFlexibleType(underlying) else tp case tp: AnnotatedType => val parent1 = recur(tp.parent) if parent1 ne tp.parent then tp.derivedAnnotatedType(parent1, tp.annot) else tp diff --git a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala index 4e3596ea8814..3ab50d561fcf 100644 --- a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala +++ b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala @@ -10,6 +10,7 @@ import Contexts.ctx import dotty.tools.dotc.reporting.trace import config.Feature.migrateTo3 import config.Printers.* +import dotty.tools.dotc.core.NullOpsDecorator.stripFlexible trait PatternTypeConstrainer { self: TypeComparer => @@ -163,7 +164,7 @@ trait PatternTypeConstrainer { self: TypeComparer => } } - def dealiasDropNonmoduleRefs(tp: Type) = tp.dealias match { + def dealiasDropNonmoduleRefs(tp: Type): Type = tp.dealias match { case tp: TermRef => // we drop TermRefs that don't have a class symbol, as they can't // meaningfully participate in GADT reasoning and just get in the way. @@ -172,6 +173,7 @@ trait PatternTypeConstrainer { self: TypeComparer => // additional trait - argument-less enum cases desugar to vals. // See run/enum-Tree.scala. if tp.classSymbol.exists then tp else tp.info + case FlexibleType(tp) => dealiasDropNonmoduleRefs(tp) case tp => tp } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 76236d635182..6d831e6a3f1b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -23,6 +23,7 @@ import reporting.trace import annotation.constructorOnly import cc.* import NameKinds.WildcardParamName +import NullOpsDecorator.stripFlexible /** Provides methods to compare types. */ @@ -524,7 +525,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling constraint = constraint.hardenTypeVars(tp2) res - case tp1 @ CapturingType(parent1, refs1) => def compareCapturing = if tp2.isAny then true @@ -863,6 +863,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } compareClassInfo + case tp2: FlexibleType => + recur(tp1, tp2.lo) case _ => fourthTry } @@ -1058,6 +1060,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp1: ExprType if ctx.phaseId > gettersPhase.id => // getters might have converted T to => T, need to compensate. recur(tp1.widenExpr, tp2) + case tp1: FlexibleType => + recur(tp1.hi, tp2) case _ => false } @@ -2499,15 +2503,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling NoType } - private def andTypeGen(tp1: Type, tp2: Type, op: (Type, Type) => Type, - original: (Type, Type) => Type = _ & _, isErased: Boolean = ctx.erasedTypes): Type = trace(s"andTypeGen(${tp1.show}, ${tp2.show})", subtyping, show = true) { - val t1 = distributeAnd(tp1, tp2) - if (t1.exists) t1 - else { - val t2 = distributeAnd(tp2, tp1) - if (t2.exists) t2 - else if (isErased) erasedGlb(tp1, tp2) - else liftIfHK(tp1, tp2, op, original, _ | _) + private def andTypeGen(tp1orig: Type, tp2orig: Type, op: (Type, Type) => Type, + original: (Type, Type) => Type = _ & _, isErased: Boolean = ctx.erasedTypes): Type = trace(s"andTypeGen(${tp1orig.show}, ${tp2orig.show})", subtyping, show = true) { + val tp1 = tp1orig.stripFlexible + val tp2 = tp2orig.stripFlexible + val ret = { + val t1 = distributeAnd(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = distributeAnd(tp2, tp1) + if (t2.exists) t2 + else if (isErased) erasedGlb(tp1, tp2) + else liftIfHK(tp1, tp2, op, original, _ | _) // The ` | ` on variances is needed since variances are associated with bounds // not lambdas. Example: // @@ -2517,7 +2524,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // // Here, `F` is treated as bivariant in `O`. That is, only bivariant implementation // of `F` are allowed. See neg/hk-variance2s.scala test. + } } + if(tp1orig.isInstanceOf[FlexibleType] && tp2orig.isInstanceOf[FlexibleType]) FlexibleType(ret) else ret } /** Form a normalized conjunction of two types. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e5c166f28d78..0264b214bef1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -343,6 +343,7 @@ object Types extends TypeUtils { /** Is this type guaranteed not to have `null` as a value? */ final def isNotNull(using Context): Boolean = this match { case tp: ConstantType => tp.value.value != null + case tp: FlexibleType => false case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass case tp: AppliedType => tp.superType.isNotNull case tp: TypeBounds => tp.lo.isNotNull @@ -372,6 +373,7 @@ object Types extends TypeUtils { case AppliedType(tycon, args) => tycon.unusableForInference || args.exists(_.unusableForInference) case RefinedType(parent, _, rinfo) => parent.unusableForInference || rinfo.unusableForInference case TypeBounds(lo, hi) => lo.unusableForInference || hi.unusableForInference + case FlexibleType(underlying) => underlying.unusableForInference case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) case WildcardType(optBounds) => optBounds.unusableForInference @@ -3396,6 +3398,40 @@ object Types extends TypeUtils { } } + // --- FlexibleType ----------------------------------------------------------------- + + /* Represents a nullable type coming from Java code in a similar way to Platform Types + * in Kotlin. A FlexibleType(T) generally behaves like an abstract type with bad bounds + * T|Null .. T, so that T|Null <: FlexibleType(T) <: T. + */ + case class FlexibleType(original: Type, lo: Type, hi: Type) extends CachedProxyType with ValueType { + def underlying(using Context): Type = original + + override def superType(using Context): Type = hi + + def derivedFlexibleType(original: Type)(using Context): Type = + if this.original eq original then this else FlexibleType(original) + + override def computeHash(bs: Binders): Int = doHash(bs, original) + + override final def baseClasses(using Context): List[ClassSymbol] = original.baseClasses + } + + object FlexibleType { + def apply(original: Type)(using Context): FlexibleType = original match { + case ft: FlexibleType => ft + case _ => + val hi = original.stripNull + val lo = if hi eq original then OrNull(hi) else original + new FlexibleType(original, lo, hi) + } + + def unapply(tp: Type)(using Context): Option[Type] = tp match { + case ft: FlexibleType => Some(ft.original) + case _ => None + } + } + // --- AndType/OrType --------------------------------------------------------------- abstract class AndOrType extends CachedGroundType with ValueType { @@ -5694,6 +5730,8 @@ object Types extends TypeUtils { samClass(tp.underlying) case tp: AnnotatedType => samClass(tp.underlying) + case tp: FlexibleType => + samClass(tp.superType) case _ => NoSymbol @@ -5824,6 +5862,8 @@ object Types extends TypeUtils { tp.derivedJavaArrayType(elemtp) protected def derivedExprType(tp: ExprType, restpe: Type): Type = tp.derivedExprType(restpe) + protected def derivedFlexibleType(tp: FlexibleType, under: Type): Type = + tp.derivedFlexibleType(under) // note: currying needed because Scala2 does not support param-dependencies protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type = tp.derivedLambdaType(tp.paramNames, formals, restpe) @@ -5947,6 +5987,9 @@ object Types extends TypeUtils { case tp: OrType => derivedOrType(tp, this(tp.tp1), this(tp.tp2)) + case tp: FlexibleType => + derivedFlexibleType(tp, this(tp.underlying)) + case tp: MatchType => val bound1 = this(tp.bound) val scrut1 = atVariance(0)(this(tp.scrutinee)) @@ -6234,6 +6277,14 @@ object Types extends TypeUtils { if (underlying.isExactlyNothing) underlying else tp.derivedAnnotatedType(underlying, annot) } + override protected def derivedFlexibleType(tp: FlexibleType, underlying: Type): Type = + underlying match { + case Range(lo, hi) => + range(tp.derivedFlexibleType(lo), tp.derivedFlexibleType(hi)) + case _ => + if (underlying.isExactlyNothing) underlying + else tp.derivedFlexibleType(underlying) + } override protected def derivedCapturingType(tp: Type, parent: Type, refs: CaptureSet): Type = parent match // TODO ^^^ handle ranges in capture sets as well case Range(lo, hi) => @@ -6375,6 +6426,9 @@ object Types extends TypeUtils { case tp: TypeVar => this(x, tp.underlying) + case tp: FlexibleType => + this(x, tp.underlying) + case ExprType(restpe) => this(x, restpe) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dafd6c2e8daa..667eddfbe7bf 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -269,6 +269,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { case tpe: OrType => writeByte(ORtype) withLength { pickleType(tpe.tp1, richTypes); pickleType(tpe.tp2, richTypes) } + case tpe: FlexibleType => + writeByte(FLEXIBLEtype) + withLength { pickleType(tpe.underlying, richTypes) } case tpe: ExprType => writeByte(BYNAMEtype) pickleType(tpe.underlying) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index d4271d5bffaf..a1502b32867e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -430,6 +430,8 @@ class TreeUnpickler(reader: TastyReader, readTypeRef() match { case binder: LambdaType => binder.paramRefs(readNat()) } + case FLEXIBLEtype => + FlexibleType(readType()) } assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") result diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 7fed5bc97f35..83b1edcbf2e6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -287,6 +287,8 @@ class PlainPrinter(_ctx: Context) extends Printer { case AnnotatedType(tpe, annot) => if annot.symbol == defn.InlineParamAnnot || annot.symbol == defn.ErasedParamAnnot then toText(tpe) else toTextLocal(tpe) ~ " " ~ toText(annot) + case FlexibleType(tpe) => + "FlexibleType(" ~ toText(tpe) ~ ")" case tp: TypeVar => def toTextCaret(tp: Type) = if printDebug then toTextLocal(tp) ~ Str("^") else toText(tp) if (tp.isInstantiated) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index dafb44d525e4..d3307e651233 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -565,6 +565,8 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case tp: OrType => val s = combineApiTypes(apiType(tp.tp1), apiType(tp.tp2)) withMarker(s, orMarker) + case tp: FlexibleType => + apiType(tp.underlying) case ExprType(resultType) => withMarker(apiType(resultType), byNameMarker) case MatchType(bound, scrut, cases) => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 004b21ce4fb5..78ae41025095 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -643,14 +643,15 @@ trait Applications extends Compatibility { missingArg(n) } - if (formal.isRepeatedParam) + val formal1 = formal.stripFlexible + if (formal1.isRepeatedParam) args match { case arg :: Nil if isVarArg(arg) => addTyped(arg) case (arg @ Typed(Literal(Constant(null)), _)) :: Nil if ctx.isAfterTyper => addTyped(arg) case _ => - val elemFormal = formal.widenExpr.argTypesLo.head + val elemFormal = formal1.widenExpr.argTypesLo.head val typedArgs = harmonic(harmonizeArgs, elemFormal) { args.map { arg => diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index cc3fac3a6ffd..5783b1fc2280 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -33,20 +33,24 @@ object Nullables: && hi.isValueType // We cannot check if hi is nullable, because it can cause cyclic reference. + private def nullifiedHi(lo: Type, hi: Type)(using Context): Type = + if needNullifyHi(lo, hi) then + if ctx.flexibleTypes then FlexibleType(hi) else OrNull(hi) + else hi + /** Create a nullable type bound * If lo is `Null`, `| Null` is added to hi */ def createNullableTypeBounds(lo: Type, hi: Type)(using Context): TypeBounds = - val newHi = if needNullifyHi(lo, hi) then OrType(hi, defn.NullType, soft = false) else hi - TypeBounds(lo, newHi) + TypeBounds(lo, nullifiedHi(lo, hi)) /** Create a nullable type bound tree * If lo is `Null`, `| Null` is added to hi */ def createNullableTypeBoundsTree(lo: Tree, hi: Tree, alias: Tree = EmptyTree)(using Context): TypeBoundsTree = - val hiTpe = hi.typeOpt - val newHi = if needNullifyHi(lo.typeOpt, hiTpe) then TypeTree(OrType(hiTpe, defn.NullType, soft = false)) else hi - TypeBoundsTree(lo, newHi, alias) + val hiTpe = nullifiedHi(lo.typeOpt, hi.typeOpt) + val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) + TypeBoundsTree(lo, hiTree, alias) /** 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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a1ef6c0b2f25..1fd8a7717681 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -969,13 +969,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // A sequence argument `xs: _*` can be either a `Seq[T]` or an `Array[_ <: T]`, // irrespective of whether the method we're calling is a Java or Scala method, // so the expected type is the union `Seq[T] | Array[_ <: T]`. - val ptArg = + val pt1 = pt.stripFlexible + val ptArg0 = // FIXME(#8680): Quoted patterns do not support Array repeated arguments if ctx.mode.isQuotedPattern then - pt.translateFromRepeated(toArray = false, translateWildcard = true) + pt1.translateFromRepeated(toArray = false, translateWildcard = true) else - pt.translateFromRepeated(toArray = false, translateWildcard = true) - | pt.translateFromRepeated(toArray = true, translateWildcard = true) + pt1.translateFromRepeated(toArray = false, translateWildcard = true) + | pt1.translateFromRepeated(toArray = true, translateWildcard = true) + val ptArg = if pt1 eq pt then ptArg0 else FlexibleType(ptArg0) val expr0 = typedExpr(tree.expr, ptArg) val expr1 = if ctx.explicitNulls && (!ctx.mode.is(Mode.Pattern)) then if expr0.tpe.isNullType then @@ -4257,10 +4259,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } // convert function literal to SAM closure + val pt1 = pt.stripFlexible tree match { case closure(Nil, id @ Ident(nme.ANON_FUN), _) - if defn.isFunctionNType(wtp) && !defn.isFunctionNType(pt) => - pt match { + if defn.isFunctionNType(wtp) && !defn.isFunctionNType(pt1) => + pt1 match { case SAMType(samMeth, samParent) if wtp <:< samMeth.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) => // was ... && isFullyDefined(pt, ForceDegree.flipBottom) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 41b8de0d6138..6a775ec62528 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -212,6 +212,28 @@ class CompilationTests { compileFilesInDir("tests/explicit-nulls/run", defaultOptions and "-Yexplicit-nulls") }.checkRuns() + // Flexible types tests + // @Test def flexibleTypesRun: Unit = { + // implicit val testGroup: TestGroup = TestGroup("flexibleTypesRun") + // compileFilesInDir("tests/flexible-types/run", flexibleTypesOptions) + // }.checkRuns() + + @Test def flexibleTypesNeg: Unit = { + implicit val testGroup: TestGroup = TestGroup("flexibleTypesNeg") + aggregateTests( + // compileFilesInDir("tests/explicit-nulls/flexible-types/neg", defaultOptions and "-Yexplicit-nulls"), + compileFilesInDir("tests/explicit-nulls/flexible-types/common", defaultOptions and "-Yexplicit-nulls"), + ) + }.checkExpectedErrors() + + @Test def flexibleTypesPos: Unit = { + implicit val testGroup: TestGroup = TestGroup("flexibleTypesPos") + aggregateTests( + compileFilesInDir("tests/explicit-nulls/flexible-types/pos", flexibleTypesOptions), + compileFilesInDir("tests/explicit-nulls/flexible-types/common", flexibleTypesOptions), + ) + }.checkCompile() + // initialization tests @Test def checkInitGlobal: Unit = { implicit val testGroup: TestGroup = TestGroup("checkInitGlobal") diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 04be00fe921e..cc5d40be1244 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -87,6 +87,10 @@ object TestConfiguration { val picklingWithCompilerOptions = picklingOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath) + val explicitNullsOptions = defaultOptions and "-Yexplicit-nulls" + + val flexibleTypesOptions = explicitNullsOptions and "-Yflexible-types" + /** Default target of the generated class files */ private def defaultTarget: String = { import scala.util.Properties.isJavaAtLeast diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 09feaf11c31d..f9508d30e710 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -61,4 +61,5 @@ object Predef: inline def ne(inline y: AnyRef | Null): Boolean = !(x eq y) + extension (inline opt: Option.type) inline def fromNullable[T](t: T|Null): Option[T] = Option(t).asInstanceOf end Predef diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 6ceb82f011f4..ee0050d20a88 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -604,6 +604,7 @@ object TastyFormat { final val MATCHtype = 190 final val MATCHtpt = 191 final val MATCHCASEtype = 192 + final val FLEXIBLEtype = 193 final val HOLE = 255 diff --git a/tests/explicit-nulls/flexible-types/common/i7883.scala b/tests/explicit-nulls/flexible-types/common/i7883.scala new file mode 100644 index 000000000000..9ee92553b60d --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/i7883.scala @@ -0,0 +1,9 @@ +import scala.util.matching.Regex + +object Test extends App { + def head(s: String, r: Regex): Option[(String, String)] = + s.trim match { + case r(hd, tl) => Some((hd, tl)) // error // error // error + case _ => None + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/interop-propagate.scala b/tests/explicit-nulls/flexible-types/common/interop-propagate.scala new file mode 100644 index 000000000000..40eb12dd287c --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/interop-propagate.scala @@ -0,0 +1,18 @@ + class Foo { + import java.util.ArrayList + + // Test that type mapping works with flexible types. + val ll: ArrayList[ArrayList[ArrayList[String]]] = 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 lb = new ArrayList[ArrayList[ArrayList[String]]] + val levelA = lb.get(0) + val levelB = lb.get(0).get(0) // error + val levelC = lb.get(0).get(0).get(0) // error + + val x = levelA.get(0) // error + val y = levelB.get(0) + val z: String = levelA.get(0).get(0) // error +} diff --git a/tests/explicit-nulls/flexible-types/common/java-call/J.java b/tests/explicit-nulls/flexible-types/common/java-call/J.java new file mode 100644 index 000000000000..7adc03f6898e --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-call/J.java @@ -0,0 +1,28 @@ +public class J { + + public class K {} + + public J self() { + return this; + } + + public String f1() { + return ""; + } + + public int f2() { + return 0; + } + + public K f3() { + return null; + } + + public T g1() { + return null; + } +} + +class J2 { + public T x = null; +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/java-call/S.scala b/tests/explicit-nulls/flexible-types/common/java-call/S.scala new file mode 100644 index 000000000000..e5f9abe70d5f --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-call/S.scala @@ -0,0 +1,37 @@ +// Check Java calls have been cast to non-nullable. + +val j: J = new J + +val jj = j.self() + +val s1: String = j.f1() // error + +val s1n: String | Null = j.f1() + +val i1: Int = j.f2() + +val k: jj.K = jj.f3() // error // error + +val s2: String = j.g1[String]() // error + +val s2n: String | Null = j.g1[String]() + +val s3: String = j.g1[String | Null]() // error + +val s3n: String | Null = j.g1[String | Null]() + +val i2: Int = j.g1[Int]() // error + +val a1: Any = j.g1[Any]() + +val ar1: AnyRef = j.g1[AnyRef]() // error + +val n1: Null = j.g1[Null]() + +def clo1[T]: T = j.g1[T]() // error + +def clo3[T >: Null <: AnyRef | Null]: T = j.g1[T]() + +def testJ2[T]: T = + val j2: J2[T] = new J2 + j2.x // error diff --git a/tests/explicit-nulls/flexible-types/common/java-chain/J.java b/tests/explicit-nulls/flexible-types/common/java-chain/J.java new file mode 100644 index 000000000000..bd266bae13d9 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-chain/J.java @@ -0,0 +1,7 @@ +class J1 { + J2 getJ2() { return new J2(); } +} + +class J2 { + J1 getJ1() { return new J1(); } +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/java-chain/S.scala b/tests/explicit-nulls/flexible-types/common/java-chain/S.scala new file mode 100644 index 000000000000..9fe5aa3f08ce --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-chain/S.scala @@ -0,0 +1,4 @@ +class S { + val j: J2 = new J2() + j.getJ1().getJ2().getJ1().getJ2().getJ1().getJ2() // error +} diff --git a/tests/explicit-nulls/flexible-types/common/java-varargs-src/J.java b/tests/explicit-nulls/flexible-types/common/java-varargs-src/J.java new file mode 100644 index 000000000000..21ba08be66c9 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-varargs-src/J.java @@ -0,0 +1,3 @@ +abstract class J { + abstract void foo(String... x); +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/java-varargs-src/S.scala b/tests/explicit-nulls/flexible-types/common/java-varargs-src/S.scala new file mode 100644 index 000000000000..87b73ab9f9ea --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/java-varargs-src/S.scala @@ -0,0 +1,20 @@ +class S { + val j: J = ??? + + j.foo() + j.foo("") + j.foo(null) + j.foo("", "") + j.foo("", null, "") + + val arg1: Array[String] = ??? + val arg2: Array[String | Null] = ??? + val arg3: Array[String] | Null = ??? + val arg4: Array[String | Null] | Null = ??? + + j.foo(arg1: _*) + j.foo(arg2: _*) + // TODO: fix for flexible types + j.foo(arg3: _*) // error + j.foo(arg4: _*) // error +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/unsafe-chain.scala b/tests/explicit-nulls/flexible-types/common/unsafe-chain.scala new file mode 100644 index 000000000000..f087e22d50f9 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/unsafe-chain.scala @@ -0,0 +1,9 @@ +// Test that we can select through "| Null" is unsafeNulls is enabled (unsoundly). + +class Foo { + import java.util.ArrayList + import java.util.Iterator + + val x3 = new ArrayList[ArrayList[ArrayList[String]]]() + val x4: Int = x3.get(0).get(0).get(0).length() // error +} diff --git a/tests/explicit-nulls/flexible-types/common/unsafe-implicit.scala b/tests/explicit-nulls/flexible-types/common/unsafe-implicit.scala new file mode 100644 index 000000000000..4bbba8f11cab --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/unsafe-implicit.scala @@ -0,0 +1,10 @@ +class S { + locally { + // OfType Implicits + + import java.nio.charset.StandardCharsets + import scala.io.Codec + + val c: Codec = StandardCharsets.UTF_8 // error + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/unsafe-java-varargs.scala b/tests/explicit-nulls/flexible-types/common/unsafe-java-varargs.scala new file mode 100644 index 000000000000..8e61f5763391 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/unsafe-java-varargs.scala @@ -0,0 +1,38 @@ +import java.nio.file.Paths + +def test1 = { + Paths.get("") + Paths.get("", null) + Paths.get("", "") + Paths.get("", "", null) + + val x1: String = ??? + val x2: String | Null = ??? + + Paths.get("", x1) + Paths.get("", x2) +} + +def test2 = { + val xs1: Seq[String] = ??? + val xs2: Seq[String | Null] = ??? + val xs3: Seq[String | Null] | Null = ??? + val xs4: Seq[String] | Null = ??? + + val ys1: Array[String] = ??? + val ys2: Array[String | Null] = ??? + val ys3: Array[String | Null] | Null = ??? + val ys4: Array[String] | Null = ??? + + Paths.get("", xs1: _*) + Paths.get("", xs2: _*) + Paths.get("", xs3: _*) // error + Paths.get("", xs4: _*) // error + + Paths.get("", ys1: _*) + Paths.get("", ys2: _*) + Paths.get("", ys3: _*) // error + Paths.get("", ys4: _*) // error + + Paths.get("", null: _*) // error +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/common/unsafe-select-type-member.scala b/tests/explicit-nulls/flexible-types/common/unsafe-select-type-member.scala new file mode 100644 index 000000000000..ddd402545edb --- /dev/null +++ b/tests/explicit-nulls/flexible-types/common/unsafe-select-type-member.scala @@ -0,0 +1,7 @@ +import java.util.ArrayList + +def f[T]: ArrayList[T] = { + val cz = Class.forName("java.util.ArrayList") + val o = cz.newInstance() // error: T of Class[?] | Null + o.asInstanceOf[ArrayList[T]] +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/J.java b/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/J.java new file mode 100644 index 000000000000..c85a921a81b9 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/J.java @@ -0,0 +1,3 @@ +public class J { + public J j = this; +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/S.scala b/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/S.scala new file mode 100644 index 000000000000..8ff50ab63840 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/applied-type-in-java/S.scala @@ -0,0 +1,14 @@ +def test1[T](x: J[T]): J[T] = + x match { + case y: J[_] => y + } + +def test2[T](x: J[T]): J[T] = + x match { + case y: J[_] => y.j + } + +def test3[T](x: J[T]): J[T] = + x.j match { + case y: J[_] => y.j + } \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/equal/J.java b/tests/explicit-nulls/flexible-types/pos/equal/J.java new file mode 100644 index 000000000000..2a79059c99ef --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/equal/J.java @@ -0,0 +1,9 @@ +public class J { + public String f() { + return ""; + } + + public T g() { + return null; + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/equal/S.scala b/tests/explicit-nulls/flexible-types/pos/equal/S.scala new file mode 100644 index 000000000000..f36dfad977b8 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/equal/S.scala @@ -0,0 +1,17 @@ +// Check Java calls have been cast to non-nullable. + +def test[T <: AnyRef] = + val j: J = new J + + val s = j.f() + val sn = s == null + val sn2 = s != null + val seqn = s eq null + val seqn2 = null eq s + + + val t = j.g[T]() + val tn = t == null + val tn2 = t != null + val teqn = t eq null + val teqn2 = null eq t \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/interop-constructor-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-constructor-src/J.java new file mode 100644 index 000000000000..b1590d50023e --- /dev/null +++ b/tests/explicit-nulls/flexible-types/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/flexible-types/pos/interop-constructor-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-constructor-src/S.scala new file mode 100644 index 000000000000..3defd73f3945 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-constructor-src/S.scala @@ -0,0 +1,6 @@ + +class S { + val x1: J = new J("hello") + val x2: J = new J(null) + val x3: J = new J(null, null, null) +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-enum-src/Day.java b/tests/explicit-nulls/flexible-types/pos/interop-enum-src/Day.java new file mode 100644 index 000000000000..55dca0783931 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-enum-src/Day.java @@ -0,0 +1,6 @@ + +public enum Day { + SUN, + MON, + TUE +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-enum-src/Planet.java b/tests/explicit-nulls/flexible-types/pos/interop-enum-src/Planet.java new file mode 100644 index 000000000000..287aed6aecc5 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/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/flexible-types/pos/interop-enum-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-enum-src/S.scala new file mode 100644 index 000000000000..75e4654869a4 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/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/flexible-types/pos/interop-generics/J.java b/tests/explicit-nulls/flexible-types/pos/interop-generics/J.java new file mode 100644 index 000000000000..4bbdbd4cf319 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-generics/J.java @@ -0,0 +1,13 @@ + +class I {} + +class J { + I foo(T x) { + return new I(); + } + + I[] bar(T x) { + Object[] r = new Object[]{new I()}; + return (I[]) r; + } +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-generics/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-generics/S.scala new file mode 100644 index 000000000000..a5d885d7e664 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-generics/S.scala @@ -0,0 +1,9 @@ +class S { + val j = new J() + // Check that nullable and non-nullable work + val x: I[String] | Null = j.foo("hello") + val y: Array[I[String] | Null] | Null = j.bar[String](null) + val x2: I[String] = j.foo("hello") + val y2: Array[I[String] | Null] = j.bar[String](null) + val y3: Array[I[String]] = j.bar[String](null) +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-match-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-match-src/J.java new file mode 100644 index 000000000000..16f99f082ebb --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-match-src/J.java @@ -0,0 +1,5 @@ +class J { + public J j = this; + //public J bar() { return null; } +} + diff --git a/tests/explicit-nulls/flexible-types/pos/interop-match-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-match-src/S.scala new file mode 100644 index 000000000000..49e8852226ff --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-match-src/S.scala @@ -0,0 +1,6 @@ +class S[T] { + def bar(x: J[T]): J[T] = x.j match { + case y: J[_] => y.j + } +} + diff --git a/tests/explicit-nulls/flexible-types/pos/interop-method-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-method-src/J.java new file mode 100644 index 000000000000..1b7ea514e4b2 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/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/flexible-types/pos/interop-method-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-method-src/S.scala new file mode 100644 index 000000000000..403c86bc4c06 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/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/flexible-types/pos/interop-ortype-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-ortype-src/J.java new file mode 100644 index 000000000000..b0d767bccf3e --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-ortype-src/J.java @@ -0,0 +1,3 @@ +class J { + public static T foo(T t) { return null; } +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-ortype-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-ortype-src/S.scala new file mode 100644 index 000000000000..af3b44ab29a7 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-ortype-src/S.scala @@ -0,0 +1,7 @@ +// Tests that member finding works on (Flex(T) | S) +class S { + def foo(a: J|String) = (a match { + case x: J => J.foo(x: J) + case y: String => "" + }).asInstanceOf[J] +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-poly-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-poly-src/J.java new file mode 100644 index 000000000000..a0d5c109605e --- /dev/null +++ b/tests/explicit-nulls/flexible-types/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/flexible-types/pos/interop-poly-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-poly-src/S.scala new file mode 100644 index 000000000000..8aed9e99b689 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-poly-src/S.scala @@ -0,0 +1,37 @@ +// 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]() + val jc2: JavaCat[String] = J.getJavaCat[String]() + // ScalaCat is Scala-defined, so we need the inner |Null. + val sc: ScalaCat[String|Null]|Null = J.getScalaCat[String]() + val sc2: ScalaCat[String]|Null = J.getScalaCat[String]() + val sc3: ScalaCat[String|Null] = J.getScalaCat[String]() + val sc4: ScalaCat[String] = J.getScalaCat[String]() + + import java.util.List + + val las: List[Array[String|Null]]|Null = J.getListOfStringArray() + val las2: List[Array[String|Null]] = J.getListOfStringArray() + val las3: List[Array[String]]|Null = J.getListOfStringArray() + val las4: List[Array[String]] = J.getListOfStringArray() + val als: Array[List[String]|Null]|Null = J.getArrayOfStringList() + val als2: Array[List[String]|Null] = J.getArrayOfStringList() + val als3: Array[List[String]]|Null = J.getArrayOfStringList() + val als4: Array[List[String]] = J.getArrayOfStringList() + val css: List[Array[List[Array[String|Null]]|Null]]|Null = J.getComplexStrings() + val css2: List[Array[List[Array[String]]|Null]]|Null = J.getComplexStrings() + val css3: List[Array[List[Array[String|Null]]]]|Null = J.getComplexStrings() + val css4: List[Array[List[Array[String|Null]]|Null]] = J.getComplexStrings() + val css5: List[Array[List[Array[String|Null]]]] = J.getComplexStrings() + val css6: List[Array[List[Array[String]]]]|Null = J.getComplexStrings() + val css7: List[Array[List[Array[String]]|Null]] = J.getComplexStrings() + val css8: List[Array[List[Array[String]]]] = J.getComplexStrings() +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-sam-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-sam-src/J.java new file mode 100644 index 000000000000..336e252aa861 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-sam-src/J.java @@ -0,0 +1,22 @@ +import java.util.function.*; + +@FunctionalInterface +interface SAMJava1 { + public String[] f(String x); +} + +@FunctionalInterface +interface SAMJava2 { + public void f(int x); +} + +class J { + public void g1(SAMJava1 s) { + } + + public void g2(SAMJava2 s) { + } + + public void h1(Function s) { + } +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/interop-sam-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-sam-src/S.scala new file mode 100644 index 000000000000..c0da89163018 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-sam-src/S.scala @@ -0,0 +1,19 @@ +def m = { + val j: J = ??? + + def f1(x: String | Null): Array[String | Null] | Null = null + + def f2(i: Int): Unit = () + + j.g1(f1) + j.g1((_: String | Null) => null) + j.g1(null) + + j.g2(f2) + j.g2((_: Int) => ()) + j.g2(null) + + j.h1(f1) + j.h1((_: String | Null) => null) + j.h1(null) +} \ No newline at end of file diff --git a/tests/explicit-nulls/flexible-types/pos/interop-static-src/J.java b/tests/explicit-nulls/flexible-types/pos/interop-static-src/J.java new file mode 100644 index 000000000000..a233d9662950 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-static-src/J.java @@ -0,0 +1,5 @@ + +class J { + static int foo(String s) { return 42; } + static String bar(int i) { return null; } +} diff --git a/tests/explicit-nulls/flexible-types/pos/interop-static-src/S.scala b/tests/explicit-nulls/flexible-types/pos/interop-static-src/S.scala new file mode 100644 index 000000000000..61694434d018 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/interop-static-src/S.scala @@ -0,0 +1,6 @@ +class S { + // Java static methods are also nullified + val x: Int = J.foo(null) + val y: String | Null = J.bar(0) + val y2: String = J.bar(0) +} diff --git a/tests/explicit-nulls/flexible-types/pos/match-null.scala b/tests/explicit-nulls/flexible-types/pos/match-null.scala new file mode 100644 index 000000000000..4cdf7632fa35 --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/match-null.scala @@ -0,0 +1,5 @@ +def test1 = + val s: String = ??? + s.trim() match + case _: String => + case null => diff --git a/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/injava.java b/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/injava.java new file mode 100644 index 000000000000..28925b3c492a --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/injava.java @@ -0,0 +1,6 @@ +class injava { + static void overloaded(Runnable r) {} + static void overloaded(int i) {} + + static void notoverloaded(Runnable r) {} +} diff --git a/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/sam-test.scala b/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/sam-test.scala new file mode 100644 index 000000000000..552f1f01ba6e --- /dev/null +++ b/tests/explicit-nulls/flexible-types/pos/sam-parameter-javadefined/sam-test.scala @@ -0,0 +1,22 @@ +def foo = { + def unit: Unit = () + + injava.overloaded({ () => unit } : Runnable ) + injava.overloaded({ () => unit } ) + + injava.notoverloaded({ () => unit } : Runnable ) + injava.notoverloaded({ () => unit } ) + + val list = new java.util.Vector[Int]() + java.util.Collections.sort[Int](list, { (a,b) => a - b } : java.util.Comparator[Int] ) + java.util.Collections.sort[Int](list, { (a,b) => a - b }) + + new Thread({ () => unit } : Runnable ) + new Thread({ () => unit } ) + + val cf = new java.util.concurrent.CompletableFuture[String] + cf.handle[Unit]({ + case (string, null) => unit + case (string, throwable) => unit + }) +}