Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bd470eb
Refactor FlexibleType to use TypeBounds instead of case class
HarrisL2 Jun 5, 2026
203fa7c
Fix Implicits and Quotes
HarrisL2 Jun 5, 2026
8991943
Remove extra import and fix pickling issue
HarrisL2 Jun 8, 2026
8ecc72c
Fix Printing for FlexibleTypes
HarrisL2 Jun 9, 2026
e6d8f55
Fix issues from tests
HarrisL2 Jun 9, 2026
653bb81
Fix Open Community Build issue
HarrisL2 Jun 12, 2026
aa7217d
Fix more Open Community Build issue
HarrisL2 Jun 13, 2026
f822afb
reduce scope of special-casing FlexibleType in TypeComparer
olhotak Sep 1, 2026
f24e397
further restrict FlexibleType handling to type inference
olhotak Sep 2, 2026
0baaab5
remove some redundant FlexibleType special-casing
olhotak Sep 2, 2026
5e7c20d
remove commented code, narrow type of unapply
olhotak Sep 8, 2026
6a06a7d
make quotes-level FlexibleType.unapply refutable
olhotak Sep 9, 2026
fc24930
Enable explicit-nulls and unsafeNulls by default
HarrisL2 Mar 9, 2026
b26deb4
Disable CC in library files for now
HarrisL2 Mar 13, 2026
458587d
Add safeNulls language option and no-explicit-nulls flag
HarrisL2 Mar 14, 2026
3c797f8
Adjust build and test flags
HarrisL2 Mar 15, 2026
804e06b
Fix issues from tests
HarrisL2 Mar 16, 2026
f29edb0
Improve presentation compiler fixes
HarrisL2 Mar 26, 2026
e7e3d0d
Revert CC changes
HarrisL2 Mar 27, 2026
c01ea34
Improve scaladoc fix
HarrisL2 Mar 27, 2026
bcf308e
Rebase Changes
HarrisL2 May 28, 2026
9010066
Disable repl tab complete test
HarrisL2 May 29, 2026
4a797d1
fix tests and checkfiles
olhotak Sep 3, 2026
cfdf57a
fix more tests
olhotak Sep 7, 2026
9f5d78c
Merge branch 'pr/26985' into merge-26985-27004
olhotak Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions community-build/src/scala/dotty/communitybuild/projects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ object SbtCommunityProject:
def scalacOptions = List(
"-Xcheck-macros",
"-Wsafe-init",
"-Yexplicit-nulls",
"-language:unsafeNulls",
)

object projects:
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
.setTyper(new Typer)
.addMode(Mode.ImplicitsEnabled)
.setTyperState(ctx.typerState.fresh(ctx.reporter))
if ctx.settings.YexplicitNulls.value && !Feature.enabledBySetting(nme.unsafeNulls) then
if ctx.settings.YexplicitNulls.value || Feature.enabledBySetting(nme.safeNulls) then
start = start.addMode(Mode.SafeNulls)
ctx.initialize()(using start) // re-initialize the base context with start

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1995,8 +1995,8 @@ class CheckCaptures extends Recheck, SymTransformer:
case _ =>

actual match
case actual: FlexibleType =>
return actual.derivedFlexibleType(recur(actual.hi, expected, covariant))
case actual @ FlexibleType(hi) =>
return FlexibleType.derivedFlexibleType(actual, recur(hi, expected, covariant))
case _ =>

// Decompose the actual type into the inner shape type, the capture set and the box status
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object Feature:
(nme.noAutoTupling, "Disable automatic tupling"),
(nme.dynamics, "Allow direct or indirect subclasses of scala.Dynamic"),
(nme.unsafeNulls, "Enable unsafe nulls for explicit nulls"),
(nme.safeNulls, "Enable safe nulls for explicit nulls"),
(nme.postfixOps, "Allow postfix operators (not recommended)"),
(nme.strictEquality, "Enable strict equality (disable canEqualAny)"),
(nme.implicitConversions, "Allow implicit conversions without warnings"),
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,8 @@ private sealed trait YSettings:
val YmagicOffsetHeader: Setting[String] = StringSetting(ForkSetting, "Ymagic-offset-header", "header", "Specify the magic header comment that marks the start of the actual code in generated wrapper scripts. Example: -Ymagic-offset-header:SOURCE_CODE_START. Then, in the source, the magic comment `///SOURCE_CODE_START:<ORIGINAL_FILE_PATH>` marks the start of user code. The comment should be suffixed by `:<ORIGINAL_FILE_PATH>` to indicate the original file.", "")

