diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 328b3510c38..d1879119973 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -8462,8 +8462,10 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg | ApplicableExpr(expr=Expr.Val (valRef=vref)) | ApplicableExpr(expr=Expr.App (funcExpr=Expr.Val (valRef=vref))) -> match TryFindLocalizedFSharpStringAttribute g g.attrib_WarnOnWithoutNullArgumentAttribute vref.Attribs with - | Some _ as msg -> env,{ cenv with css.WarnWhenUsingWithoutNullOnAWithNullTarget = msg} - | None -> env,cenv + | Some _ as msg -> env,{ cenv with css.WarnWhenUsingWithoutNullOnAWithNullTarget = msg} + | None when cenv.css.WarnWhenUsingWithoutNullOnAWithNullTarget <> None -> + env, { cenv with css.WarnWhenUsingWithoutNullOnAWithNullTarget = None} + | None -> env,cenv | _ -> env,cenv TcExprFlex2 cenv domainTy env false tpenv synArg, cenv @@ -10531,7 +10533,9 @@ and TcMatchClause cenv inputTy (resultTy: OverallTy) env isFirst tpenv synMatchC let target = TTarget(vspecs, resultExpr, None) let inputTypeForNextPatterns= - let removeNull t = replaceNullnessOfTy KnownWithoutNull t + let removeNull t = + let stripped = stripTyEqns cenv.g t + replaceNullnessOfTy KnownWithoutNull stripped let rec isWild (p:Pattern) = match p with | TPat_wild _ -> true diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index a5b123ac937..37bb43a28cd 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1021,6 +1021,11 @@ and SolveTypMeetsTyparConstraints (csenv: ConstraintSolverEnv) ndeep m2 trace ty SolveMemberConstraint csenv false PermitWeakResolution.No ndeep m2 trace traitInfo |> OperationResult.ignore } +and shouldWarnUselessNullCheck (csenv:ConstraintSolverEnv) = + csenv.g.checkNullness && + csenv.IsSpeculativeForMethodOverloading = false && + csenv.SolverState.WarnWhenUsingWithoutNullOnAWithNullTarget.IsSome + // nullness1: actual // nullness2: expected and SolveNullnessEquiv (csenv: ConstraintSolverEnv) m2 (trace: OptionalTrace) ty1 ty2 nullness1 nullness2 = @@ -1044,7 +1049,7 @@ and SolveNullnessEquiv (csenv: ConstraintSolverEnv) m2 (trace: OptionalTrace) ty | NullnessInfo.WithNull, NullnessInfo.WithNull -> CompleteD | NullnessInfo.WithoutNull, NullnessInfo.WithoutNull -> CompleteD // Warn for 'strict "must pass null"` APIs like Option.ofObj - | NullnessInfo.WithNull, NullnessInfo.WithoutNull when csenv.g.checkNullness && csenv.SolverState.WarnWhenUsingWithoutNullOnAWithNullTarget.IsSome -> + | NullnessInfo.WithNull, NullnessInfo.WithoutNull when shouldWarnUselessNullCheck csenv -> WarnD(Error(FSComp.SR.tcPassingWithoutNullToANullableExpectingFunc (csenv.SolverState.WarnWhenUsingWithoutNullOnAWithNullTarget.Value),m2)) // Allow expected of WithNull and actual of WithoutNull // TODO NULLNESS: this is not sound in contravariant cases etc. It is assuming covariance. @@ -1078,7 +1083,7 @@ and SolveNullnessSubsumesNullness (csenv: ConstraintSolverEnv) m2 (trace: Option | NullnessInfo.WithNull, NullnessInfo.WithNull -> CompleteD | NullnessInfo.WithoutNull, NullnessInfo.WithoutNull -> CompleteD // Warn for 'strict "must pass null"` APIs like Option.ofObj - | NullnessInfo.WithNull, NullnessInfo.WithoutNull when csenv.g.checkNullness && csenv.SolverState.WarnWhenUsingWithoutNullOnAWithNullTarget.IsSome -> + | NullnessInfo.WithNull, NullnessInfo.WithoutNull when shouldWarnUselessNullCheck csenv -> WarnD(Error(FSComp.SR.tcPassingWithoutNullToANullableExpectingFunc (csenv.SolverState.WarnWhenUsingWithoutNullOnAWithNullTarget.Value),m2)) // Allow target of WithNull and actual of WithoutNull | NullnessInfo.WithNull, NullnessInfo.WithoutNull -> @@ -1227,6 +1232,12 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr // Unifying 'T1? and 'T2? | ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithNull -> SolveTyparEqualsType csenv ndeep m2 trace sty1 (TType_var (tp2, g.knownWithoutNull)) + | ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithoutNull -> + let tpNew = NewCompGenTypar(TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false) + trackErrors { + do! SolveTypeEqualsType csenv ndeep m2 trace cxsln sty2 (TType_var(tpNew, g.knownWithNull)) + do! SolveTypeEqualsType csenv ndeep m2 trace cxsln (TType_var(tpNew, g.knownWithoutNull)) sty1 + } //// Unifying 'T1 % and 'T2 % //| ValueSome NullnessInfo.AmbivalentToNull, ValueSome NullnessInfo.AmbivalentToNull -> // SolveTyparEqualsType csenv ndeep m2 trace sty1 (TType_var (tp2, g.knownWithoutNull)) @@ -1243,6 +1254,12 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr // Unifying 'T1? and 'T2? | ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithNull -> SolveTyparEqualsType csenv ndeep m2 trace sty2 (TType_var (tp1, g.knownWithoutNull)) + | ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithoutNull -> + let tpNew = NewCompGenTypar(TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false) + trackErrors { + do! SolveTypeEqualsType csenv ndeep m2 trace cxsln sty2 (TType_var(tpNew, g.knownWithNull)) + do! SolveTypeEqualsType csenv ndeep m2 trace cxsln (TType_var(tpNew, g.knownWithoutNull)) sty1 + } //// Unifying 'T1 % and 'T2 % //| ValueSome NullnessInfo.AmbivalentToNull, ValueSome NullnessInfo.AmbivalentToNull -> // SolveTyparEqualsType csenv ndeep m2 trace sty2 (TType_var (tp1, g.knownWithoutNull)) @@ -1259,7 +1276,7 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr match nullness1.TryEvaluate(), (nullnessOfTy g sty2).TryEvaluate() with // Unifying 'T1? and 'T2? | ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithNull -> - SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2) + SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2) // Unifying 'T1 % and 'T2 % //| ValueSome NullnessInfo.AmbivalentToNull, ValueSome NullnessInfo.AmbivalentToNull -> // SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2) @@ -2927,7 +2944,11 @@ and CanMemberSigsMatchUpToCheck ErrorD(Error (FSComp.SR.csMemberIsNotInstance(minfo.LogicalName), m)) else // The object types must be non-null - let nonNullCalledObjArgTys = calledObjArgTys |> List.map (replaceNullnessOfTy g.knownWithoutNull) + let nonNullCalledObjArgTys = + if not calledMeth.Method.IsExtensionMember then + calledObjArgTys |> List.map (replaceNullnessOfTy g.knownWithoutNull) + else + calledObjArgTys MapCombineTDC2D subsumeTypes nonNullCalledObjArgTys callerObjArgTys let! usesTDC3 = diff --git a/src/Compiler/Checking/import.fs b/src/Compiler/Checking/import.fs index b3935c5e46c..e2d5419e71e 100644 --- a/src/Compiler/Checking/import.fs +++ b/src/Compiler/Checking/import.fs @@ -186,7 +186,10 @@ module Nullness = | 2uy -> knownNullable | _ -> dprintfn "%i was passed to Nullness mapping, this is not a valid value" byteValue - knownAmbivalent + knownAmbivalent + + let isByte (g:TcGlobals) (ilgType:ILType) = + g.ilg.typ_Byte.BasicQualifiedName = ilgType.BasicQualifiedName let tryParseAttributeDataToNullableByteFlags (g:TcGlobals) attrData = match attrData with @@ -194,7 +197,7 @@ module Nullness = | Some ([ILAttribElem.Byte 0uy],_) -> ValueSome arrayWithByte0 | Some ([ILAttribElem.Byte 1uy],_) -> ValueSome arrayWithByte1 | Some ([ILAttribElem.Byte 2uy],_) -> ValueSome arrayWithByte2 - | Some ([ILAttribElem.Array(byteType,listOfBytes)],_) when byteType = g.ilg.typ_Byte -> + | Some ([ILAttribElem.Array(byteType,listOfBytes)],_) when isByte g byteType -> listOfBytes |> Array.ofList |> Array.choose(function | ILAttribElem.Byte b -> Some b | _ -> None) diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 29d41b8e543..1a4ff99f8e5 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -9104,10 +9104,7 @@ let nullnessOfTy g ty = /// 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 is 'obj' - isObjTy g sty - || + // Check if the type has AllowNullLiteral (match tryTcrefOfAppTy g sty with | ValueSome tcref -> diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs new file mode 100644 index 00000000000..73fe562b29f --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs @@ -0,0 +1,3 @@ +module MyTestModule + +let inline myCustomPipeFunc arg func = func arg \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.net472.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.net472.bsl new file mode 100644 index 00000000000..f057362bee0 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.net472.bsl @@ -0,0 +1,123 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly assembly +{ + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.dll + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed MyTestModule + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .method public static !!b myassemblyFunc(!!a arg, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 func) cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) + .param type a + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 00 00 00 ) + .param type b + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: tail. + IL_0004: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::Invoke(!0) + IL_0009: ret + } + +} + +.class private abstract auto ansi sealed ''.$MyTestModule + extends [runtime]System.Object +{ +} + +.class private auto ansi beforefieldinit System.Runtime.CompilerServices.NullableAttribute + extends [runtime]System.Attribute +{ + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field public uint8[] NullableFlags + .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 ) + .method public specialname rtspecialname instance void .ctor(uint8 scalarByteValue) cil managed + { + .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 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [runtime]System.Attribute::.ctor() + IL_0006: ldarg.0 + IL_0007: ldc.i4.1 + IL_0008: newarr [runtime]System.Byte + IL_000d: dup + IL_000e: ldc.i4.0 + IL_000f: ldarg.1 + IL_0010: stelem.i1 + IL_0011: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags + IL_0016: ret + } + + .method public specialname rtspecialname instance void .ctor(uint8[] NullableFlags) cil managed + { + .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 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [runtime]System.Attribute::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags + IL_000d: ret + } + +} + +.class private auto ansi beforefieldinit System.Runtime.CompilerServices.NullableContextAttribute + extends [runtime]System.Attribute +{ + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .field public uint8 Flag + .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 ) + .method public specialname rtspecialname instance void .ctor(uint8 Flag) cil managed + { + .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 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [runtime]System.Attribute::.ctor() + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld uint8 System.Runtime.CompilerServices.NullableContextAttribute::Flag + IL_000d: ret + } + +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.netcore.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.netcore.bsl new file mode 100644 index 00000000000..cb9e545989f --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/CustomPipe.fs.il.netcore.bsl @@ -0,0 +1,58 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly assembly +{ + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.dll + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed MyTestModule + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .custom instance void [runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) + .method public static !!b myassemblyFunc(!!a arg, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 func) cil managed + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) + .param type a + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 00 00 00 ) + .param type b + .custom instance void [runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: tail. + IL_0004: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2::Invoke(!0) + IL_0009: ret + } + +} + +.class private abstract auto ansi sealed ''.$MyTestModule + extends [runtime]System.Object +{ +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs index c7bc6a47755..46672e0208c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/NullnessMetadata.fs @@ -79,6 +79,11 @@ let ``Nullable inheritance`` compilation = compilation |> verifyCompilation DoNotOptimize +[] +let ``Custom pipe`` compilation = + compilation + |> verifyCompilation DoNotOptimize + module Interop = open System.IO diff --git a/tests/FSharp.Compiler.ComponentTests/Language/NullableCsharpImportTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/NullableCsharpImportTests.fs index 65e3a5c85bb..7c138db4e47 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/NullableCsharpImportTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/NullableCsharpImportTests.fs @@ -34,6 +34,43 @@ let doSomethingAboutIt (ilg:ILGenerator) = |> typeCheckWithStrictNullness |> shouldSucceed +[] +let ``Consuming C# extension methods which allow nullable this`` () = + FSharp """module MyLibrary + +open System + +let asMemoryOnNonNull : Memory = + let bytes = [|0uy..11uy|] + let memory = bytes.AsMemory() + memory + +let asMemoryOnNull : Memory = + let bytes : (byte[]) | null = null + let memory = bytes.AsMemory() + memory +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``Consuming LinkedList First and Last should warn about nullness`` () = + FSharp """module MyLibrary + +let ll = new System.Collections.Generic.LinkedList() +let x:System.Collections.Generic.LinkedListNode = ll.Last +let y:System.Collections.Generic.LinkedListNode = ll.First +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnostics + [ Error 3261, Line 4, Col 59, Line 4, Col 66, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode' and 'System.Collections.Generic.LinkedListNode | null' do not have compatible nullability." + Error 3261, Line 4, Col 59, Line 4, Col 66, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode' and 'System.Collections.Generic.LinkedListNode | null' do not have equivalent nullability." + Error 3261, Line 5, Col 59, Line 5, Col 67, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode' and 'System.Collections.Generic.LinkedListNode | null' do not have compatible nullability." + Error 3261, Line 5, Col 59, Line 5, Col 67, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode' and 'System.Collections.Generic.LinkedListNode | null' do not have equivalent nullability."] + [] let ``Nullable directory info show warn on prop access`` () = FSharp """module MyLibrary diff --git a/tests/FSharp.Compiler.ComponentTests/Language/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/NullableReferenceTypesTests.fs index 7639c8ca208..f546c8e8d59 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/NullableReferenceTypesTests.fs @@ -185,6 +185,21 @@ let myFunction (input1 : string | null) (input2 : string | null): (string*string |> typeCheckWithStrictNullness |> shouldFail |> withErrorCode 3261 + +[] +let ``Eliminate aliased nullness after matching`` () = + FSharp $"""module MyLibrary + +type Maybe<'T> = 'T | null + +let myFunction (input : string Maybe) : string = + match input with + | null -> "" + | nonNullString -> nonNullString +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed [] let ``WithNull used on anon type`` () = @@ -249,8 +264,7 @@ strictFunc(null:(string|null)) |> ignore |> typeCheckWithStrictNullness |> shouldFail |> withDiagnostics - [ Error 3261, Line 6, Col 12, Line 6, Col 20, "Nullness warning: The type 'obj' supports 'null' but a non-null type is expected." - Error 3261, Line 7, Col 18, Line 7, Col 26, "Nullness warning: The type 'obj' supports 'null' but a non-null type is expected." + [ Error 3261, Line 6, Col 12, Line 6, Col 16, "Nullness warning: The type 'obj' does not support 'null'." Error 3261, Line 7, Col 12, Line 7, Col 27, "Nullness warning: The type 'obj | null' supports 'null' but a non-null type is expected." Error 3261, Line 8, Col 12, Line 8, Col 30, "Nullness warning: The type 'string | null' supports 'null' but a non-null type is expected."] @@ -408,6 +422,43 @@ let whatIsThis = Option.ofObj "abc123" |> shouldFail |> withErrorCodes [3262] +[] +let ``Option ofObj for PathGetDirectoryName`` () = + FSharp """module MyLibrary +open System.IO + +let dirName = Path.GetDirectoryName "" +let whatIsThis1 = Option.ofObj dirName +let whatIsThis2 = Option.ofObj ( Path.GetDirectoryName "" ) +let whatIsThis3 = Option.ofObj ("" |> Path.GetDirectoryName ) // Warnings were happening at this line only +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``Option ofObj with fully annotated nullsupportive func`` () = + FSharp """module MyLibrary + +let nullSupportiveFunc (x: string | null) : string | null = x +let maybePath : string | null = null +let whatIsThis3 = Option.ofObj (maybePath |> nullSupportiveFunc) +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +[] +let ``Option ofObj with calling id inside`` () = + FSharp """module MyLibrary + +let maybePath : string | null = null +let whatIsThis5 = Option.ofObj (id maybePath) +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + [] let ``Useless null pattern match`` () = FSharp """module MyLibrary @@ -437,6 +488,18 @@ let mappedMaybe = nonNull maybeNull |> shouldFail |> withDiagnostics [Error 3262, Line 4, Col 25, Line 4, Col 39, "Value known to be without null passed to a function meant for nullables: You can remove this `nonNull` assertion."] +[] +let ``Regression: Useless usage in nested calls`` () = + FSharp """module MyLibrary +open System.IO + +let meTry = Option.ofObj (Path.GetDirectoryName "") +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + + [] let ``Useless usage of null active patterns from fscore`` () = FSharp """module MyLibrary @@ -457,4 +520,20 @@ let mapped2 = |> withDiagnostics [ Error 3262, Line 6, Col 7, Line 6, Col 24, "Value known to be without null passed to a function meant for nullables: You can remove this |NonNullQuick| pattern usage." Error 3262, Line 10, Col 6, Line 10, Col 10, "Value known to be without null passed to a function meant for nullables: You can remove this |Null|NonNull| pattern usage." - Error 3262, Line 11, Col 6, Line 11, Col 15, "Value known to be without null passed to a function meant for nullables: You can remove this |Null|NonNull| pattern usage."] \ No newline at end of file + Error 3262, Line 11, Col 6, Line 11, Col 15, "Value known to be without null passed to a function meant for nullables: You can remove this |Null|NonNull| pattern usage."] + +[] +let ``Obj can be passed to not null constrained methods`` () = + FSharp """module MyLibrary + +let objVal:(obj | null) = box 42 + + +let mappableFunc = + match objVal with + |Null -> 42 + |NonNull o -> o.GetHashCode() +""" + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed \ No newline at end of file