Skip to content

Commit e2e698d

Browse files
authored
Feature nullness :: Bugfixes (#17102)
* Ignore Nullness applied on structs (C# allows T? when when T is a struct) * Bigfix: Working with CLI events in Fsharp * Bugfix: Mutable binding initially assigned to null should not need type annotation * Solving `let mutable cache = null` via type inference * Enforcing TyparConstraint.IsReferenceType when WithNull type is used * Nullness-related constraint consistency * Bugfix for emitting Nullable attrs for C#
1 parent 13e50b4 commit e2e698d

32 files changed

Lines changed: 1134 additions & 283 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,5 @@ tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstanda
128128
*.vsp
129129
/tests/AheadOfTime/Trimming/output.txt
130130
*.svclog
131+
micro.exe
132+
positive.exe

src/Compiler/AbstractIL/ilread.fs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,9 @@ let mkCacheInt32 lowMem _inbase _nm _sz =
931931
(fun f x -> f x)
932932
else
933933
let mutable cache: ConcurrentDictionary<int32, _> MaybeNull = null // TODO NULLNESS: this explicit annotation should not be needed
934-
let mutable count = 0
935934
#if STATISTICS
935+
let mutable count = 0
936+
936937
addReport (fun oc ->
937938
if count <> 0 then
938939
oc.WriteLine((_inbase + string count + " " + _nm + " cache hits"): string))
@@ -948,7 +949,9 @@ let mkCacheInt32 lowMem _inbase _nm _sz =
948949

949950
match cache.TryGetValue idx with
950951
| true, res ->
952+
#if STATISTICS
951953
count <- count + 1
954+
#endif
952955
res
953956
| _ ->
954957
let res = f idx
@@ -960,8 +963,9 @@ let mkCacheGeneric lowMem _inbase _nm _sz =
960963
(fun f x -> f x)
961964
else
962965
let mutable cache: ConcurrentDictionary<_, _> MaybeNull = null // TODO NULLNESS: this explicit annotation should not be needed
963-
let mutable count = 0
964966
#if STATISTICS
967+
let mutable count = 0
968+
965969
addReport (fun oc ->
966970
if !count <> 0 then
967971
oc.WriteLine((_inbase + string !count + " " + _nm + " cache hits"): string))
@@ -977,7 +981,9 @@ let mkCacheGeneric lowMem _inbase _nm _sz =
977981

978982
match cache.TryGetValue idx with
979983
| true, v ->
984+
#if STATISTICS
980985
count <- count + 1
986+
#endif
981987
v
982988
| _ ->
983989
let res = f idx

src/Compiler/Checking/CheckExpressions.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ let TcAddNullnessToType (warn: bool) (cenv: cenv) (env: TcEnv) nullness innerTyC
10401040

10411041
if not g.compilingFSharpCore || not (isTyparTy g innerTyC) then
10421042
AddCxTypeDefnNotSupportsNull env.DisplayEnv cenv.css m NoTrace innerTyC
1043+
AddCxTypeIsReferenceType env.DisplayEnv cenv.css m NoTrace innerTyC
10431044

10441045
if not g.compilingFSharpCore && isTyparTy g innerTyC then
10451046
// A typar might be later infered into a type not supporting `| null|, like tuple or anon.
@@ -5314,6 +5315,7 @@ and TcExprFlex (cenv: cenv) flex compat (desiredTy: TType) (env: TcEnv) tpenv (s
53145315

53155316
if flex then
53165317
let argTy = NewInferenceType g
5318+
(destTyparTy g argTy).SetSupportsNullFlex(true)
53175319
if compat then
53185320
(destTyparTy g argTy).SetIsCompatFlex(true)
53195321

@@ -9601,7 +9603,8 @@ and TcEventItemThen (cenv: cenv) overallTy env tpenv mItem mExprAndItem objDetai
96019603
| None, false -> error (Error (FSComp.SR.tcEventIsNotStatic nm, mItem))
96029604
| _ -> ()
96039605

9604-
let delTy = einfo.GetDelegateType(cenv.amap, mItem)
9606+
// The F# wrappers around events are null safe (impl is in FSharp.Core). Therefore, from an F# perspective, the type of the delegate can be considered Not Null.
9607+
let delTy = einfo.GetDelegateType(cenv.amap, mItem) |> replaceNullnessOfTy KnownWithoutNull
96059608
let (SigOfFunctionForDelegate(delInvokeMeth, delArgTys, _, _)) = GetSigOfFunctionForDelegate cenv.infoReader delTy mItem ad
96069609
let objArgs = Option.toList (Option.map fst objDetails)
96079610
MethInfoChecks g cenv.amap true None objArgs env.eAccessRights mItem delInvokeMeth

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ type ConstraintSolverEnv =
308308
// Is this speculative, with a trace allowing undo, and trial method overload resolution
309309
IsSpeculativeForMethodOverloading: bool
310310

311+
// Can this ignore the 'must support null' constraint, e.g. in a mutable assignment scenario
312+
IsSupportsNullFlex: bool
313+
311314
/// Indicates that when unifying ty1 = ty2, only type variables in ty1 may be solved. Constraints
312315
/// can't be added to type variables in ty2
313316
MatchingOnly: bool
@@ -344,6 +347,7 @@ let MakeConstraintSolverEnv contextInfo css m denv =
344347
EquivEnv = TypeEquivEnv.Empty
345348
DisplayEnv = denv
346349
IsSpeculativeForMethodOverloading = false
350+
IsSupportsNullFlex = false
347351
ExtraRigidTypars = emptyFreeTypars
348352
}
349353

@@ -953,6 +957,13 @@ let rec SolveTyparEqualsTypePart1 (csenv: ConstraintSolverEnv) m2 (trace: Option
953957
// Record the solution before we solve the constraints, since
954958
// We may need to make use of the equation when solving the constraints.
955959
// Record a entry in the undo trace if one is provided
960+
961+
//let ty1AllowsNull = r.Constraints |> List.exists (function | TyparConstraint.SupportsNull _ -> true | _ -> false )
962+
//let tyAllowsNull() = TypeNullIsExtraValueNew csenv.g m2 ty
963+
//if ty1AllowsNull && not (tyAllowsNull()) then
964+
// trace.Exec (fun () -> r.typar_solution <- Some (ty |> replaceNullnessOfTy csenv.g.knownWithNull)) (fun () -> r.typar_solution <- None)
965+
//else
966+
// trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
956967
trace.Exec (fun () -> r.typar_solution <- Some ty) (fun () -> r.typar_solution <- None)
957968
}
958969

@@ -1218,6 +1229,12 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr
12181229
let sty1 = stripTyEqnsA csenv.g canShortcut ty1
12191230
let sty2 = stripTyEqnsA csenv.g canShortcut ty2
12201231

1232+
let csenv =
1233+
match ty1 with
1234+
| TType.TType_var(r,_) when r.typar_flags.IsSupportsNullFlex ->
1235+
{ csenv with IsSupportsNullFlex = true}
1236+
| _ -> csenv
1237+
12211238
match sty1, sty2 with
12221239
// type vars inside forall-types may be alpha-equivalent
12231240
| TType_var (tp1, nullness1), TType_var (tp2, nullness2) when typarEq tp1 tp2 || (match aenv.EquivTypars.TryFind tp1 with | Some tpTy1 when typeEquiv g tpTy1 ty2 -> true | _ -> false) ->
@@ -1273,6 +1290,15 @@ and SolveTypeEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTr
12731290
// Unifying 'T1? and 'T2?
12741291
| ValueSome NullnessInfo.WithNull, ValueSome NullnessInfo.WithNull ->
12751292
SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2)
1293+
| ValueSome NullnessInfo.WithoutNull, ValueSome NullnessInfo.WithoutNull when
1294+
csenv.IsSupportsNullFlex &&
1295+
isAppTy g sty2 &&
1296+
tp1.Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false) ->
1297+
let tpNew = NewCompGenTypar(TyparKind.Type, TyparRigidity.Flexible, TyparStaticReq.None, TyparDynamicReq.No, false)
1298+
trackErrors {
1299+
do! SolveTypeEqualsType csenv ndeep m2 trace cxsln (TType_var(tpNew, g.knownWithoutNull)) sty2
1300+
do! SolveTypeEqualsType csenv ndeep m2 trace cxsln ty1 (TType_var(tpNew, g.knownWithNull))
1301+
}
12761302
// Unifying 'T1 % and 'T2 %
12771303
//| ValueSome NullnessInfo.AmbivalentToNull, ValueSome NullnessInfo.AmbivalentToNull ->
12781304
// SolveTyparEqualsType csenv ndeep m2 trace sty1 (replaceNullnessOfTy g.knownWithoutNull sty2)
@@ -2354,6 +2380,10 @@ and EnforceConstraintConsistency (csenv: ConstraintSolverEnv) ndeep m2 trace ret
23542380
| TyparConstraint.IsNonNullableStruct _, TyparConstraint.IsReferenceType _
23552381
| TyparConstraint.IsReferenceType _, TyparConstraint.IsNonNullableStruct _ ->
23562382
return! ErrorD (Error(FSComp.SR.csStructConstraintInconsistent(), m))
2383+
2384+
| TyparConstraint.SupportsNull _, TyparConstraint.NotSupportsNull _
2385+
| TyparConstraint.NotSupportsNull _, TyparConstraint.SupportsNull _ ->
2386+
return! ErrorD (Error(FSComp.SR.csNullNotNullConstraintInconsistent(), m))
23572387