// Experimental language features
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 YexplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplicit-nulls", "Since explicit nulls is enabled by default, this flag now enables safe nulls for explicit-nulls")
val YnoExplicitNulls: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-explicit-nulls", "Make reference types implictly nullable.")
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.")
Expand All @@ -577,6 +578,7 @@ private sealed trait YSettings:
val YexplainLowlevel: Setting[Boolean] = BooleanSetting(ForkSetting, "Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
val YnoDoubleBindings: Setting[Boolean] = BooleanSetting(ForkSetting, "Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")
val YshowVarBounds: Setting[Boolean] = BooleanSetting(ForkSetting, "Yshow-var-bounds", "Print type variables with their bounds.")
val YhideFlexibleTypes: Setting[Boolean] = BooleanSetting(ForkSetting, "Yhide-flexible-types", "Print flexible types as their base type. (T instead of (T)?)")

val Yinstrument: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument", "Add instrumentation code that counts allocations and closure creations.")
val YinstrumentDefs: Setting[Boolean] = BooleanSetting(ForkSetting, "Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
Expand Down
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -720,8 +720,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)
Expand Down Expand Up @@ -832,6 +832,14 @@ trait ConstraintHandling {
*/
protected def addConstraint(param: TypeParamRef, bound: Type, fromBelow: Boolean)(using Context): Boolean =
if !bound.isValueTypeOrLambda then return false
// Never infer the `<FlexibleType>` type constructor for a higher-kinded type
// parameter. A flexible type is an implementation device of explicit nulls that
// should be transparent to type inference; if we allowed `param := <FlexibleType>`,
// every `F[A]`-shaped signature would match a flexible-typed value, shadowing the
// members of its underlying type (see tests/explicit-nulls/pos/flexible-hk-extension.scala).
// Refusing the constraint makes the comparison fall back to looking through the
// flexible type.
if FlexibleType.isTypeConstructor(bound) then return false

/** When comparing lambdas we might get constraints such as
* `A <: X0` or `A = List[X0]` where `A` is a constrained parameter
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -489,13 +489,13 @@ object Contexts {
fresh.setSetting(ctx.settings.color, "never")

/** Is the explicit nulls option set? */
def explicitNulls: Boolean = base.settings.YexplicitNulls.value
def explicitNulls: Boolean = !base.settings.YnoExplicitNulls.value

/** Is the flexible types option set? */
def flexibleTypes: Boolean = base.settings.YexplicitNulls.value && !base.settings.YnoFlexibleTypes.value
def flexibleTypes: Boolean = explicitNulls && !base.settings.YnoFlexibleTypes.value

/** Is the flexify tasty option set? */
def flexifyTasty: Boolean = base.settings.YexplicitNulls.value && base.settings.YflexifyTasty.value
def flexifyTasty: Boolean = explicitNulls && base.settings.YflexifyTasty.value

/** Is the best-effort option set? */
def isBestEffort: Boolean = base.settings.YbestEffort.value
Expand Down Expand Up @@ -771,7 +771,9 @@ object Contexts {
importInfo.mentionsFeature(nme.unsafeNulls) match
case Some(true) =>
setMode(this.mode &~ Mode.SafeNulls)
case Some(false) if ctx.settings.YexplicitNulls.value =>
case _ =>
importInfo.mentionsFeature(nme.safeNulls) match
case Some(true) if explicitNulls =>
setMode(this.mode | Mode.SafeNulls)
case _ =>
updateStore(importInfoLoc, importInfo)
Expand Down
15 changes: 13 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,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

Expand Down Expand Up @@ -2022,8 +2033,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
Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/core/ImplicitNullInterop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,18 @@ 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))
case _ =>
derivedAnnotatedType(tp, parent2, tp.annot)
case FlexibleType(_) =>
// A flexible type is already a fully-nullified result, so nullification must be
// idempotent on it: leave it unchanged. Without this case it would match the
// `AppliedType` case below (a flexible type is encoded as `AppliedType(FlexibleType, T)`)
// and get wrapped a second time, e.g. `(Array[T])?` becoming `((Array[T])?)?`.
tp
case appTp @ AppliedType(tycon, targs) =>
val savedState = state
// If Java-defined tycon, don't nullify outer level of type args (Java classes are fully nullified)
Expand Down Expand Up @@ -236,7 +242,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))
Expand All @@ -256,7 +262,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))
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/NullOpsDecorator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,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 _ =>
Expand Down Expand Up @@ -754,7 +754,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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ object StdNames {
final val NotNull: N = "NotNull"
final val Null: N = "Null"
final val Object: N = "Object"
final val FlexibleType : N = "<FlexibleType>"
final val FromJavaObject: N = "<FromJavaObject>"
final val Record: N = "Record"
final val Product: N = "Product"
Expand Down Expand Up @@ -667,6 +668,7 @@ object StdNames {
val unbox: N = "unbox"
val universe: N = "universe"
val unsafeNulls: N = "unsafeNulls"
val safeNulls: N = "safeNulls"
val update: N = "update"
val updateDynamic: N = "updateDynamic"
val uses: N = "uses"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,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.
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -950,8 +950,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
false
}
compareClassInfo
case tp2: FlexibleType =>
recur(tp1, tp2.lo)
case _ =>
fourthTry
}
Expand Down Expand Up @@ -1160,8 +1158,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
}
Expand Down
Loading
Loading