Skip to content
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
3 changes: 3 additions & 0 deletions src/Fable.Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,9 @@ module Helpers =
// typ.TypeDefinition.DisplayName = "inref" ||
// typ.TypeDefinition.DisplayName = "outref")

let isInRefType (typ: FSharpType) =
typ.HasTypeDefinition && typ.TypeDefinition.DisplayName = "inref"

let isByRefValue (value: FSharpMemberOrFunctionOrValue) =
// Value type "this" is passed as inref, so it has to be excluded
// (Note: the non-abbreviated type of inref and outref is byref)
Expand Down
54 changes: 52 additions & 2 deletions src/Fable.Transforms/FSharp2Fable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,26 @@ let private withDefaultValueFields (com: IFableCompiler) (ctx: Context) (fsType:
else
args

let private derefByRefCallee
(com: IFableCompiler)
(ctx: Context)
r
(callee: FSharpExpr)
(calleeType: FSharpType)
(transformedCallee: Fable.Expr)
=
match callee with
| FSharpExprPatterns.Value var when
com.Options.Language <> Rust
&& isByRefType callee.Type
&& isByRefValue var
// An inref argument is passed as the value itself when its source is immutable
&& not (isInRefType var.FullType)
&& not (var.IsCompilerGenerated && var.CompiledName = "inputRecord")
->
Replacements.Api.getRefCell com r (makeType ctx.GenericArgs calleeType) transformedCallee
| _ -> transformedCallee

let rec private transformDecisionTargets
(com: IFableCompiler)
(ctx: Context)
Expand Down Expand Up @@ -1100,6 +1120,17 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs

return (expr, bindings) ||> List.fold (fun e (i, v) -> Fable.Let(i, v, e))

| FSharpExprPatterns.AddressOf inner when
var.IsCompilerGenerated
&& var.CompiledName = "inputRecord"
&& com.Options.Language <> Rust
->
let! value = transformExpr com ctx [] inner
let ctx, ident = putIdentInScope com ctx var (Some value)
let ident = { ident with Type = value.Type }
Comment thread
MangelMaxime marked this conversation as resolved.
Resolved
let! body = transformExpr com ctx [] body
return Fable.Let(ident, value, body)

| _ when isInline var ->
let ctx = { ctx with ScopeInlineValues = (var, value) :: ctx.ScopeInlineValues }

Expand Down Expand Up @@ -1402,7 +1433,14 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs
// Getters and Setters
| FSharpExprPatterns.AnonRecordGet(callee, calleeType, fieldIndex) ->
let r = makeRangeFrom fsExpr
let! callee = transformExpr com ctx [] callee

let callee =
match callee with
| IgnoreAddressOf inner when com.Options.Language <> Rust -> inner
| _ -> callee

let! transformedCallee = transformExpr com ctx [] callee
let callee = derefByRefCallee com ctx r callee calleeType transformedCallee

let fieldName = calleeType.AnonRecordTypeDetails.SortedFieldNames[fieldIndex]

Expand All @@ -1414,7 +1452,19 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs

| FSharpExprPatterns.FSharpFieldGet(callee, calleeType, field) ->
let r = makeRangeFrom fsExpr
let! callee = transformCallee com ctx callee calleeType

let callee =
match callee with
| Some(IgnoreAddressOf inner) when com.Options.Language <> Rust -> Some inner
| callee -> callee

let! transformedCallee = transformCallee com ctx callee calleeType

let callee =
match callee with
| Some callee -> derefByRefCallee com ctx r callee calleeType transformedCallee
| None -> transformedCallee

// let typ = makeType ctx.GenericArgs fsExpr.Type // Doesn't always work
let typ = resolveFieldType ctx calleeType field.FieldType

Expand Down
87 changes: 87 additions & 0 deletions tests/Beam/RecordTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@ type IntegrityRecord =

override this.ToString() = this.Level

[<Struct>]
type StructRecordCopied = { T: float; S: string }

[<Struct>]
type StructRecordWithMutableField = { mutable MutableN: int; FixedN: int }

[<Struct>]
type StructRecordOrdered = { OrdA: int; OrdB: int; OrdC: int }

type ByRefClassRecord = { BcP: int; BcQ: int }

type ByRefClass =
val mutable BkF: int
val BkV: int
new() = { BkF = 0; BkV = 3 }

let sumStructRecordByRef (r: byref<StructRecordOrdered>) = r.OrdA + r.OrdC

let copyStructRecordByRef (r: byref<StructRecordOrdered>) = { r with OrdB = 99 }

let sumClassRecordByRef (r: byref<ByRefClassRecord>) = r.BcP + r.BcQ

let setClassRecordOutRef (r: outref<ByRefClassRecord>) =
r <- { BcP = 4; BcQ = 1 }
r.BcP + r.BcQ

let sumClassByRef (r: byref<ByRefClass>) = r.BkF + r.BkV

let sumAnonymousStructRecordByRef (x: byref<struct {| AnX: int; AnY: int |}>) = x.AnX + x.AnY

let mutable moduleStructRecord = { T = 1.0; S = "a" }

[<Fact>]
let ``test simple record creation works`` () =
let p = { Name = "Alice"; Age = 30 }
Expand Down Expand Up @@ -311,3 +343,58 @@ let ``test Records with ref-type interior mutability`` () =
x.MutRefValue |> equal "ab"
x.MutRefValue <- x.MutRefValue + "c"
x.MutRefValue |> equal "abc"

[<Fact>]
let ``test copy-and-update of a let mutable struct record keeps the unchanged fields`` () =
let mutable mark = { T = 1.0; S = "a" }
let copies = ResizeArray()
for i in 1..2 do
mark <- { T = float i; S = "b" }
copies.Add { mark with S = "c" }
copies |> Seq.map (fun m -> m.T) |> List.ofSeq |> equal [ 1.0; 2.0 ]
copies |> Seq.map (fun m -> m.S) |> List.ofSeq |> equal [ "c"; "c" ]

[<Fact>]
let ``test copy-and-update of a let mutable struct record with mutable fields keeps the unchanged fields`` () =
let mutable p = { MutableN = 1; FixedN = 2 }
p.MutableN <- 5
let q = { p with FixedN = 9 }
q.MutableN |> equal 5
q.FixedN |> equal 9
p.FixedN |> equal 2

[<Fact>]
let ``test copy-and-update of a module-level mutable struct record keeps the unchanged fields`` () =
moduleStructRecord <- { moduleStructRecord with T = 7.0 }
let copy = { moduleStructRecord with S = "b" }
copy.T |> equal 7.0
copy.S |> equal "b"
moduleStructRecord.S |> equal "a"

[<Fact>]
let ``test field reads and copy-and-update through a byref struct record parameter work`` () =
let mutable r = { OrdA = 1; OrdB = 2; OrdC = 3 }
sumStructRecordByRef &r |> equal 4
let x = copyStructRecordByRef &r
(x.OrdA, x.OrdB, x.OrdC) |> equal (1, 99, 3)

[<Fact>]
let ``test field reads through a byref or outref to a reference type work`` () =
let mutable c = { BcP = 1; BcQ = 2 }
sumClassRecordByRef &c |> equal 3
let mutable o = { BcP = 0; BcQ = 0 }
setClassRecordOutRef &o |> equal 5
let mutable k = ByRefClass()
k.BkF <- 4
sumClassByRef &k |> equal 7

[<Fact>]
let ``test copy-and-update of a let mutable anonymous struct record keeps the unchanged fields`` () =
let mutable a = struct {| X = 1; Y = 2 |}
let b = {| a with Y = 99 |}
(b.X, b.Y) |> equal (1, 99)

[<Fact>]
let ``test field reads through a byref to an anonymous struct record work`` () =
let mutable a = struct {| AnX = 1; AnY = 2 |}
sumAnonymousStructRecordByRef &a |> equal 3
37 changes: 37 additions & 0 deletions tests/Dart/src/RecordTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ type RecordShadowingFileScope =
member this.Wrap() = { Value = this.Wrapped }
member this.Add() = addOne this.addOne

[<Struct>]
type StructRecordCopied = { T: float; S: string }

[<Struct>]
type StructRecordWithMutableField = { mutable MutableN: int; FixedN: int }

let mutable moduleStructRecord = { T = 1.0; S = "a" }

let tests() =
testCase "Anonymous records work" <| fun () ->
let r = makeAnonRec()
Expand Down Expand Up @@ -181,3 +189,32 @@ let tests() =
let record = { Wrapped = 3; addOne = 7 } :> IWrap
record.Wrap().Value |> equal 3
record.Add() |> equal 8

testCase "Copy-and-update of a let mutable struct record keeps the unchanged fields" <| fun () ->
let mutable mark = { T = 1.0; S = "a" }
let copies = ResizeArray()
for i in 1..2 do
mark <- { T = float i; S = "b" }
copies.Add { mark with S = "c" }
copies |> Seq.map (fun m -> m.T) |> List.ofSeq |> equal [ 1.0; 2.0 ]
copies |> Seq.map (fun m -> m.S) |> List.ofSeq |> equal [ "c"; "c" ]

testCase "Copy-and-update of a let mutable struct record with mutable fields keeps the unchanged fields" <| fun () ->
let mutable p = { MutableN = 1; FixedN = 2 }
p.MutableN <- 5
let q = { p with FixedN = 9 }
q.MutableN |> equal 5
q.FixedN |> equal 9
p.FixedN |> equal 2

testCase "Copy-and-update of a module-level mutable struct record keeps the unchanged fields" <| fun () ->
moduleStructRecord <- { moduleStructRecord with T = 7.0 }
let copy = { moduleStructRecord with S = "b" }
copy.T |> equal 7.0
copy.S |> equal "b"
moduleStructRecord.S |> equal "a"

testCase "Copy-and-update of a let mutable anonymous struct record keeps the unchanged fields" <| fun () ->
let mutable a = struct {| X = 1; Y = 2 |}
let b = {| a with Y = 99 |}
(b.X, b.Y) |> equal (1, 99)
80 changes: 80 additions & 0 deletions tests/Js/Main/RecordTypeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ let holdStructRecord (data: StructRecord) (path: int list) =
Data = { data with Path = List.rev path }
}