23582388
| TyparConstraint.IsUnmanaged _, TyparConstraint.IsReferenceType _
23592389
| TyparConstraint.IsReferenceType _, TyparConstraint.IsUnmanaged _ ->
@@ -2534,16 +2564,18 @@ and SolveTypeUseSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
25342564
return! ErrorD (ConstraintSolverError(FSComp.SR.csNullableTypeDoesNotHaveNull(NicePrint.minimalStringOfType denv ty), m, m2))
25352565
else
25362566
match tryDestTyparTy g ty with
2537-
| ValueSome tp ->
2567+
| ValueSome tp ->
25382568
let nullness = nullnessOfTy g ty
25392569
match nullness.TryEvaluate() with
25402570
// NULLNESS TODO: This rule means turning on checkNullness changes type inference results for the cases
25412571
// mentioned in the comment above. THat's OK but needs to be documented in the RFC.
25422572
| ValueNone when not g.checkNullness ->
2543-
return! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m)
2544-
| ValueSome NullnessInfo.WithoutNull ->
2573+
return! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m)
2574+
| ValueSome NullnessInfo.WithoutNull ->
25452575
return! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.SupportsNull m)
25462576
| _ ->
2577+
if tp.Constraints |> List.exists (function | TyparConstraint.IsReferenceType _ -> true | _ -> false) |> not then
2578+
do! AddConstraint csenv ndeep m2 trace tp (TyparConstraint.IsReferenceType m)
25472579
return! SolveNullnessSupportsNull csenv ndeep m2 trace ty nullness
25482580
| _ ->
25492581
let nullness = nullnessOfTy g ty
@@ -2580,9 +2612,10 @@ and SolveNullnessSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 (trace: Opti
25802612
match n1 with
25812613
| NullnessInfo.AmbivalentToNull -> ()
25822614
| NullnessInfo.WithNull -> ()
2583-
| NullnessInfo.WithoutNull ->
2615+
| NullnessInfo.WithoutNull ->
25842616
if g.checkNullness then
2585-
return! WarnD(ConstraintSolverNullnessWarningWithType(denv, ty, n1, m, m2))
2617+
// TODO nullness: Shouldn't this be an error? We have a 'must support null' situation which is not being met.
2618+
return! WarnD(ConstraintSolverNullnessWarningWithType(denv, ty, n1, m, m2))
25862619
}
25872620

