Skip to content
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

Nullness :: Pattern matching with null should mark input for subsequent clauses as without null (+ tuple magic) #16659

Merged
merged 10 commits into from
Feb 12, 2024
34 changes: 32 additions & 2 deletions src/Compiler/Checking/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10399,10 +10399,13 @@ and TcMatchPattern cenv inputTy env tpenv (synPat: SynPat) (synWhenExprOpt: SynE
and TcMatchClauses cenv inputTy (resultTy: OverallTy) env tpenv clauses =
let mutable first = true
let isFirst() = if first then first <- false; true else false
List.mapFold (fun clause -> TcMatchClause cenv inputTy resultTy env (isFirst()) clause) tpenv clauses
let resultList,(tpEnv,_input) =
List.mapFold (fun (unscopedTyParEnv,inputTy) -> TcMatchClause cenv inputTy resultTy env (isFirst()) unscopedTyParEnv) (tpenv,inputTy) clauses
resultList,tpEnv

and TcMatchClause cenv inputTy (resultTy: OverallTy) env isFirst tpenv synMatchClause =
let (SynMatchClause(synPat, synWhenExprOpt, synResultExpr, patm, spTgt, _)) = synMatchClause

let pat, whenExprOpt, vspecs, envinner, tpenv = TcMatchPattern cenv inputTy env tpenv synPat synWhenExprOpt

let resultEnv =
Expand All @@ -10417,8 +10420,35 @@ and TcMatchClause cenv inputTy (resultTy: OverallTy) env isFirst tpenv synMatchC
let resultExpr, tpenv = TcExprThatCanBeCtorBody cenv resultTy resultEnv tpenv synResultExpr

let target = TTarget(vspecs, resultExpr, None)

let inputTypeForNextPatterns=
let removeNull t = replaceNullnessOfTy KnownWithoutNull t
let rec isWild (p:Pattern) =
match p with
| TPat_wild _ -> true
| TPat_as (p,_,_) -> isWild p
| TPat_disjs(patterns,_) -> patterns |> List.exists isWild
| TPat_conjs(patterns,_) -> patterns |> List.forall isWild
| TPat_tuple (_,pats,_,_) -> pats |> List.forall isWild
| _ -> false

let rec eliminateNull (ty:TType) (p:Pattern) =
match p with
| TPat_null _ -> removeNull ty
| TPat_as (p,_,_) -> eliminateNull ty p
| TPat_disjs(patterns,_) -> (ty,patterns) ||> List.fold eliminateNull
| TPat_tuple (_,pats,_,_) ->
match stripTyparEqns ty with
// In a tuple of size N, if 1 elem is matched for null and N-1 are wild => subsequent clauses can strip nullness
| TType_tuple(ti,tys) when tys.Length = pats.Length && (pats |> List.count (isWild >> not)) = 1 ->
TType_tuple(ti, List.map2 eliminateNull tys pats)
| _ -> ty
| _ -> ty
match whenExprOpt with
| None -> eliminateNull inputTy pat
| _ -> inputTy

MatchClause(pat, whenExprOpt, target, patm), tpenv
MatchClause(pat, whenExprOpt, target, patm), (tpenv,inputTypeForNextPatterns)

and TcStaticOptimizationConstraint cenv env tpenv c =
let g = cenv.g
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ let typeCheckWithStrictNullness cu =
|> withCheckNulls
|> withWarnOn 3261
|> withOptions ["--warnaserror+"]
|> typecheck
|> compile

[<Fact>]
let ``Printing a nullable string should pass`` () =
Expand Down Expand Up @@ -71,4 +71,60 @@ let doStuff() =
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed
|> shouldSucceed


[<InlineData("null")>]
[<InlineData(""" null | "" """)>]
[<InlineData(""" "" | null """)>]
[<InlineData(""" "" | " " | null """)>]
[<InlineData("(null)")>]
[<InlineData("(null) as _myUselessNullValue")>]
[<Theory>]
let ``Eliminate nullness after matching`` (tp) =
FSharp $"""module MyLibrary

let myFunction (input : string | null) : string =
match input with
| {tp} -> ""
| nonNullString -> nonNullString
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed

[<InlineData("""(null,_aVal) | (_aVal, null) """)>]
[<InlineData("""(null,("" | null | _)) | (_, null)""")>]
[<Theory>]
let ``Eliminate tupled nullness after matching`` (tp) =
FSharp $"""module MyLibrary

let myFunction (input1 : string | null) (input2 : string | null): (string*string) =
match input1,input2 with
| {tp} -> "",""
| nns1,nns2 -> nns1,nns2
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed


[<InlineData("""(null,"a") | ("b",null) """)>]
[<InlineData("(null,null)")>]
[<InlineData(""" null, a """)>]
[<InlineData(""" "a", "b" """)>]
[<InlineData(""" (_a,_b) when System.Console.ReadLine() = "lucky" """)>]
[<InlineData("(_,null)")>]
[<Theory>]
let ``Should NOT eliminate tupled nullness after matching`` (tp) =
FSharp $"""module MyLibrary

let myFunction (input1 : string | null) (input2 : string | null): (string*string) =
match input1,input2 with
| %s{tp} -> "",""
| nns1,nns2 -> nns1,nns2
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldFail
|> withErrorCode 3261
Loading