Skip to content

Commit

Permalink
Improve null coalescing operator support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Williams authored and Sam Williams committed Oct 26, 2019
1 parent bfe0a4b commit 4febef5
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 7 deletions.
25 changes: 21 additions & 4 deletions FSharper.Core/CsharpParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,28 @@ type CSharpStatementWalker() =
Expr.Downcast (left, right)
| SyntaxKind.CoalesceExpression ->

// There are two cases to handle here.
// 1) assigment. This is the simple case `x ?? (x = 42)` and means that we can translate to a simple `if x = null then ...; x`
// 2) setting a default value. This is actually harder since it can be nested in other statements. An if then
// actually requires two statements so won't be correct. Instead we use the Option and Option.defaultValue which can all be done
// in a single line.

let (left, right) = parseLeftandRight ()
let pipe = PrettyNaming.CompileOpName "|>" |> toLongIdent
let defaultValue = ExprOps.toApp (toLongIdent "Option.defaultValue") right
let toOptional = ExprOps.toInfixApp left pipe (toLongIdent "Option.ofObj")
ExprOps.toInfixApp toOptional pipe defaultValue

let x = right |> containsExpr (function
| Expr.LongIdentSet _
| Expr.Set _ -> true
| _ -> false )

if x |> List.isEmpty then
let pipe = PrettyNaming.CompileOpName "|>" |> toLongIdent
let defaultValue = ExprOps.toApp (toLongIdent "Option.defaultValue") right
let toOptional = ExprOps.toInfixApp left pipe (toLongIdent "Option.ofObj")
ExprOps.toInfixApp toOptional pipe defaultValue
else
let isNull = ExprOps.toInfixApp left ("=" |> PrettyNaming.CompileOpName |> Expr.Ident) Expr.Null
let assign = Expr.IfThenElse (isNull, right, None, NoSequencePointAtLetBinding, false)
sequential [assign; left]

| e ->
let ident = createErorrCode node
Expand Down
3 changes: 1 addition & 2 deletions FSharper.Core/Translation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ module TreeOps =
| Expr.App (isAtomic, isInfix, Expr.App (_, true, (Expr.Ident "op_BooleanOr"), left), right) when containsCsharpIsMatch left ->
let left' = left |> convertLogicalOrToLogicalAnd |> logicalNegateExpr
let right' = right |> convertLogicalOrToLogicalAnd |> logicalNegateExpr
let e = Expr.App (isAtomic, isInfix, Expr.App (isAtomic, true, (Expr.Ident "op_BooleanAnd"), left'), right') |> Expr.Paren
Expr.App(isAtomic, false, Expr.Ident "not", e)
ExprOps.toApp (Expr.Ident "not") (ExprOps.toInfixApp left' (Expr.Ident "op_BooleanAnd") right' |> Expr.Paren)

| Expr.App (isAtomic, isInfix, Expr.App (_, true, (Expr.Ident "op_BooleanOr"), left), right) ->
let right' = right |> convertLogicalOrToLogicalAnd
Expand Down
35 changes: 34 additions & 1 deletion FSharper.Tests/ConversionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1104,4 +1104,37 @@ type ConversionTests () =
()"""
csharp |> Converter.runWithConfig false
|> (fun x -> printfn "%s" x; x)
|> should equal (formatFsharp fsharp)
|> should equal (formatFsharp fsharp)


[<Test>]
member this.``coalescing operator handles assignment`` () =
let csharp =
"""public Foo GetFoobar ()
{
return fooBar ?? (fooBar = CreateFoo());
}"""

let fsharp =
"""member this.GetFoobar(): Foo =
if fooBar = null then fooBar <- CreateFoo()
fooBar"""
csharp |> Converter.runWithConfig false
|> (fun x -> printfn "%s" x; x)
|> should equal (formatFsharp fsharp)

[<Test>]
member this.``double question mark operator`` () =
let csharp =
"""public Foo GetFoobar ()
{
return fooBar ?? (fooBar = CreateFoo());
}"""

let fsharp =
"""member this.GetFoobar(): Foo =
if fooBar = null then fooBar <- CreateFoo()
fooBar"""
csharp |> Converter.runWithConfig false
|> (fun x -> printfn "%s" x; x)
|> should equal (formatFsharp fsharp)

0 comments on commit 4febef5

Please sign in to comment.