25882621
and SolveTypeUseNotSupportsNull (csenv: ConstraintSolverEnv) ndeep m2 trace ty =
@@ -2635,7 +2668,9 @@ and SolveTypeCanCarryNullness (csenv: ConstraintSolverEnv) ty nullness =
26352668
let m = csenv.m
26362669
let strippedTy = stripTyEqnsA g true ty
26372670
match tryAddNullnessToTy nullness strippedTy with
2638-
| Some _ -> ()
2671+
| Some _ ->
2672+
if isTyparTy g strippedTy && not (isReferenceTyparTy g strippedTy) then
2673+
return! AddConstraint csenv 0 m NoTrace (destTyparTy g strippedTy) (TyparConstraint.IsReferenceType m)
26392674
| None ->
26402675
let tyString = NicePrint.minimalStringOfType csenv.DisplayEnv strippedTy
26412676
return! ErrorD(Error(FSComp.SR.tcTypeDoesNotHaveAnyNull(tyString), m))

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5648,17 +5648,23 @@ and GenGenericParam cenv eenv (tp: Typar) =
56485648
| TyparConstraint.IsNonNullableStruct _ -> true
56495649
| _ -> false)
56505650

5651-
let notNullReferenceTypeConstraint =
5651+
let nullnessOfTypar =
56525652
if g.langFeatureNullness && g.checkNullness then
56535653
let hasNotSupportsNull =
56545654
tp.Constraints
56555655
|> List.exists (function
56565656
| TyparConstraint.NotSupportsNull _ -> true
56575657
| _ -> false)
56585658

