Skip to content

Nullness feature :: various bugfixes #17080

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

Merged
merged 9 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/Compiler/Checking/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
29 changes: 25 additions & 4 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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.
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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 =
Expand Down
7 changes: 5 additions & 2 deletions src/Compiler/Checking/import.fs
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,18 @@ 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
| None -> ValueNone
| 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)
Expand Down
5 changes: 1 addition & 4 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module MyTestModule

let inline myCustomPipeFunc arg func = func arg
Original file line number Diff line number Diff line change
@@ -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,b>(!!a arg,
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!a,!!b> 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<!!a,!!b>::Invoke(!0)
IL_0009: ret
}

}

.class private abstract auto ansi sealed '<StartupCode$assembly>'.$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
}

}






Original file line number Diff line number Diff line change
@@ -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,b>(!!a arg,
class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!a,!!b> 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<!!a,!!b>::Invoke(!0)
IL_0009: ret
}

}

.class private abstract auto ansi sealed '<StartupCode$assembly>'.$MyTestModule
extends [runtime]System.Object
{
}






Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ let ``Nullable inheritance`` compilation =
compilation
|> verifyCompilation DoNotOptimize

[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"CustomPipe.fs"|])>]
let ``Custom pipe`` compilation =
compilation
|> verifyCompilation DoNotOptimize


module Interop =
open System.IO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,43 @@ let doSomethingAboutIt (ilg:ILGenerator) =
|> typeCheckWithStrictNullness
|> shouldSucceed

[<FactForNETCOREAPP>]
let ``Consuming C# extension methods which allow nullable this`` () =
FSharp """module MyLibrary

open System

let asMemoryOnNonNull : Memory<byte> =
let bytes = [|0uy..11uy|]
let memory = bytes.AsMemory()
memory

let asMemoryOnNull : Memory<byte> =
let bytes : (byte[]) | null = null
let memory = bytes.AsMemory()
memory
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed

[<FactForNETCOREAPP>]
let ``Consuming LinkedList First and Last should warn about nullness`` () =
FSharp """module MyLibrary

let ll = new System.Collections.Generic.LinkedList<string>()
let x:System.Collections.Generic.LinkedListNode<string> = ll.Last
let y:System.Collections.Generic.LinkedListNode<string> = ll.First
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldFail
|> withDiagnostics
[ Error 3261, Line 4, Col 59, Line 4, Col 66, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode<string>' and 'System.Collections.Generic.LinkedListNode<string> | null' do not have compatible nullability."
Error 3261, Line 4, Col 59, Line 4, Col 66, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode<string>' and 'System.Collections.Generic.LinkedListNode<string> | null' do not have equivalent nullability."
Error 3261, Line 5, Col 59, Line 5, Col 67, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode<string>' and 'System.Collections.Generic.LinkedListNode<string> | null' do not have compatible nullability."
Error 3261, Line 5, Col 59, Line 5, Col 67, "Nullness warning: The types 'System.Collections.Generic.LinkedListNode<string>' and 'System.Collections.Generic.LinkedListNode<string> | null' do not have equivalent nullability."]

[<FactForNETCOREAPP>]
let ``Nullable directory info show warn on prop access`` () =
FSharp """module MyLibrary
Expand Down
Loading