diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 5d6169cc6990..4ad7d3f1dff7 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -715,8 +715,8 @@ trait ConstraintHandling { tp.rebind(tp.parent.hardenUnions) case tp: HKTypeLambda => tp.derivedLambdaType(resType = tp.resType.hardenUnions) - case tp: FlexibleType => - tp.derivedFlexibleType(tp.hi.hardenUnions) + case tp @ FlexibleType(hi) => + FlexibleType.derivedFlexibleType(tp, hi.hardenUnions) case tp: OrType => val tp1 = tp.stripNull(stripFlexibleTypes = false) if tp1 ne tp then tp.derivedOrType(tp1.hardenUnions, defn.NullType, soft = false) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 152b9a55cde4..393a3174b3fd 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -427,6 +427,17 @@ class Definitions { newPermanentSymbol(OpsPackageClass, tpnme.FromJavaObject, JavaDefined, TypeAlias(ObjectType)).entered def FromJavaObjectType: TypeRef = FromJavaObjectSymbol.typeRef + @tu lazy val FlexibleTypeSymbol: TypeSymbol = + newPermanentSymbol(ScalaPackageClass, tpnme.FlexibleType, EmptyFlags, TypeBounds( + HKTypeLambda(TypeBounds.empty :: Nil)( + tl => OrNull(tl.paramRefs(0)) + ), + HKTypeLambda(TypeBounds.empty :: Nil)( + tl => tl.paramRefs(0) + ) + )).entered + def FlexibleTypeType: TypeRef = FlexibleTypeSymbol.typeRef + @tu lazy val AnyRefAlias: TypeSymbol = enterAliasType(tpnme.AnyRef, ObjectType) def AnyRefType: TypeRef = AnyRefAlias.typeRef @@ -1980,8 +1991,8 @@ class Definitions { asContextFunctionType(TypeComparer.bounds(tp1).hiBound) case tp1 @ PolyFunctionOf(mt: MethodType) if mt.isContextualMethod => tp1 - case tp: FlexibleType => - asContextFunctionType(tp.hi) + case FlexibleType(hi) => + asContextFunctionType(hi) 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 7350af348ea0..44745567afdd 100644 --- a/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala +++ b/compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala @@ -192,7 +192,7 @@ object ImplicitNullInterop: state = savedState if isNullAnnot then parent2 else parent2 match - case FlexibleType(_, parent2a) => + case FlexibleType(parent2a) => FlexibleType(derivedAnnotatedType(tp, parent2a, tp.annot)) case OrNull(parent2a) => OrNull(derivedAnnotatedType(tp, parent2a, tp.annot)) @@ -236,7 +236,7 @@ object ImplicitNullInterop: // 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 => + case (FlexibleType(t1), FlexibleType(t2)) if ctx.flexibleTypes => FlexibleType(derivedAndOrType(tp, t1, t2)) case (OrNull(t1), OrNull(t2)) => OrNull(derivedAndOrType(tp, t1, t2)) @@ -256,7 +256,7 @@ object ImplicitNullInterop: // If the parent type becomes nullable, then we pop the nullification to the outer level. parent2 match - case FlexibleType(_, parent2a) => + case FlexibleType(parent2a) => FlexibleType(derivedRefinedType(tp, parent2a, refinedInfo2)) case OrNull(parent2a) => OrNull(derivedRefinedType(tp, parent2a, refinedInfo2)) diff --git a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala index 572c94070066..ce066f05d119 100644 --- a/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala +++ b/compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala @@ -33,9 +33,9 @@ object NullOpsDecorator: if (tp1s ne tp1) && (tp2s ne tp2) then tp.derivedAndType(tp1s, tp2s) else tp - case tp: FlexibleType => - val hi1 = strip(tp.hi) - if stripFlexibleTypes then hi1 else tp.derivedFlexibleType(hi1) + case tp @ FlexibleType(hi) => + val hi1 = strip(hi) + if stripFlexibleTypes then hi1 else FlexibleType.derivedFlexibleType(tp, hi1) 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 b239c092f1df..1f46c54674ba 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -558,8 +558,8 @@ class OrderingConstraint(private val boundsMap: ParamBounds, if underlying1 ne tp.underlying then underlying1 else tp case CapturingType(parent, refs) => tp.derivedCapturingType(recur(parent), refs) - case tp: FlexibleType => - tp.derivedFlexibleType(recur(tp.hi)) + case tp @ FlexibleType(hi) => + FlexibleType.derivedFlexibleType(tp, recur(hi)) case tp: AnnotatedType => tp.derivedAnnotatedType(recur(tp.parent), tp.annot) case _ => @@ -755,7 +755,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case tp: TypeVar if contains(tp.origin) => withHard(tp) case tp: TypeParamRef if contains(tp) => hardenTypeVars(typeVarOfParam(tp)) case tp: AndOrType => hardenTypeVars(tp.tp1).hardenTypeVars(tp.tp2) - case tp: FlexibleType => hardenTypeVars(tp.hi) + case FlexibleType(hi) => hardenTypeVars(hi) case _ => this def remove(pt: TypeLambda)(using Context): This = { diff --git a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala index 9baf0c40a80b..5500fdfbedea 100644 --- a/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala +++ b/compiler/src/dotty/tools/dotc/core/PatternTypeConstrainer.scala @@ -167,7 +167,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 tp: FlexibleType => dealiasDropNonmoduleRefs(tp.underlying) + case FlexibleType(hi) => dealiasDropNonmoduleRefs(hi) case tp => tp } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 205e649b2390..938d12f52502 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -202,6 +202,7 @@ object StdNames { final val NotNull: N = "NotNull" final val Null: N = "Null" final val Object: N = "Object" + final val FlexibleType : N = "" final val FromJavaObject: N = "" final val Record: N = "Record" final val Product: N = "Product" diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 97b0af3e3a4f..fb0e847b6c11 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -552,8 +552,8 @@ class TypeApplications(val self: Type) extends AnyVal { * Existential types in arguments are returned as TypeBounds instances. */ final def argInfos(using Context): List[Type] = self.stripped match + case FlexibleType(hi) => hi.argInfos case AppliedType(tycon, args) => args - case tp: FlexibleType => tp.underlying.argInfos case _ => Nil /** If this is an encoding of a function type, return its arguments, otherwise return Nil. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e8c05f7bc6c0..adbe4f837e82 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -648,6 +648,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } def thirdTry: Boolean = tp2 match { + case FlexibleType(hi2) => + recur(tp1, OrNull(hi2)) case tp2 @ AppliedType(tycon2, args2) => compareAppliedType2(tp2, tycon2, args2) case tp2: NamedType => @@ -915,8 +917,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } compareClassInfo - case tp2: FlexibleType => - recur(tp1, tp2.lo) case _ => fourthTry } @@ -977,6 +977,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else fourthTry def fourthTry: Boolean = tp1 match { + case FlexibleType(hi1) => + recur(hi1, tp2) case tp1: TypeRef => tp1.info match { case info1 @ TypeBounds(lo1, hi1) => @@ -1129,8 +1131,6 @@ 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 } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 56f3e2599f10..f381d0a4d472 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -317,7 +317,7 @@ object Types extends TypeUtils { isRef(defn.ObjectClass) && (typeSymbol eq defn.FromJavaObjectSymbol) def containsFromJavaObject(using Context): Boolean = this match - case tp: FlexibleType => tp.underlying.containsFromJavaObject + case FlexibleType(hi) => hi.containsFromJavaObject case tp: OrType => tp.tp1.containsFromJavaObject || tp.tp2.containsFromJavaObject case tp: AndType => tp.tp1.containsFromJavaObject && tp.tp2.containsFromJavaObject case _ => isFromJavaObject @@ -382,7 +382,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 FlexibleType(_) => false case tp: ClassInfo => !tp.cls.isNullableClass && !tp.isNothingType case tp: AppliedType => tp.superType.isNotNull case tp: TypeBounds => tp.hi.isNotNull @@ -398,7 +398,7 @@ object Types extends TypeUtils { case OrType(l, r) => r.admitsNull || l.admitsNull case AndType(l, r) => r.admitsNull && l.admitsNull case TypeBounds(lo, hi) => lo.admitsNull - case FlexibleType(lo, hi) => true + case FlexibleType(_) => true case tp: TypeProxy => tp.underlying.admitsNull case _ => false ) @@ -423,7 +423,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 tp: FlexibleType => tp.underlying.unusableForInference + case FlexibleType(hi) => hi.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 @@ -464,8 +464,9 @@ object Types extends TypeUtils { (new isGroundAccumulator).apply(true, this) /** Is this a type of a repeated parameter? */ - def isRepeatedParam(using Context): Boolean = - typeSymbol eq defn.RepeatedParamClass + def isRepeatedParam(using Context): Boolean = this match + case FlexibleType(hi) => hi.isRepeatedParam + case _ => typeSymbol eq defn.RepeatedParamClass /** Is this type of the form `compiletime.into[T]`, which means it can be the * target of an implicit converson without requiring a language import? @@ -1471,8 +1472,8 @@ object Types extends TypeUtils { tp.rebind(tp.parent.widenUnion) case tp: HKTypeLambda => tp.derivedLambdaType(resType = tp.resType.widenUnion) - case tp: FlexibleType => - tp.derivedFlexibleType(tp.hi.widenUnionWithoutNull) + case tp @ FlexibleType(hi) => + FlexibleType.derivedFlexibleType(tp, hi.widenUnionWithoutNull) case tp => tp @@ -1902,8 +1903,8 @@ object Types extends TypeUtils { t case t @ SAMType(_, _) => t - case ft: FlexibleType => - ft.underlying.findFunctionType + case FlexibleType(hi) => + hi.findFunctionType case _ => NoType @@ -3474,24 +3475,24 @@ object Types extends TypeUtils { * `T | Null .. T`, so that `T | Null <: FlexibleType(T) <: T`. * A flexible type will be erased to its original type `T`. */ - case class FlexibleType protected(lo: Type, hi: Type) extends CachedProxyType with ValueType { + // case class FlexibleType protected(lo: Type, hi: Type) extends CachedProxyType with ValueType { - override def underlying(using Context): Type = hi + // override def underlying(using Context): Type = hi - def derivedFlexibleType(hi: Type)(using Context): Type = - if hi eq this.hi then this else FlexibleType.make(hi) + // def derivedFlexibleType(hi: Type)(using Context): Type = + // if hi eq this.hi then this else FlexibleType.make(hi) - override def computeHash(bs: Binders): Int = doHash(bs, hi) + // override def computeHash(bs: Binders): Int = doHash(bs, hi) - override final def baseClasses(using Context): List[ClassSymbol] = hi.baseClasses - } + // override final def baseClasses(using Context): List[ClassSymbol] = hi.baseClasses + // } object FlexibleType: - def apply(tp: Type)(using Context): FlexibleType = + def apply(tp: Type)(using Context): Type = assert(tp.isValueType, s"Should not flexify ${tp}") tp match - case ft: FlexibleType => ft - case _ => FlexibleType(OrNull(tp), tp) + case ft @ FlexibleType(hi) => ft + case _ => AppliedType(defn.FlexibleTypeType, tp :: Nil) // val tp1 = tp.stripNull() // if tp1.isNullType then // // (Null)? =:= ? >: Null <: (Object & Null) @@ -3508,8 +3509,19 @@ object Types extends TypeUtils { // It is not necessary according to the use cases, so we choose to use a simpler // rule. + def unapply(tp: Type)(using Context): Option[Type] = tp match + case AppliedType(tycon, args) if tycon.isRef(defn.FlexibleTypeSymbol) => Some(args.head) + case _ => None + + def isInstance(tp: Type)(using Context): Boolean = unapply(tp).isDefined + + def derivedFlexibleType(tp: Type, hi: Type)(using Context): Type = + tp match + case FlexibleType(hi0) if hi eq hi0 => tp + case _ => FlexibleType.make(hi) + def make(tp: Type)(using Context): Type = tp match - case _: FlexibleType => tp // tp is already flexible + case tp @ FlexibleType(hi) => tp // tp is already flexible case SimpleOrNull(_) => tp // tp is already nullable case TypeBounds(lo, hi) => TypeBounds(FlexibleType.make(lo), FlexibleType.make(hi)) case wt: WildcardType => wt.optBounds match @@ -6154,8 +6166,8 @@ object Types extends TypeUtils { samClass(tp.underlying) case tp: AnnotatedType => samClass(tp.underlying) - case tp: FlexibleType => - samClass(tp.underlying) + case FlexibleType(hi) => + samClass(hi) case _ => NoSymbol @@ -6313,8 +6325,6 @@ object Types extends TypeUtils { tp.derivedJavaArrayType(elemtp) protected def derivedExprType(tp: ExprType, restpe: Type): Type = tp.derivedExprType(restpe) - protected def derivedFlexibleType(tp: FlexibleType, hi: Type): Type = - tp.derivedFlexibleType(hi) // 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) @@ -6512,9 +6522,6 @@ object Types extends TypeUtils { case tp: OrType => derivedOrType(tp, this(tp.tp1), this(tp.tp2)) - case tp: FlexibleType => - derivedFlexibleType(tp, this(tp.hi)) - case tp: MatchType => val bound1 = this(tp.bound) val scrut1 = atVariance(0)(this(tp.scrutinee)) @@ -6759,6 +6766,16 @@ object Types extends TypeUtils { override protected def derivedAppliedType(tp: AppliedType, tycon: Type, args: List[Type]): Type = tycon match { + case tr if tr.isRef(defn.FlexibleTypeSymbol) => + val hi = args.head + hi match { + case Range(lo, hi) => + // We know FlexibleType(t).hi = t and FlexibleType(t).lo = OrNull(t) + range(OrNull(lo), hi) + case _ => + if (hi.isExactlyNothing) hi + else FlexibleType.derivedFlexibleType(tp, hi) + } case Range(tyconLo, tyconHi) => range(derivedAppliedType(tp, tyconLo, args), derivedAppliedType(tp, tyconHi, args)) case _ => @@ -6826,16 +6843,6 @@ object Types extends TypeUtils { else tp.derivedAnnotatedType(underlying, annot) } - override protected def derivedFlexibleType(tp: FlexibleType, hi: Type): Type = - hi match { - case Range(lo, hi) => - // We know FlexibleType(t).hi = t and FlexibleType(t).lo = OrNull(t) - range(OrNull(lo), hi) - case _ => - if (hi.isExactlyNothing) hi - else tp.derivedFlexibleType(hi) - } - 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) => @@ -6975,9 +6982,6 @@ 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 22f2e53287ea..d5fd124366e1 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -189,6 +189,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { } private def pickleNewType(tpe: Type, richTypes: Boolean)(using Context): Unit = tpe match { + case FlexibleType(hi) => + writeByte(FLEXIBLEtype) + withLength { pickleType(hi, richTypes) } case AppliedType(tycon, args) => if tycon.typeSymbol == defn.MatchCaseClass then writeByte(MATCHCASEtype) @@ -296,9 +299,6 @@ 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/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index b47d9c2b42ee..159ce867bdb9 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -334,7 +334,7 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(tpe) case _ => toTextLocal(tpe) ~ " " ~ toText(annot) - case FlexibleType(_, tpe) => + case FlexibleType(tpe) => "(" ~ toText(tpe) ~ ")?" case tp: TypeVar => def toTextCaret(tp: Type) = if printDebug then toTextLocal(tp) ~ Str("^") else toText(tp) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 999295d8cbe6..8be954beaadb 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -551,6 +551,8 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) else tp.prefix api.Projection.of(apiType(prefix), sym.name.toString) + case FlexibleType(hi) => + apiType(hi) case AppliedType(tycon, args) => def processArg(arg: Type): api.Type = arg match { case arg @ TypeBounds(lo, hi) => // Handle wildcard parameters @@ -630,8 +632,6 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol]) 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/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 2e9465d654d5..4efdb7cd987c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -151,8 +151,8 @@ object TypeTestsCasts { // - T1 & T2 <:< T3 // See TypeComparer#either recur(tp1, P) && recur(tp2, P) - case tpX: FlexibleType => - recur(tpX.underlying, P) + case FlexibleType(hi) => + recur(hi, P) case x => // always false test warnings are emitted elsewhere // provablyDisjoint wants fully applied types as input; because we're in the middle of erasure, we sometimes get raw types here diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 2516082ffbbb..ee853b2e952e 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -926,7 +926,7 @@ object SpaceEngine { case tp: SingletonType => toUnderlying(tp.underlying) case tp: ExprType => toUnderlying(tp.resultType) case AnnotatedType(tp, annot) => AnnotatedType(toUnderlying(tp), annot) - case tp: FlexibleType => tp.derivedFlexibleType(toUnderlying(tp.underlying)) + case tp @ FlexibleType(hi) => FlexibleType.derivedFlexibleType(tp, toUnderlying(hi)) case _ => tp }) @@ -1070,7 +1070,7 @@ object SpaceEngine { def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)"): val selTyp = toUnderlying(m.selector.tpe).dealias - val isNullable = selTyp.isInstanceOf[FlexibleType] || selTyp.classSymbol.isNullableClass + val isNullable = FlexibleType.isInstance(selTyp) || selTyp.classSymbol.isNullableClass val targetSpace = trace(i"targetSpace($selTyp)"): if isNullable && !ctx.mode.is(Mode.SafeNulls) then project(OrType(selTyp, ConstantType(Constant(null)), soft = false)) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index b775c0457950..aca7a5f4af92 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1054,6 +1054,10 @@ trait Implicits: // This is done to check whether such types might plausibly be comparable to each other. val lift = new TypeMap { def apply(t: Type): Type = t match { + case FlexibleType(_) => + // Keep flexible types as-is: they admit null, and lifting their + // abstract tycon to its upper bound would lose that information. + t case t: TypeRef => t.info match { case TypeBounds(lo, hi) if lo.ne(hi) && !t.symbol.is(Opaque) => apply(hi) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index f28532d564eb..60d26b6ca900 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -625,13 +625,13 @@ object Inferencing { case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent)) case tp: LazyRef => captureWildcards(tp.ref) case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot) - case tp: FlexibleType => tp.derivedFlexibleType(captureWildcards(tp.hi)) + case tp @ FlexibleType(hi) => FlexibleType.derivedFlexibleType(tp, captureWildcards(hi)) case _ => tp } def hasCaptureConversionArg(tp: Type)(using Context): Boolean = tp match case tp: AppliedType => tp.args.exists(_.typeSymbol == defn.TypeBox_CAP) - case tp: FlexibleType => hasCaptureConversionArg(tp.hi) + case FlexibleType(hi) => hasCaptureConversionArg(hi) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2940135f53ed..bafdc5285e1f 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1236,7 +1236,7 @@ object RefChecks { && !sym.owner.isAnonymousClass && !sym.isOneOf(JavaOrPrivateOrSynthetic | InlineProxy | Param | Exported) then val resTp = sym.info.finalResultType - if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then + if resTp.existsPart(FlexibleType.isInstance(_), StopAt.Static) then report.warning( em"${sym.show} exposes a flexible type in its inferred result type ${resTp}. Consider annotating the type explicitly", sym.srcPos diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d88c2a2f837c..1104999bfd82 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1344,8 +1344,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (untpd.isWildcardStarArg(tree)) { def fromRepeated(pt: Type): Type = pt match - case pt: FlexibleType => - pt.derivedFlexibleType(fromRepeated(pt.hi)) + case pt @ FlexibleType(hi) => + FlexibleType.derivedFlexibleType(pt, fromRepeated(hi)) case _ => if ctx.mode.isQuotedPattern then // FIXME(#8680): Quoted patterns do not support Array repeated arguments @@ -1992,8 +1992,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val t1 = instantiatableTypeVar(tp.tp1) if t1.exists then t1 else instantiatableTypeVar(tp.tp2) - case tp: FlexibleType => - instantiatableTypeVar(tp.hi) + case FlexibleType(hi) => + instantiatableTypeVar(hi) case tp: TypeVar if isConstrainedByFunctionType(tp) => // Only instantiate if the type variable is constrained by function types tp @@ -2008,8 +2008,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case SAMType(_, _) => true case tp: AndOrType => containsFunctionType(tp.tp1) || containsFunctionType(tp.tp2) - case tp: FlexibleType => - containsFunctionType(tp.hi) + case FlexibleType(hi) => + containsFunctionType(hi) case _ => false containsFunctionType(bounds.lo) || containsFunctionType(bounds.hi) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 0bc9f14c284d..e24d6d27ac2f 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1967,9 +1967,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler self.subst(from, to) def typeArgs: List[TypeRepr] = self match + // `FlexibleType` is an `AppliedType` here (opaque alias is transparent in this + // scope), so check it via the dotc-level extractor to avoid `case AppliedType`. + case _ if Types.FlexibleType.isInstance(self) => Types.FlexibleType.unapply(self).get.typeArgs case AppliedType(_, args) => args case AnnotatedType(parent, _) => parent.typeArgs - case FlexibleType(underlying) => underlying.typeArgs case _ => List.empty end extension end TypeReprMethods @@ -2094,7 +2096,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object AppliedTypeTypeTest extends TypeTest[TypeRepr, AppliedType]: def unapply(x: TypeRepr): Option[AppliedType & x.type] = x match - case tpe: (Types.AppliedType & x.type) if !tpe.tycon.isRef(dotc.core.Symbols.defn.MatchCaseClass) => Some(tpe) + case tpe: (Types.AppliedType & x.type) + if !tpe.tycon.isRef(dotc.core.Symbols.defn.MatchCaseClass) && !Types.FlexibleType.isInstance(tpe) => Some(tpe) case _ => None end AppliedTypeTypeTest @@ -2459,24 +2462,28 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def unapply(x: NoPrefix): true = true end NoPrefix - type FlexibleType = dotc.core.Types.FlexibleType + // A flexible type is represented as an `AppliedType` over the synthetic + // `` symbol. We use an opaque type so that `FlexibleType` is + // nominally distinct from `AppliedType`, otherwise the two `TypeTest`s would + // be indistinguishable and `case FlexibleType(_)` could match plain applied types. + opaque type FlexibleType <: TypeRepr = dotc.core.Types.AppliedType object FlexibleTypeTypeTest extends TypeTest[TypeRepr, FlexibleType]: def unapply(x: TypeRepr): Option[FlexibleType & x.type] = x match - case x: (Types.FlexibleType & x.type) => Some(x) + case x: (Types.AppliedType & x.type) if Types.FlexibleType.isInstance(x) => Some(x) case _ => None end FlexibleTypeTypeTest object FlexibleType extends FlexibleTypeModule: - def apply(tp: TypeRepr): FlexibleType = Types.FlexibleType(tp) - def unapply(x: FlexibleType): Some[TypeRepr] = Some(x.hi) + def apply(tp: TypeRepr): FlexibleType = Types.FlexibleType(tp).asInstanceOf[FlexibleType] + def unapply(x: FlexibleType): Some[TypeRepr] = Some(Types.FlexibleType.unapply(x).get) end FlexibleType given FlexibleTypeMethods: FlexibleTypeMethods with extension (self: FlexibleType) - def underlying: TypeRepr = self.hi - def lo: TypeRepr = self.lo - def hi: TypeRepr = self.hi + def underlying: TypeRepr = Types.FlexibleType.unapply(self).get + def lo: TypeRepr = Types.OrNull(Types.FlexibleType.unapply(self).get) + def hi: TypeRepr = Types.FlexibleType.unapply(self).get end extension end FlexibleTypeMethods diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index b43b6e23e8ca..b4a61ddb8563 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -214,6 +214,8 @@ object Extractors { this += "TypeRef(" += qual += ", \"" += name += "\")" case Refinement(parent, name, info) => this += "Refinement(" += parent += ", \"" += name += "\", " += info += ")" + case FlexibleType(tp) => + this += "FlexibleType(" += tp += ")" case AppliedType(tycon, args) => this += "AppliedType(" += tycon += ", " ++= args += ")" case AnnotatedType(underlying, annot) => @@ -253,8 +255,6 @@ object Extractors { this += "NoPrefix()" case MatchCase(pat, rhs) => this += "MatchCase(" += pat += ", " += rhs += ")" - case FlexibleType(tp) => - this += "FlexibleType(" += tp += ")" case tp => this += s"" } diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 92b2290e3b1d..f97e19c8f6ed 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -1142,6 +1142,11 @@ object SourceCode { case tpe @ Refinement(_, _, _) => printRefinement(tpe) + case FlexibleType(tp) => + this += "(" + printType(tp) + this += ")?" + case AppliedType(tp, args) => tp match { case tp: TypeLambda => @@ -1260,11 +1265,6 @@ object SourceCode { this += " => " printType(rhs) - case FlexibleType(tp) => - this += "(" - printType(tp) - this += ")?" - case _ => cannotBeShownAsSource(tpe.show(using Printer.TypeReprStructure)) } diff --git a/tests/explicit-nulls/warn/expose-flexible-types.check b/tests/explicit-nulls/warn/expose-flexible-types.check index 5e0f8519b7e7..5189be9ecbd6 100644 --- a/tests/explicit-nulls/warn/expose-flexible-types.check +++ b/tests/explicit-nulls/warn/expose-flexible-types.check @@ -1,56 +1,56 @@ -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:4:6 -------------------------------------------------- 4 | def f = s.trim // warn | ^ - | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |method f exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:8:6 -------------------------------------------------- 8 | def h = (s.trim, s.length) // warn | ^ - |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly + |method h exposes a flexible type in its inferred result type ([String], Int). Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:9:16 ------------------------------------------------- 9 | protected def i = s.trim // warn | ^ - | method i exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |method i exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:11:19 ------------------------------------------------ 11 | private[foo] def k = s.trim // warn | ^ - | method k exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |method k exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:12:6 ------------------------------------------------- 12 | val ss = s.replace("a", "A") // warn | ^ - | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |value ss exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:13:6 ------------------------------------------------- 13 | val ss2 = Seq(s.trim) // warn | ^ - |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly + |value ss2 exposes a flexible type in its inferred result type Seq[[String]]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:21:6 ------------------------------------------------- 21 | def f = s2.trim // warn | ^ - | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |method f exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:25:6 ------------------------------------------------- 25 | def h = (s2.trim, s2.length) // warn | ^ - |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly + |method h exposes a flexible type in its inferred result type ([String], Int). Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:26:6 ------------------------------------------------- 26 | val ss = s2.replace("a", "A") // warn | ^ - | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |value ss exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:27:6 ------------------------------------------------- 27 | val ss2 = Seq(s2.trim) // warn | ^ - |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly + |value ss2 exposes a flexible type in its inferred result type Seq[[String]]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:32:4 ------------------------------------------------- 32 |def f = s2.trim // warn | ^ - | method f exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |method f exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:36:4 ------------------------------------------------- 36 |def h = (s2.trim, s2.length) // warn | ^ - |method h exposes a flexible type in its inferred result type ((String)?, Int). Consider annotating the type explicitly + |method h exposes a flexible type in its inferred result type ([String], Int). Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:37:4 ------------------------------------------------- 37 |val ss = s2.replace("a", "A") // warn | ^ - | value ss exposes a flexible type in its inferred result type (String)?. Consider annotating the type explicitly + |value ss exposes a flexible type in its inferred result type [String]. Consider annotating the type explicitly -- Warning: tests/explicit-nulls/warn/expose-flexible-types.scala:38:4 ------------------------------------------------- 38 |val ss2 = Seq(s2.trim) // warn | ^ - |value ss2 exposes a flexible type in its inferred result type Seq[(String)?]. Consider annotating the type explicitly + |value ss2 exposes a flexible type in its inferred result type Seq[[String]]. Consider annotating the type explicitly