[<Struct>]
type StructRecordCopied = { T: float; S: string }

[<Struct>]
type StructRecordWithMutableField = { mutable MutableN: int; FixedN: int }

[<Struct>]
type StructRecordOrdered = { OrdA: int; OrdB: int; OrdC: int }

type ByRefClassRecord = { BcP: int; BcQ: int }

type ByRefClass =
val mutable BkF: int
val BkV: int
new() = { BkF = 0; BkV = 3 }

let sumStructRecordByRef (r: byref<StructRecordOrdered>) = r.OrdA + r.OrdC

let copyStructRecordByRef (r: byref<StructRecordOrdered>) = { r with OrdB = 99 }

let sumClassRecordByRef (r: byref<ByRefClassRecord>) = r.BcP + r.BcQ

let setClassRecordOutRef (r: outref<ByRefClassRecord>) =
r <- { BcP = 4; BcQ = 1 }
r.BcP + r.BcQ

let sumClassByRef (r: byref<ByRefClass>) = r.BkF + r.BkV

let sumAnonymousStructRecordByRef (x: byref<struct {| AnX: int; AnY: int |}>) = x.AnX + x.AnY

let mutable moduleStructRecord = { T = 1.0; S = "a" }

let tests =
testList "RecordTypes" [

Expand Down Expand Up @@ -210,4 +242,52 @@ let tests =
equal 1 a.xs.Count
equal 1 b.xs.Count

testCase "Copy-and-update of a let mutable struct record keeps the unchanged fields" <| fun () ->
let mutable mark = { T = 1.0; S = "a" }
let copies = ResizeArray()
for i in 1..2 do
mark <- { T = float i; S = "b" }
copies.Add { mark with S = "c" }
copies |> Seq.map (fun m -> m.T) |> List.ofSeq |> equal [ 1.0; 2.0 ]
copies |> Seq.map (fun m -> m.S) |> List.ofSeq |> equal [ "c"; "c" ]

testCase "Copy-and-update of a let mutable struct record with mutable fields keeps the unchanged fields" <| fun () ->
let mutable p = { MutableN = 1; FixedN = 2 }
p.MutableN <- 5
let q = { p with FixedN = 9 }
q.MutableN |> equal 5
q.FixedN |> equal 9
p.FixedN |> equal 2

testCase "Copy-and-update of a module-level mutable struct record keeps the unchanged fields" <| fun () ->
moduleStructRecord <- { moduleStructRecord with T = 7.0 }
let copy = { moduleStructRecord with S = "b" }
copy.T |> equal 7.0
copy.S |> equal "b"
moduleStructRecord.S |> equal "a"

testCase "Field reads and copy-and-update through a byref struct record parameter work" <| fun () ->
let mutable r = { OrdA = 1; OrdB = 2; OrdC = 3 }
sumStructRecordByRef &r |> equal 4
let x = copyStructRecordByRef &r
(x.OrdA, x.OrdB, x.OrdC) |> equal (1, 99, 3)

testCase "Field reads through a byref or outref to a reference type work" <| fun () ->
let mutable c = { BcP = 1; BcQ = 2 }
sumClassRecordByRef &c |> equal 3
let mutable o = { BcP = 0; BcQ = 0 }
setClassRecordOutRef &o |> equal 5
let mutable k = ByRefClass()
k.BkF <- 4
sumClassByRef &k |> equal 7

testCase "Copy-and-update of a let mutable anonymous struct record keeps the unchanged fields" <| fun () ->
let mutable a = struct {| X = 1; Y = 2 |}
let b = {| a with Y = 99 |}
(b.X, b.Y) |> equal (1, 99)

testCase "Field reads through a byref to an anonymous struct record work" <| fun () ->
let mutable a = struct {| AnX = 1; AnY = 2 |}
sumAnonymousStructRecordByRef &a |> equal 3

]
Loading
Loading