@@ -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,38 @@ 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 savedSkipResultType = skipResultType
210+ val savedSkipCurrentLevel = skipCurrentLevel
211+ val savedExplicitlyNullable = explicitlyNullable
212+ if (ctx.definitions.NullableAnnots .exists(ann => tp.hasAnnotation(ann))) {
213+ explicitlyNullable = true
214+ skipCurrentLevel = false
215+ }
216+
217+ if (ctx.definitions.NotNullAnnots .exists(ann => tp.hasAnnotation(ann))) {
218+ skipResultType = true
219+ skipCurrentLevel = false
220+ }
221+ val resType = this (tp.underlying)
222+ explicitlyNullable = savedExplicitlyNullable
223+ skipCurrentLevel = savedSkipCurrentLevel
224+ skipResultType = savedSkipResultType
225+ derivedAnnotatedType(tp, resType, tp.annot)
193226 case tp : RefinedType =>
194227 val savedSkipCurrentLevel = skipCurrentLevel
195228 val savedSkipResultType = skipResultType
229+ val savedExplicitlyNullable = explicitlyNullable
196230
197231 val parent2 = this (tp.parent)
198232
199233 skipCurrentLevel = false
200234 skipResultType = false
235+ explicitlyNullable = false
201236 val refinedInfo2 = this (tp.refinedInfo)
202237
203238 skipCurrentLevel = savedSkipCurrentLevel
204239 skipResultType = savedSkipResultType
240+ explicitlyNullable = savedExplicitlyNullable
205241
206242 parent2 match
207243 case FlexibleType (_, parent2a) if ctx.flexibleTypes =>
@@ -217,5 +253,6 @@ object ImplicitNullInterop:
217253 // complex computed types such as match types here; those remain as-is to avoid forcing
218254 // incomplete information during symbol construction.
219255 tp
256+ }
220257 end apply
221258 end ImplicitNullMap
0 commit comments