@@ -76,16 +76,22 @@ object ImplicitNullInterop:
7676 val skipResultType = sym.isConstructor || hasNotNullAnnot(sym)
7777 // Don't nullify Given/implicit parameters
7878 val skipCurrentLevel = sym.isOneOf(GivenOrImplicitVal )
79+ // Use OrNull instead of flexible types if symbol is explicitly nullable
80+ val explicitlyNullable = hasNullableAnnot(sym)
7981
8082 val map = new ImplicitNullMap (
8183 javaDefined = sym.is(JavaDefined ),
8284 skipResultType = skipResultType,
83- skipCurrentLevel = skipCurrentLevel)
85+ skipCurrentLevel = skipCurrentLevel,
86+ explicitlyNullable = explicitlyNullable)
8487 map(tp)
8588
8689 private def hasNotNullAnnot (sym : Symbol )(using Context ): Boolean =
8790 ctx.definitions.NotNullAnnots .exists(nna => sym.unforcedAnnotation(nna).isDefined)
8891
92+ private def hasNullableAnnot (sym : Symbol )(using Context ): Boolean =
93+ ctx.definitions.NullableAnnots .exists(nna => sym.unforcedAnnotation(nna).isDefined)
94+
8995 /** A type map that implements the nullification function on types. Given a Java-sourced type or a type
9096 * coming from Scala code compiled without explicit nulls, this adds `| Null` or `FlexibleType` in the
9197 * right places to make nullability explicit in a conservative way (without forcing incomplete symbols).
@@ -98,18 +104,23 @@ object ImplicitNullInterop:
98104 private class ImplicitNullMap (
99105 val javaDefined : Boolean ,
100106 var skipResultType : Boolean = false ,
101- var skipCurrentLevel : Boolean = false
107+ var skipCurrentLevel : Boolean = false ,
108+ var explicitlyNullable : Boolean = false
102109 )(using Context ) extends TypeMap :
103110
104- def nullify (tp : Type ): Type = if ctx.flexibleTypes then FlexibleType (tp) else OrNull (tp)
111+ def nullify (tp : Type ): Type =
112+ if ctx.flexibleTypes && ! explicitlyNullable then
113+ FlexibleType (tp)
114+ else
115+ OrNull (tp)
105116
106117 /** Should we nullify `tp` at the outermost level?
107118 * The symbols are still under construction, so we don't have precise information.
108119 * We purposely do not rely on precise subtyping checks here (e.g., asking whether `tp <:< AnyRef`),
109120 * because doing so could force incomplete symbols or trigger cycles. Instead, we conservatively
110121 * nullify only when we can recognize a concrete reference type or type parameters from Java.
111122 */
112- def needsNull (tp : Type ): Boolean =
123+ def needsNull (tp : Type ): Boolean = trace( i " needsNull ${tp} " ) :
113124 if skipCurrentLevel || ! tp.hasSimpleKind then false
114125 else tp.dealias match
115126 case tp : TypeRef =>
@@ -140,30 +151,36 @@ object ImplicitNullInterop:
140151 case tp : TypeRef if defn.isTupleClass(tp.symbol) => false
141152 case _ => true
142153
143- override def apply (tp : Type ): Type = tp match
154+ override def apply (tp : Type ): Type = trace( i " apply $tp " ){ tp match
144155 case tp : TypeRef if needsNull(tp) =>
145156 nullify(tp)
146157 case tp : TypeParamRef if needsNull(tp) =>
147158 nullify(tp)
148159 case appTp @ AppliedType (tycon, targs) =>
149160 val savedSkipCurrentLevel = skipCurrentLevel
161+ val savedExplicitlyNullable = explicitlyNullable
150162
151163 // If Java-defined tycon, don't nullify outer level of type args (Java classes are fully nullified)
152164 skipCurrentLevel = tp.classSymbol.is(JavaDefined )
165+ explicitlyNullable = false
153166 val targs2 = targs.map(this )
154167
155168 skipCurrentLevel = savedSkipCurrentLevel
169+ explicitlyNullable = savedExplicitlyNullable
156170 val appTp2 = derivedAppliedType(appTp, tycon, targs2)
157171 if tyconNeedsNull(tycon) && tp.hasSimpleKind then nullify(appTp2) else appTp2
158172 case ptp : PolyType =>
159173 derivedLambdaType(ptp)(ptp.paramInfos, this (ptp.resType))
160174 case mtp : MethodType =>
161175 val savedSkipCurrentLevel = skipCurrentLevel
176+ val savedExplicitlyNullable = explicitlyNullable
162177
163178 // Don't nullify param types for implicit/using sections
164179 skipCurrentLevel = mtp.isImplicitMethod
180+ explicitlyNullable = false
165181 val paramInfos2 = mtp.paramInfos.map(this )
166182
183+ explicitlyNullable = savedExplicitlyNullable
167184 skipCurrentLevel = skipResultType
168185 val resType2 = this (mtp.resType)
169186
@@ -189,19 +206,32 @@ object ImplicitNullInterop:
189206 mapOver(tp)
190207 case tp : AnnotatedType =>
191208 // We don't nullify the annotation part.
192- derivedAnnotatedType(tp, this (tp.underlying), tp.annot)
209+ val savedSkipCurrentLevel = skipCurrentLevel
210+ val savedExplicitlyNullable = explicitlyNullable
211+ if (ctx.definitions.NullableAnnots .exists(ann => tp.hasAnnotation(ann))) {
212+ explicitlyNullable = true
213+ skipCurrentLevel = false
214+ }
215+ val resType = this (tp.underlying)
216+ explicitlyNullable = savedExplicitlyNullable
217+ skipCurrentLevel = savedSkipCurrentLevel
218+
219+ derivedAnnotatedType(tp, resType, tp.annot)
193220 case tp : RefinedType =>
194221 val savedSkipCurrentLevel = skipCurrentLevel
195222 val savedSkipResultType = skipResultType
223+ val savedExplicitlyNullable = explicitlyNullable
196224
197225 val parent2 = this (tp.parent)
198226
199227 skipCurrentLevel = false
200228 skipResultType = false
229+ explicitlyNullable = false
201230 val refinedInfo2 = this (tp.refinedInfo)
202231
203232 skipCurrentLevel = savedSkipCurrentLevel
204233 skipResultType = savedSkipResultType
234+ explicitlyNullable = savedExplicitlyNullable
205235
206236 parent2 match
207237 case FlexibleType (_, parent2a) if ctx.flexibleTypes =>
@@ -217,5 +247,6 @@ object ImplicitNullInterop:
217247 // complex computed types such as match types here; those remain as-is to avoid forcing
218248 // incomplete information during symbol construction.
219249 tp
250+ }
220251 end apply
221252 end ImplicitNullMap
0 commit comments