Skip to content

Commit f939050

Browse files
authored
Bugfix :: Nullness :: Allow nullable return type for first branches of match and ifthenelse expressions (#18322)
1 parent a2f2b00 commit f939050

File tree

6 files changed

+95
-5
lines changed

6 files changed

+95
-5
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.300.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Fix Realsig+ generates nested closures with incorrect Generic ([Issue #17797](https://github.com/dotnet/fsharp/issues/17797), [PR #17877](https://github.com/dotnet/fsharp/pull/17877))
44
* Fix optimizer internal error for records with static fields ([Issue #18165](https://github.com/dotnet/fsharp/issues/18165), [PR #18280](https://github.com/dotnet/fsharp/pull/18280))
55
* Fix nullness warning with flexible types ([Issue #18056](https://github.com/dotnet/fsharp/issues/18056), [PR #18266](https://github.com/dotnet/fsharp/pull/18266))
6+
* Allow first branches of match and if expressions to return nullable results ([Issue #18015](https://github.com/dotnet/fsharp/issues/18015), [PR #18322](https://github.com/dotnet/fsharp/pull/18322))
67
* Fix internal error when missing measure attribute in an unsolved measure typar. ([Issue #7491](https://github.com/dotnet/fsharp/issues/7491), [PR #18234](https://github.com/dotnet/fsharp/pull/18234)==
78
* Set `Cancellable.token` from async computation ([Issue #18235](https://github.com/dotnet/fsharp/issues/18235), [PR #18238](https://github.com/dotnet/fsharp/pull/18238))
89
* Fix missing nullness warning when static upcast dropped nullness ([Issue #18232](https://github.com/dotnet/fsharp/issues/18232), [PR #18261](https://github.com/dotnet/fsharp/pull/18261))

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10634,6 +10634,7 @@ and TcLinearExprs bodyChecker cenv env overallTy tpenv isCompExpr synExpr cont =
1063410634

1063510635
| Some synElseExpr ->
1063610636
let env = { env with eContextInfo = ContextInfo.ElseBranchResult synElseExpr.Range }
10637+
TryAllowFlexibleNullnessInControlFlow (*isFirst=*) true g overallTy.Commit
1063710638
// tailcall
1063810639
TcLinearExprs bodyChecker cenv env overallTy tpenv isCompExpr synElseExpr (fun (elseExpr, tpenv) ->
1063910640
let resExpr = primMkCond spIfToThen m overallTy.Commit boolExpr thenExpr elseExpr
@@ -10697,6 +10698,7 @@ and TcMatchClause cenv inputTy (resultTy: OverallTy) env isFirst tpenv synMatchC
1069710698
| DebugPointAtTarget.No -> resultEnv
1069810699

1069910700
let resultExpr, tpenv = TcExprThatCanBeCtorBody cenv resultTy resultEnv tpenv synResultExpr
10701+
TryAllowFlexibleNullnessInControlFlow isFirst cenv.g resultTy.Commit
1070010702

1070110703
let target = TTarget(vspecs, resultExpr, None)
1070210704

src/Compiler/Checking/Expressions/CheckExpressionsOps.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ open FSharp.Compiler.TypedTreeBasics
1818
open FSharp.Compiler.TypedTreeOps
1919
open FSharp.Compiler.SyntaxTreeOps
2020

21+
let TryAllowFlexibleNullnessInControlFlow isFirst (g: TcGlobals.TcGlobals) ty =
22+
match isFirst, g.checkNullness, GetTyparTyIfSupportsNull g ty with
23+
| true, true, ValueSome tp -> tp.SetSupportsNullFlex(true)
24+
| _ -> ()
25+
2126
let CopyAndFixupTypars g m rigid tpsorig =
2227
FreshenAndFixupTypars g m rigid [] [] tpsorig
2328

src/Compiler/TypedTree/TypedTreeOps.fs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9171,11 +9171,14 @@ let isReferenceTyparTy g ty =
91719171
| ValueNone ->
91729172
false
91739173

9174-
let isSupportsNullTyparTy g ty =
9174+
let GetTyparTyIfSupportsNull g ty =
91759175
if isReferenceTyparTy g ty then
9176-
(destTyparTy g ty).Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false)
9176+
let tp = destTyparTy g ty
9177+
if tp.Constraints |> List.exists (function TyparConstraint.SupportsNull _ -> true | _ -> false) then
9178+
ValueSome tp
9179+
else ValueNone
91779180
else
9178-
false
9181+
ValueNone
91799182

91809183
let TypeNullNever g ty =
91819184
let underlyingTy = stripTyEqnsAndMeasureEqns g ty
@@ -9203,7 +9206,7 @@ let TypeNullIsExtraValue g m ty =
92039206
| ValueNone ->
92049207

92059208
// Consider type parameters
9206-
isSupportsNullTyparTy g ty
9209+
(GetTyparTyIfSupportsNull g ty).IsSome
92079210

92089211
// Any mention of a type with AllowNullLiteral(true) is considered to be with-null
92099212
let intrinsicNullnessOfTyconRef g (tcref: TyconRef) =
@@ -9317,7 +9320,7 @@ let TypeNullIsExtraValueNew g m ty =
93179320
| NullnessInfo.WithNull -> true)
93189321
||
93199322
// Check if the type has a ': null' constraint
9320-
isSupportsNullTyparTy g ty
9323+
(GetTyparTyIfSupportsNull g ty).IsSome
93219324

93229325
/// The pre-nullness logic about whether a type uses 'null' as a true representation value
93239326
let TypeNullIsTrueValue g ty =

src/Compiler/TypedTree/TypedTreeOps.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,8 @@ val TypeHasAllowNull: TyconRef -> TcGlobals -> range -> bool
18291829

18301830
val TypeNullIsExtraValueNew: TcGlobals -> range -> TType -> bool
18311831

1832+
val GetTyparTyIfSupportsNull: TcGlobals -> TType -> Typar voption
1833+
18321834
val TypeNullNever: TcGlobals -> TType -> bool
18331835

18341836
val TypeHasDefaultValue: TcGlobals -> range -> TType -> bool

tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,83 @@ let doNotWarnOnDowncastRepeatedNestedNullable(o:objnull) = o :? list<((AB | null
146146
Error 3060, Line 7, Col 51, Line 7, Col 97, "This type test or downcast will erase the provided type 'List<AB | null array | null> | null' to the type 'List<AB array>'"]
147147

148148

149+
[<Fact>]
150+
let ``Can infer nullable type if first match handler returns null`` () =
151+
FSharp """module TestLib
152+
153+
let myFunc x =
154+
match x with
155+
| 0 -> null
156+
| i -> "x"
157+
"""
158+
|> asLibrary
159+
|> typeCheckWithStrictNullness
160+
|> shouldSucceed
161+
162+
[<Fact>]
163+
let ``Can NOT infer nullable type if second match handler returns null`` () =
164+
FSharp """module TestLib
165+
166+
let myFunc x defaultValue =
167+
match x with
168+
| 0 -> defaultValue
169+
| 1 -> null
170+
| i -> "y"
171+
"""
172+
|> asLibrary
173+
|> typeCheckWithStrictNullness
174+
|> shouldFail
175+
|> withDiagnostics [Error 3261, Line 7, Col 12, Line 7, Col 15, "Nullness warning: The type 'string' does not support 'null'.. See also test.fs(7,11)-(7,14)."]
176+
177+
[<Fact>]
178+
let ``Can infer nullable type if first match handler returns masked null`` () =
179+
FSharp """module TestLib
180+
181+
let thisIsNull : string|null = null
182+
let myFunc x =
183+
match x with
184+
| 0 -> thisIsNull
185+
| i -> "x"
186+
"""
187+
|> asLibrary
188+
|> typeCheckWithStrictNullness
189+
|> shouldSucceed
190+
191+
[<Fact>]
192+
let ``Can infer nullable type from first branch of ifthenelse`` () =
193+
FSharp """module TestLib
194+
195+
let myFunc x =
196+
if x = 0 then null else "x"
197+
"""
198+
|> asLibrary
199+
|> typeCheckWithStrictNullness
200+
|> shouldSucceed
201+
202+
[<Fact>]
203+
let ``Can NOT infer nullable type from second branch of ifthenelse`` () =
204+
FSharp """module TestLib
205+
206+
let myFunc x =
207+
if x = 0 then "x" else null
208+
"""
209+
|> asLibrary
210+
|> typeCheckWithStrictNullness
211+
|> shouldFail
212+
|> withDiagnostics [Error 3261, Line 4, Col 28, Line 4, Col 32, "Nullness warning: The type 'string' does not support 'null'."]
213+
214+
[<Fact>]
215+
let ``Can NOT infer nullable type from second branch of nested elifs`` () =
216+
FSharp """module TestLib
217+
218+
let myFunc x =
219+
if x = 0 then "x" elif x=1 then null else "y"
220+
"""
221+
|> asLibrary
222+
|> typeCheckWithStrictNullness
223+
|> shouldFail
224+
|> withDiagnostics [Error 3261, Line 4, Col 37, Line 4, Col 41, "Nullness warning: The type 'string' does not support 'null'."]
225+
149226
[<Fact>]
150227
let ``Can convert generic value to objnull arg`` () =
151228
FSharp """module TestLib

0 commit comments

Comments
 (0)