5659+
let hasSupportsNull () =
5660+
tp.Constraints
5661+
|> List.exists (function
5662+
| TyparConstraint.SupportsNull _ -> true
5663+
| _ -> false)
5664+
56595665
if hasNotSupportsNull || notNullableValueTypeConstraint then
56605666
NullnessInfo.WithoutNull
5661-
elif hasNotSupportsNull || refTypeConstraint then
5667+
elif refTypeConstraint || hasSupportsNull () then
56625668
NullnessInfo.WithNull
56635669
else
56645670
NullnessInfo.AmbivalentToNull
@@ -5711,7 +5717,7 @@ and GenGenericParam cenv eenv (tp: Typar) =
57115717
yield! GenAttrs cenv eenv tp.Attribs
57125718
if emitUnmanagedInIlOutput then
57135719
yield (GetIsUnmanagedAttribute g)
5714-
match notNullReferenceTypeConstraint with
5720+
match nullnessOfTypar with
57155721
| Some nullInfo -> yield GetNullableAttribute g [ nullInfo ]
57165722
| _ -> ()
57175723
]

src/Compiler/CodeGen/IlxGenSupport.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,8 @@ let rec GetNullnessFromTType (g: TcGlobals) ty =
446446
]
447447
| TType_forall _
448448
| TType_ucase _
449-
| TType_var _
450449
| TType_measure _ -> []
450+
| TType_var(nullness = nullness) -> [ nullness.Evaluate() ]
451451

452452
let GenNullnessIfNecessary (g: TcGlobals) ty =
453453
if g.langFeatureNullness && g.checkNullness then

src/Compiler/TypedTree/TypedTree.fs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ type TyparRigidity =
300300
[<Struct>]
301301
type TyparFlags(flags: int32) =
302302

303-
new (kind: TyparKind, rigidity: TyparRigidity, isFromError: bool, isCompGen: bool, staticReq: TyparStaticReq, dynamicReq: TyparDynamicReq, equalityDependsOn: bool, comparisonDependsOn: bool) =
303+
new (kind: TyparKind, rigidity: TyparRigidity, isFromError: bool, isCompGen: bool, staticReq: TyparStaticReq, dynamicReq: TyparDynamicReq, equalityDependsOn: bool, comparisonDependsOn: bool, supportsNullFlex: bool) =
304304
TyparFlags((if isFromError then 0b00000000000000010 else 0) |||
305305
(if isCompGen then 0b00000000000000100 else 0) |||
306306
(match staticReq with
@@ -321,7 +321,11 @@ type TyparFlags(flags: int32) =
321321
| TyparDynamicReq.No -> 0b00000000000000000
322322
| TyparDynamicReq.Yes -> 0b00000010000000000) |||
323323
(if equalityDependsOn then
324-
0b00000100000000000 else 0))
324+
0b00000100000000000 else 0) |||
325+
// 0b00001000100000000 is being checked by x.Kind, but never set in this version of the code
326+
// 0b00010000000000000 is taken by compat flex
327+
(if supportsNullFlex then
328+
0b00100000000000000 else 0))
325329

326330
/// Indicates if the type inference variable was generated after an error when type checking expressions or patterns
327331
member x.IsFromError = (flags &&& 0b00000000000000010) <> 0x0
@@ -380,8 +384,20 @@ type TyparFlags(flags: int32) =
380384
else
381385
TyparFlags(flags &&& ~~~0b00010000000000000)
382386

