From 419bea0fb024674918fac42a04f7a64e5ea85237 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 13 Aug 2024 11:39:08 +0200 Subject: [PATCH 1/8] option IL test --- .../Nullness/NullableReferenceTypesTests.fs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index c355a6a244..dc7ec619a8 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -928,4 +928,45 @@ 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`` () = + + FSharp """module Test + +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 + |> verifyIL ["xxz"] \ No newline at end of file From 08c09285e5fe2b27ef254da786a4b558b6465926 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 13 Aug 2024 15:26:17 +0200 Subject: [PATCH 2/8] Specialcase IsUnionTypeWithNullAsTrueValue and TypeHasAllowNull in IlxGen --- src/Compiler/CodeGen/IlxGenSupport.fs | 2 ++ src/Compiler/TypedTree/TypedTreeOps.fs | 10 ++++++---- src/Compiler/TypedTree/TypedTreeOps.fsi | 2 ++ .../Nullness/NullableReferenceTypesTests.fs | 14 ++++++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Compiler/CodeGen/IlxGenSupport.fs b/src/Compiler/CodeGen/IlxGenSupport.fs index 4be01ced41..059f5a18ec 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fs +++ b/src/Compiler/CodeGen/IlxGenSupport.fs @@ -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() diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index be78209034..eb7818392c 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -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 diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 3a44513aa0..202a5feb84 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/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index dc7ec619a8..bf8980e398 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -934,7 +934,7 @@ let v3WithNull = f3 (null: obj | null) [] let ``Option type detected as null for NullabilityInfoContext`` () = - FSharp """module Test + Fsx """ open System open System.Reflection @@ -969,4 +969,14 @@ module Test let myOption () : option = None """ |> withNullnessOptions |> compile - |> verifyIL ["xxz"] \ No newline at end of file + // 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 From fed6628ae91901fcf6317716eacad89ff8f3a3ff Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 13 Aug 2024 16:49:54 +0200 Subject: [PATCH 3/8] failing C# consuming custom option test --- src/Compiler/CodeGen/IlxGenSupport.fs | 5 +++- .../NullAsTrueValue.fs.il.netcore.bsl | 8 ++++++ .../EmittedIL/Nullness/NullnessMetadata.fs | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Compiler/CodeGen/IlxGenSupport.fs b/src/Compiler/CodeGen/IlxGenSupport.fs index 059f5a18ec..36abb5ea23 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fs +++ b/src/Compiler/CodeGen/IlxGenSupport.fs @@ -407,7 +407,10 @@ 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 + 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 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 057fa51596..f8162e16d9 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 @@ -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 4e93000186..252ac31a96 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs @@ -112,6 +112,33 @@ module Interop = |> File.ReadAllText |> fsharpLibCreator + [] + let ``Csharp understands option like type using UseNullAsTrueValue`` () = + let csharpCode = """ +using System; +using static TestModule; +#nullable enable +public class C { + // MyNullableOption has [] applied on it + public void M(MyNullableOption customOption) { + + Console.WriteLine(customOption.ToString()); // should warn + + var thisIsNone = MyNullableOption.MyNone; + Console.WriteLine(thisIsNone.ToString()); // should 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 8600, Line 6, Col 35, Line 6, Col 57, "xxx"] + [] let ``Csharp understands Fsharp-produced struct unions via IsXXX flow analysis`` () = let csharpCode = """ From 4e61c8aad7c1969ed095ae33ce5648e2164aa665 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 15 Aug 2024 10:11:41 +0200 Subject: [PATCH 4/8] GetNullableContextAttribute with flag param --- src/Compiler/CodeGen/EraseUnions.fs | 2 +- src/Compiler/CodeGen/IlxGen.fs | 2 +- src/Compiler/CodeGen/IlxGenSupport.fs | 4 ++-- src/Compiler/CodeGen/IlxGenSupport.fsi | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index a42661855e..a0f07c4234 100644 --- a/src/Compiler/CodeGen/EraseUnions.fs +++ b/src/Compiler/CodeGen/EraseUnions.fs @@ -1199,7 +1199,7 @@ let convAlternativeDef let attrs = if g.checkNullness && g.langFeatureNullness then - GetNullableContextAttribute g :: debugAttrs + GetNullableContextAttribute g 1uy :: debugAttrs else debugAttrs diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index f182639ae1..67ef16f0a0 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 36abb5ea23..930aa662a6 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 diff --git a/src/Compiler/CodeGen/IlxGenSupport.fsi b/src/Compiler/CodeGen/IlxGenSupport.fsi index 6bd3723e88..5661eadd50 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 From 8e4a52e333b5abff4ed3450f3f8f4d1438c91230 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 15 Aug 2024 14:26:36 +0200 Subject: [PATCH 5/8] Handle generics in types that use null as true value (in IlxGen // EraseUnions) --- src/Compiler/CodeGen/EraseUnions.fs | 10 +++++++--- .../Nullness/NullAsTrueValue.fs.il.netcore.bsl | 8 ++++---- .../EmittedIL/Nullness/NullnessMetadata.fs | 16 +++++++++++----- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index a0f07c4234..0e61894526 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,10 @@ 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 @@ -1366,7 +1370,7 @@ let mkClassUnionDef | None -> existingAttrs |> Array.append - [| GetNullableAttribute g [ FSharp.Compiler.TypedTree.NullnessInfo.WithNull ] |] + [| GetNullableAttribute g [ NullnessInfo.WithNull ] |] | Some idx -> let replacementAttr = match existingAttrs[idx] with @@ -1619,7 +1623,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 else td.CustomAttrs 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 f8162e16d9..b0b12a2349 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 ) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs index 252ac31a96..3476cf07b2 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs @@ -117,15 +117,22 @@ module Interop = 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 warn + Console.WriteLine(customOption.ToString()); // should not warn - var thisIsNone = MyNullableOption.MyNone; - Console.WriteLine(thisIsNone.ToString()); // should 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 @@ -136,8 +143,7 @@ public class C { }""" csharpCode |> csharpLibCompile (FsharpFromFile "NullAsTrueValue.fs") - |> withDiagnostics [ - Warning 8600, Line 6, Col 35, Line 6, Col 57, "xxx"] + |> 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`` () = From e442e9c3439f3c51a124e92340701f67fc3bc4ab Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 15 Aug 2024 14:27:33 +0200 Subject: [PATCH 6/8] fantomas'd --- src/Compiler/CodeGen/EraseUnions.fs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index 0e61894526..ee52010a02 100644 --- a/src/Compiler/CodeGen/EraseUnions.fs +++ b/src/Compiler/CodeGen/EraseUnions.fs @@ -956,10 +956,14 @@ let convAlternativeDef && g.langFeatureNullness && repr.RepresentAlternativeAsNull(info, alt) then - 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 + 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 @@ -1369,8 +1373,7 @@ let mkClassUnionDef match nullableIdx with | None -> existingAttrs - |> Array.append - [| GetNullableAttribute g [ NullnessInfo.WithNull ] |] + |> Array.append [| GetNullableAttribute g [ NullnessInfo.WithNull ] |] | Some idx -> let replacementAttr = match existingAttrs[idx] with From 9aab1373f04858bbb1dfcb18404fecb39ee7c5a4 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 15 Aug 2024 14:30:35 +0200 Subject: [PATCH 7/8] notes --- docs/release-notes/.FSharp.Compiler.Service/9.0.100.md | 1 + 1 file changed, 1 insertion(+) 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 a11ec24aba..364deffb5c 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) ### Added From bccc16107c3dc10b20c8726d2af97aaa1ad39de5 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 16 Aug 2024 11:00:02 +0200 Subject: [PATCH 8/8] net472 il tests --- .../Nullness/NullAsTrueValue.fs.il.net472.bsl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 f4977bfcd8..92b5d69456 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,