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
33 changes: 33 additions & 0 deletions src/Fable.Transforms/Beam/Prelude.fs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,39 @@ module Chars =
| Some _ -> Char
| None -> e.Type

/// Conversion of a value to its `string` form, dispatched on the static type.
module ToString =
open Fable.AST
open Fable.AST.Fable
open Fable.Transforms
open Fable.Transforms.Replacements.Util

/// The `ToString` of a value whose type the compiler can still see.
///
/// Two replacement tables reach this: `string x` as an operator, and `x.ToString()` as a
/// conversion. They must not diverge — a fix applied to one spelling and not the other is
/// invisible until someone happens to test the neglected one — so both call here.
///
/// A char is peeled out of its boxing cast first. `box c |> string` arrives as a char under a
/// cast to `obj` and would otherwise fall through to `fable_convert:to_string`, which sees only
/// the integer and prints the codepoint. Every other type is unaffected, since `tryAsChar` only
/// ever looks through a box around a char.
let toStringByType (com: ICompiler) (r: SourceLocation option) (t: Type) (arg: Expr) =
let arg = Chars.tryAsChar arg |> Option.defaultValue arg

match arg.Type with
| Type.String -> Some arg
| Type.Char -> emitExpr r t [ arg ] "<<($0)/utf8>>" |> Some
| Type.Number(kind, _) ->
match kind with
| Decimal -> Helper.LibCall(com, "fable_decimal", "to_string", t, [ arg ], ?loc = r) |> Some
| Float16
| Float32
| Float64 -> Helper.LibCall(com, "fable_convert", "to_string", t, [ arg ], ?loc = r) |> Some
| _ -> emitExpr r t [ arg ] "integer_to_binary($0)" |> Some
| Type.Boolean -> emitExpr r t [ arg ] "atom_to_binary($0)" |> Some
| _ -> Helper.LibCall(com, "fable_convert", "to_string", t, [ arg ], ?loc = r) |> Some

/// Fixed-width integer semantics. Erlang integers are arbitrary precision and never
/// overflow, so operations that can leave the width of a .NET sized integer are routed
/// through the `fable_int` runtime module to truncate them back (two's complement).
Expand Down
38 changes: 2 additions & 36 deletions src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -392,25 +392,7 @@ let private operators
|> Some
| _ -> Helper.LibCall(com, "fable_decimal", "from_int", _t, [ arg ], ?loc = r) |> Some
| _ -> Helper.LibCall(com, "fable_decimal", "from_int", _t, [ arg ], ?loc = r) |> Some
| "ToString", [ arg ] ->
// `box c |> string` arrives as a char under a boxing cast to `obj`, and would otherwise fall
// through to `fable_convert:to_string`, which sees only the integer and prints the codepoint.
// Peeling the cast puts it back on the `Type.Char` branch below; every other type is
// unaffected, since `tryAsChar` only ever looks through a box around a char.
let arg = Chars.tryAsChar arg |> Option.defaultValue arg

match arg.Type with
| Type.String -> Some arg
| Type.Char -> emitExpr r _t [ arg ] "<<($0)/utf8>>" |> Some
| Type.Number(kind, _) ->
match kind with
| Decimal -> Helper.LibCall(com, "fable_decimal", "to_string", _t, [ arg ], ?loc = r) |> Some
| Float16
| Float32
| Float64 -> Helper.LibCall(com, "fable_convert", "to_string", _t, [ arg ], ?loc = r) |> Some
| _ -> emitExpr r _t [ arg ] "integer_to_binary($0)" |> Some
| Type.Boolean -> emitExpr r _t [ arg ] "atom_to_binary($0)" |> Some
| _ -> Helper.LibCall(com, "fable_convert", "to_string", _t, [ arg ], ?loc = r) |> Some
| "ToString", [ arg ] -> ToString.toStringByType com r _t arg
| "ToChar", [ arg ] ->
match arg.Type with
| Type.String -> emitExpr r _t [ arg ] "binary:first($0)" |> Some
Expand Down Expand Up @@ -1418,23 +1400,7 @@ let private conversions
| [ a ] -> a
| _ -> Value(Null Type.Unit, None)

// `box c |> string` reaches here as a char under a boxing cast to `obj`. Peeling the cast
// puts it back on the `Type.Char` branch below, instead of falling through to
// `fable_convert:to_string`, which sees only the integer and prints the codepoint.
let arg = Chars.tryAsChar arg |> Option.defaultValue arg

match arg.Type with
| Type.String -> Some arg
| Type.Char -> emitExpr r t [ arg ] "<<($0)/utf8>>" |> Some
| Type.Number(kind, _) ->
match kind with
| Decimal -> Helper.LibCall(com, "fable_decimal", "to_string", t, [ arg ], ?loc = r) |> Some
| Float16
| Float32
| Float64 -> Helper.LibCall(com, "fable_convert", "to_string", t, [ arg ], ?loc = r) |> Some
| _ -> emitExpr r t [ arg ] "integer_to_binary($0)" |> Some
| Type.Boolean -> emitExpr r t [ arg ] "atom_to_binary($0)" |> Some
| _ -> Helper.LibCall(com, "fable_convert", "to_string", t, [ arg ], ?loc = r) |> Some
ToString.toStringByType com r t arg
// char(x) → Erlang integers represent chars
| "ToChar", [ arg ] ->
match arg.Type with
Expand Down
Loading