387+
/// Indicates that whether this type parameter is flexible for 'supports null' constraint, e.g. in the case of assignment to a mutable value
388+
member x.IsSupportsNullFlex =
389+
(flags &&& 0b00100000000000000) <> 0x0
390+
391+
member x.WithSupportsNullFlex b =
392+
if b then
393+
TyparFlags(flags ||| 0b00100000000000000)
394+
else
395+
TyparFlags(flags &&& ~~~0b00100000000000000)
396+
397+
398+
383399
member x.WithStaticReq staticReq =
384-
TyparFlags(x.Kind, x.Rigidity, x.IsFromError, x.IsCompilerGenerated, staticReq, x.DynamicReq, x.EqualityConditionalOn, x.ComparisonConditionalOn)
400+
TyparFlags(x.Kind, x.Rigidity, x.IsFromError, x.IsCompilerGenerated, staticReq, x.DynamicReq, x.EqualityConditionalOn, x.ComparisonConditionalOn, x.IsSupportsNullFlex)
385401

386402
/// Get the flags as included in the F# binary metadata. We pickle this as int64 to allow for future expansion
387403
member x.PickledBits = flags
@@ -2321,6 +2337,8 @@ type Typar =
23212337
/// Set whether this type parameter is a compat-flex type parameter (i.e. where "expr :> tp" only emits an optional warning)
23222338
member x.SetIsCompatFlex b = x.typar_flags <- x.typar_flags.WithCompatFlex b
23232339

2340+
member x.SetSupportsNullFlex b = x.typar_flags <- x.typar_flags.WithSupportsNullFlex b
2341+
23242342
/// Indicates whether a type variable can be instantiated by types or units-of-measure.
23252343
member x.Kind = x.typar_flags.Kind
23262344

@@ -2425,12 +2443,12 @@ type Typar =
24252443
/// Sets the rigidity of a type variable
24262444
member x.SetRigidity b =
24272445
let flags = x.typar_flags
2428-
x.typar_flags <- TyparFlags(flags.Kind, b, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, flags.ComparisonConditionalOn)
2446+
x.typar_flags <- TyparFlags(flags.Kind, b, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, flags.ComparisonConditionalOn, flags.IsSupportsNullFlex)
24292447

24302448
/// Sets whether a type variable is compiler generated
24312449
member x.SetCompilerGenerated b =
24322450
let flags = x.typar_flags
2433-
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, b, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, flags.ComparisonConditionalOn)
2451+
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, b, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, flags.ComparisonConditionalOn, flags.IsSupportsNullFlex)
24342452

24352453
/// Sets whether a type variable has a static requirement
24362454
member x.SetStaticReq b =
@@ -2439,17 +2457,17 @@ type Typar =
24392457
/// Sets whether a type variable is required at runtime
24402458
member x.SetDynamicReq b =
24412459
let flags = x.typar_flags
2442-
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, b, flags.EqualityConditionalOn, flags.ComparisonConditionalOn)
2460+
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, b, flags.EqualityConditionalOn, flags.ComparisonConditionalOn, flags.IsSupportsNullFlex)
24432461

24442462
/// Sets whether the equality constraint of a type definition depends on this type variable
24452463
member x.SetEqualityDependsOn b =
24462464
let flags = x.typar_flags
2447-
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, b, flags.ComparisonConditionalOn)
2465+
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, b, flags.ComparisonConditionalOn, flags.IsSupportsNullFlex)
24482466

24492467
/// Sets whether the comparison constraint of a type definition depends on this type variable
24502468
member x.SetComparisonDependsOn b =
24512469
let flags = x.typar_flags
2452-
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, b)
2470+
x.typar_flags <- TyparFlags(flags.Kind, flags.Rigidity, flags.IsFromError, flags.IsCompilerGenerated, flags.StaticReq, flags.DynamicReq, flags.EqualityConditionalOn, b, flags.IsSupportsNullFlex)
24532471

24542472
[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
24552473
member x.DebugText = x.ToString()
@@ -6118,7 +6136,7 @@ type Construct() =
61186136
Typar.New
61196137
{ typar_id = id
61206138
typar_stamp = newStamp()
6121-
typar_flags= TyparFlags(kind, rigid, isFromError, isCompGen, staticReq, dynamicReq, eqDep, compDep)
6139+
typar_flags= TyparFlags(kind, rigid, isFromError, isCompGen, staticReq, dynamicReq, eqDep, compDep, false)
61226140
typar_solution = None
61236141
typar_astype = Unchecked.defaultof<_>
61246142
typar_opt_data =

0 commit comments

Comments
 (0)