Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullness - option<> must be indicated as nullable in IL for C# consumers #17528

Merged
merged 13 commits into from
Aug 19, 2024
Merged
2 changes: 2 additions & 0 deletions src/Compiler/CodeGen/IlxGenSupport.fs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ let rec GetNullnessFromTType (g: TcGlobals) ty =
else if isValueType then
// Generic value type: 0, followed by the representation of the type arguments in order including containing types
yield NullnessInfo.AmbivalentToNull
else if IsUnionTypeWithNullAsTrueValue g tcref.Deref || TypeHasAllowNull tcref g FSharp.Compiler.Text.Range.Zero then
yield NullnessInfo.WithNull
else
// Reference type: the nullability (0, 1, or 2), followed by the representation of the type arguments in order including containing types
yield nullness.Evaluate()
Expand Down
10 changes: 6 additions & 4 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9185,16 +9185,18 @@ let reqTyForArgumentNullnessInference g actualTy reqTy =
changeWithNullReqTyToVariable g reqTy
| _ -> reqTy

let TypeHasAllowNull (tcref:TyconRef) g m =
not tcref.IsStructOrEnumTycon &&
not (isByrefLikeTyconRef g m tcref) &&
(TryFindTyconRefBoolAttribute g m g.attrib_AllowNullLiteralAttribute tcref = Some true)

/// The new logic about whether a type admits the use of 'null' as a value.
let TypeNullIsExtraValueNew g m ty =
let sty = stripTyparEqns ty

// Check if the type has AllowNullLiteral
(match tryTcrefOfAppTy g sty with
| ValueSome tcref ->
not tcref.IsStructOrEnumTycon &&
not (isByrefLikeTyconRef g m tcref) &&
(TryFindTyconRefBoolAttribute g m g.attrib_AllowNullLiteralAttribute tcref = Some true)
| ValueSome tcref -> TypeHasAllowNull tcref g m
| _ -> false)
||
// Check if the type has a nullness annotation
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,8 @@ val TypeNullIsTrueValue: TcGlobals -> TType -> bool

val TypeNullIsExtraValue: TcGlobals -> range -> TType -> bool

val TypeHasAllowNull: TyconRef -> TcGlobals -> range -> bool

val TypeNullIsExtraValueNew: TcGlobals -> range -> TType -> bool

val TypeNullNever: TcGlobals -> TType -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -928,4 +928,55 @@ let v3WithNull = f3 (null: obj | null)
Error 3261, Line 8, Col 14, Line 8, Col 20, "Nullness warning: The type ''a option' uses 'null' as a representation value but a non-null type is expected."
Error 3261, Line 10, Col 11, Line 10, Col 15, "Nullness warning: The type 'obj' does not support 'null'."
Error 3261, Line 11, Col 35, Line 11, Col 37, "Nullness warning: The type 'String | null' supports 'null' but a non-null type is expected."
Error 3261, Line 13, Col 22, Line 13, Col 38, "Nullness warning: The type 'obj | null' supports 'null' but a non-null type is expected."]
Error 3261, Line 13, Col 22, Line 13, Col 38, "Nullness warning: The type 'obj | null' supports 'null' but a non-null type is expected."]


[<FSharp.Test.FactForNETCOREAPPAttribute>]
let ``Option type detected as null for NullabilityInfoContext`` () =

Fsx """

open System
open System.Reflection

type MyClass(StringOrNull: string | null, StringOption: string option, ObjNull: objnull, ObjOrNull: obj | null) =
member val StringOrNull = StringOrNull
member val StringOption = StringOption
member val ObjNull = ObjNull
member val ObjOrNull = ObjOrNull

let classType = typeof<MyClass>
let ctor = classType.GetConstructors() |> Seq.exactlyOne
let paramInfos = ctor.GetParameters()


let nrtContext = NullabilityInfoContext()
for paramInfo in paramInfos do
let nrtInfo = nrtContext.Create(paramInfo)
let readState = nrtInfo.ReadState
let writeState = nrtInfo.WriteState
printfn $"{paramInfo.Name} => {readState} / {writeState}" """
|> withNullnessOptions
|> compile
|> run
|> verifyOutputContains [|"StringOption => Nullable / Nullable"|]

[<FSharp.Test.FactForNETCOREAPPAttribute>]
let ``Option type has WithNull annotation in IL`` () =
FSharp """
module Test

let myOption () : option<string> = None """
|> withNullnessOptions
|> compile
// The option<> itself is nullable (2), the string inside is not (1)
|> verifyIL ["
.method public static class [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1<string> myOption() cil managed
{
.param [0]
.custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 )

.maxstack 8
IL_0000: ldnull
IL_0001: ret
}"]
Loading