From d90885c9cf9e35bbc20f399cd37bc77b260206eb Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 19 Aug 2024 17:17:34 +0200 Subject: [PATCH] Nullness - option<> must be indicated as nullable in IL for C# consumers (#17528) --- .../.FSharp.Compiler.Service/9.0.100.md | 1 + src/Compiler/CodeGen/EraseUnions.fs | 17 ++++-- src/Compiler/CodeGen/IlxGen.fs | 2 +- src/Compiler/CodeGen/IlxGenSupport.fs | 9 +++- src/Compiler/CodeGen/IlxGenSupport.fsi | 2 +- src/Compiler/TypedTree/TypedTreeOps.fs | 10 ++-- src/Compiler/TypedTree/TypedTreeOps.fsi | 2 + .../Nullness/NullAsTrueValue.fs.il.net472.bsl | 16 ++++-- .../NullAsTrueValue.fs.il.netcore.bsl | 16 ++++-- .../EmittedIL/Nullness/NullnessMetadata.fs | 33 ++++++++++++ .../Nullness/NullableReferenceTypesTests.fs | 53 ++++++++++++++++++- 11 files changed, 139 insertions(+), 22 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md index 3d4288458c9..2923079fb88 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md @@ -8,6 +8,7 @@ * Fix `function` implicit conversion. ([Issue #7401](https://github.com/dotnet/fsharp/issues/7401), [PR #17487](https://github.com/dotnet/fsharp/pull/17487)) * Compiler fails to recognise namespace in FQN with enabled GraphBasedChecking. ([Issue #17508](https://github.com/dotnet/fsharp/issues/17508), [PR #17510](https://github.com/dotnet/fsharp/pull/17510)) * Fix missing message for type error (FS0001). ([Issue #17373](https://github.com/dotnet/fsharp/issues/17373), [PR #17516](https://github.com/dotnet/fsharp/pull/17516)) +* Nullness export - make sure option<> and other UseNullAsTrueValue types are properly annotated as nullable for C# and reflection consumers [PR #17528](https://github.com/dotnet/fsharp/pull/17528) * MethodAccessException on equality comparison of a type private to module. ([Issue #17541](https://github.com/dotnet/fsharp/issues/17541), [PR #17548](https://github.com/dotnet/fsharp/pull/17548)) ### Added diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index 7db7166e0f1..88336c057b2 100644 --- a/src/Compiler/CodeGen/EraseUnions.fs +++ b/src/Compiler/CodeGen/EraseUnions.fs @@ -8,6 +8,7 @@ open FSharp.Compiler.IlxGenSupport open System.Collections.Generic open System.Reflection open Internal.Utilities.Library +open FSharp.Compiler.TypedTree open FSharp.Compiler.TypedTreeOps open FSharp.Compiler.Features open FSharp.Compiler.TcGlobals @@ -955,7 +956,14 @@ let convAlternativeDef && g.langFeatureNullness && repr.RepresentAlternativeAsNull(info, alt) then - GetNullableAttribute g [ FSharp.Compiler.TypedTree.NullnessInfo.WithNull ] + let noTypars = td.GenericParams.Length + + GetNullableAttribute + g + [ + yield NullnessInfo.WithNull // The top-level value itself, e.g. option, is nullable + yield! List.replicate noTypars NullnessInfo.AmbivalentToNull + ] // The typars are not (i.e. do not change option into option |> Array.singleton |> mkILCustomAttrsFromArray else @@ -1199,7 +1207,7 @@ let convAlternativeDef let attrs = if g.checkNullness && g.langFeatureNullness then - GetNullableContextAttribute g :: debugAttrs + GetNullableContextAttribute g 1uy :: debugAttrs else debugAttrs @@ -1365,8 +1373,7 @@ let mkClassUnionDef match nullableIdx with | None -> existingAttrs - |> Array.append - [| GetNullableAttribute g [ FSharp.Compiler.TypedTree.NullnessInfo.WithNull ] |] + |> Array.append [| GetNullableAttribute g [ NullnessInfo.WithNull ] |] | Some idx -> let replacementAttr = match existingAttrs[idx] with @@ -1619,7 +1626,7 @@ let mkClassUnionDef customAttrs = if cud.IsNullPermitted && g.checkNullness && g.langFeatureNullness then td.CustomAttrs.AsArray() - |> Array.append [| GetNullableAttribute g [ FSharp.Compiler.TypedTree.NullnessInfo.WithNull ] |] + |> Array.append [| GetNullableAttribute g [ NullnessInfo.WithNull ] |] |> mkILCustomAttrsFromArray |> storeILCustomAttrs else diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 26ec0dfcff7..52fec82de43 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1912,7 +1912,7 @@ type TypeDefBuilder(tdef: ILTypeDef, tdefDiscards) = if attrsBefore |> TryFindILAttribute g.attrib_AllowNullLiteralAttribute then yield GetNullableAttribute g [ NullnessInfo.WithNull ] if (gmethods.Count + gfields.Count + gproperties.Count) > 0 then - yield GetNullableContextAttribute g + yield GetNullableContextAttribute g 1uy |] |> mkILCustomAttrsFromArray else diff --git a/src/Compiler/CodeGen/IlxGenSupport.fs b/src/Compiler/CodeGen/IlxGenSupport.fs index 4be01ced411..930aa662a6c 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fs +++ b/src/Compiler/CodeGen/IlxGenSupport.fs @@ -319,7 +319,7 @@ let GetDynamicDependencyAttribute (g: TcGlobals) memberTypes (ilType: ILType) = /// Nested items not being annotated with Nullable attribute themselves are interpreted as being withoutnull /// Doing it that way is a heuristical decision supporting limited usage of (| null) annotations and not allowing nulls in >50% of F# code /// (if majority of fields/parameters/return values would be nullable, this heuristic would lead to bloat of generated metadata) -let GetNullableContextAttribute (g: TcGlobals) = +let GetNullableContextAttribute (g: TcGlobals) flagValue = let tref = g.attrib_NullableContextAttribute.TypeRef g.TryEmbedILType( @@ -329,7 +329,7 @@ let GetNullableContextAttribute (g: TcGlobals) = mkLocalPrivateAttributeWithPropertyConstructors (g, tref.Name, fields, PublicFields)) ) - mkILCustomAttribute (tref, [ g.ilg.typ_Byte ], [ ILAttribElem.Byte 1uy ], []) + mkILCustomAttribute (tref, [ g.ilg.typ_Byte ], [ ILAttribElem.Byte flagValue ], []) let GetNotNullWhenTrueAttribute (g: TcGlobals) (propNames: string array) = let tref = g.attrib_MemberNotNullWhenAttribute.TypeRef @@ -407,6 +407,11 @@ 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() diff --git a/src/Compiler/CodeGen/IlxGenSupport.fsi b/src/Compiler/CodeGen/IlxGenSupport.fsi index 6bd3723e886..5661eadd502 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fsi +++ b/src/Compiler/CodeGen/IlxGenSupport.fsi @@ -23,5 +23,5 @@ val GenAdditionalAttributesForTy: g: TcGlobals -> ty: TypedTree.TType -> ILAttri val GetReadOnlyAttribute: g: TcGlobals -> ILAttribute val GetIsUnmanagedAttribute: g: TcGlobals -> ILAttribute val GetNullableAttribute: g: TcGlobals -> nullnessInfos: TypedTree.NullnessInfo list -> ILAttribute -val GetNullableContextAttribute: g: TcGlobals -> ILAttribute +val GetNullableContextAttribute: g: TcGlobals -> byte -> ILAttribute val GetNotNullWhenTrueAttribute: g: TcGlobals -> string array -> ILAttribute diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 9eda4a9c2de..df60557fd6d 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -9198,16 +9198,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 diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 3a44513aa0f..202a5feb848 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -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 diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.net472.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.net472.bsl index f4977bfcd8d..92b5d69456e 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.net472.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.net472.bsl @@ -73,7 +73,7 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, int32) = ( 01 00 08 00 00 00 00 00 00 00 00 00 ) .param [0] - .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .maxstack 8 IL_0000: ldnull @@ -188,7 +188,7 @@ .property class TestModule/MyNullableOption`1 MyNone() { - .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) @@ -266,7 +266,7 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, int32) = ( 01 00 08 00 00 00 00 00 00 00 00 00 ) .param [0] - .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .maxstack 8 IL_0000: ldnull @@ -382,7 +382,7 @@ .property class TestModule/MyOptionWhichCannotHaveNullInTheInside`1 MyNotNullNone() { - .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) @@ -422,6 +422,10 @@ .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) .param type b .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .param [0] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) .maxstack 4 .locals init (class TestModule/MyNullableOption`1 V_0, @@ -458,6 +462,10 @@ .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) .param type b .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .param [0] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) .maxstack 4 .locals init (class TestModule/MyOptionWhichCannotHaveNullInTheInside`1 V_0, diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.netcore.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.netcore.bsl index 057fa515964..b0b12a23494 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.netcore.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullAsTrueValue.fs.il.netcore.bsl @@ -73,7 +73,7 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, int32) = ( 01 00 08 00 00 00 00 00 00 00 00 00 ) .param [0] - .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .maxstack 8 IL_0000: ldnull @@ -188,7 +188,7 @@ .property class TestModule/MyNullableOption`1 MyNone() { - .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) @@ -266,7 +266,7 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, int32) = ( 01 00 08 00 00 00 00 00 00 00 00 00 ) .param [0] - .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .maxstack 8 IL_0000: ldnull @@ -382,7 +382,7 @@ .property class TestModule/MyOptionWhichCannotHaveNullInTheInside`1 MyNotNullNone() { - .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 00 00 00 ) .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) @@ -422,6 +422,10 @@ .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) .param type b .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .param [0] + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + .param [2] + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) .maxstack 4 .locals init (class TestModule/MyNullableOption`1 V_0, @@ -458,6 +462,10 @@ .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) .param type b .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .param [0] + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + .param [2] + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) .maxstack 4 .locals init (class TestModule/MyOptionWhichCannotHaveNullInTheInside`1 V_0, diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs index 4e93000186c..3476cf07b2e 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs @@ -112,6 +112,39 @@ module Interop = |> File.ReadAllText |> fsharpLibCreator + [] + let ``Csharp understands option like type using UseNullAsTrueValue`` () = + let csharpCode = """ +using System; +using static TestModule; +using static Microsoft.FSharp.Core.FuncConvert; +#nullable enable +public class C { + // MyNullableOption has [] applied on it + public void M(MyNullableOption customOption) { + + Console.WriteLine(customOption.ToString()); // should not warn + + var thisIsNone = MyNullableOption.MyNone; + Console.WriteLine(thisIsNone.ToString()); // !! should warn !! + + var mapped = mapPossiblyNullable(ToFSharpFunc(x => x.ToString()), customOption); // should not warn, because null will not be passed + var mapped2 = mapPossiblyNullable(ToFSharpFunc(x => x + ".."), thisIsNone); // should NOT warn for passing in none, this is allowed + + if(thisIsNone != null) + Console.WriteLine(thisIsNone.ToString()); // should NOT warn + + if(customOption != null) + Console.WriteLine(customOption.ToString()); // should NOT warn + + Console.WriteLine(MyOptionWhichCannotHaveNullInTheInside.NewMyNotNullSome("").ToString()); // should NOT warn + + } +}""" + csharpCode + |> csharpLibCompile (FsharpFromFile "NullAsTrueValue.fs") + |> withDiagnostics [ Warning 8602, Line 12, Col 27, Line 12, Col 37, "Dereference of a possibly null reference."] + [] let ``Csharp understands Fsharp-produced struct unions via IsXXX flow analysis`` () = let csharpCode = """ diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index c355a6a244f..bf8980e3986 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -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."] \ No newline at end of file + 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."] + + +[] +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 +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"|] + +[] +let ``Option type has WithNull annotation in IL`` () = + FSharp """ +module Test + +let myOption () : option = 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 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 + }"] \ No newline at end of file