Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/conventional-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
scopes: |
(?:all|(?:js|ts|dart|python|rust|beam)(?:/(?:js|ts|dart|python|rust|beam))*)
(?:all|(?:js|ts|dart|python|rust|beam|php)(?:/(?:js|ts|dart|python|rust|beam|php))*)
5 changes: 4 additions & 1 deletion src/Fable.Transforms/Php/Fable2Php.fs
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,10 @@ let rec tryFindMethod methodName (phpType: PhpType) =
let rec convertExpr (com: IPhpCompiler) (expr: Fable.Expr) =
match expr with
| Fable.Extended _ -> failwith "TODO: Extended instructions"
| Fable.Quote _ -> failwith "Quotations are not yet supported for PHP target"
| Fable.Quote(body, _isTyped, _r) ->
// Lower the quoted body to runtime calls that build the quotation AST,
// then convert like any other expression (mirrors JS/Python/Beam/Rust).
convertExpr com (QuotationEmitter.emitQuotedExpr com body)

| Fable.Unresolved _ -> failwith "Unexpected unresolved expression"

Expand Down
74 changes: 55 additions & 19 deletions src/Fable.Transforms/Rust/Fable2Rust.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,10 @@ module Util =
| t1, t2 when t1 = t2 -> expr // no cast needed if types are the same
| Fable.Number _, Fable.Number _ -> expr |> mkCastExpr ty
| Fable.Char, Fable.Number(UInt32, Fable.NumberInfo.Empty) -> expr |> mkCastExpr ty
// bool -> integer (Rust allows `b as iN`/`b as uN`, yields 0/1)
| Fable.Boolean,
Fable.Number((Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32 | Int64 | UInt64 | Int128 | UInt128 | NativeInt | UNativeInt),
_) -> expr |> mkCastExpr ty
| Fable.Tuple(ga1, false), Fable.Tuple(ga2, true) when ga1 = ga2 -> expr |> makeAsRef |> makeClone //.ToValueTuple()
| Fable.Tuple(ga1, true), Fable.Tuple(ga2, false) when ga1 = ga2 -> expr |> makeLrcPtrValue com ctx //.ToTuple()

Expand Down Expand Up @@ -1840,8 +1844,33 @@ module Util =
makeLibCall com ctx genArgsOpt "Native" "null" []

let makeInit com ctx (typ: Fable.Type) =
let genArgsOpt = transformGenArgs com ctx [ typ ]
makeLibCall com ctx genArgsOpt "Native" "getZero" []
// `Native::getZero` uses `core::mem::zeroed`, which panics at runtime when the
// type is a reference type (Rc/Arc/Box pointer). For such pre-declared match
// bindings we must emit a valid placeholder value instead (it is only there to
// satisfy definite-assignment and gets overwritten before it is ever read).
match typ with
| Fable.Any ->
// `dyn Any` is unsized, so `Native::null` (needs a sized `NullableRef`) does
// not apply; use a valid boxed-unit placeholder of type `LrcPtr<dyn Any>`.
makeLibCall com ctx None "Native" "getZeroObj" []
| _ ->
// Only concrete (sized) `Lrc`-wrapped reference types can use the null-ref
// placeholder. Unsized refs (interfaces, enumerators) and Arc/Box-wrapped
// types keep `getZero` (they don't reach this path in practice).
let isConcreteLrcRef =
match shouldBeRefCountWrapped com ctx typ with
| Some Lrc ->
match typ with
| Fable.DeclaredType(entRef, _) -> not (com.GetEntity(entRef)).IsInterface
| Replacements.Util.IsEnumerator _ -> false
| _ -> true
| _ -> false

if isConcreteLrcRef then
makeNull com ctx typ
else
let genArgsOpt = transformGenArgs com ctx [ typ ]
makeLibCall com ctx genArgsOpt "Native" "getZero" []

let makeDefault com ctx (typ: Fable.Type) =
let genArgsOpt = transformGenArgs com ctx [ typ ]
Expand Down Expand Up @@ -2370,6 +2399,12 @@ module Util =
| UnaryOperator.UnaryNotBitwise -> mkNotExpr expr // ?loc=range)
| UnaryOperator.UnaryAddressOf -> expr |> mkAddrOfExpr

| Fable.Binary(BinaryOperator.BinaryExponent, leftExpr, rightExpr) ->
// Rust has no `**` operator; F# (**) is floating-point exponentiation.
let left = transformLeaveContext com ctx None leftExpr |> maybeAddParens leftExpr
let right = transformLeaveContext com ctx None rightExpr |> maybeAddParens rightExpr
mkMethodCallExpr "powf" None left [ right ]

| Fable.Binary(op, leftExpr, rightExpr) ->
let kind =
match op with
Expand All @@ -2387,7 +2422,7 @@ module Util =
| BinaryOperator.BinaryMultiply -> Rust.BinOpKind.Mul
| BinaryOperator.BinaryDivide -> Rust.BinOpKind.Div
| BinaryOperator.BinaryModulus -> Rust.BinOpKind.Rem
| BinaryOperator.BinaryExponent -> failwithf "BinaryExponent not supported. TODO: implement with pow."
| BinaryOperator.BinaryExponent -> failwith "unreachable: BinaryExponent handled above"
| BinaryOperator.BinaryOrBitwise -> Rust.BinOpKind.BitOr
| BinaryOperator.BinaryXorBitwise -> Rust.BinOpKind.BitXor
| BinaryOperator.BinaryAndBitwise -> Rust.BinOpKind.BitAnd
Expand Down Expand Up @@ -3360,12 +3395,15 @@ module Util =
function
| Fable.Operation(Fable.Binary(BinaryEqual, left, right), _, _, _) ->
match left, right with
| _, Fable.Value((Fable.CharConstant _ | Fable.StringConstant _ | Fable.NumberConstant _), _) ->
Some(left, right)
| Fable.Value((Fable.CharConstant _ | Fable.StringConstant _ | Fable.NumberConstant _), _), _ ->
Some(right, left)
// NOTE: StringConstant is intentionally excluded — Rust match patterns can't
// represent a fable-library string (it lowers to a `string("...")` call), so
// string matches must use the if/else decision-tree path with real `==`.
| _, Fable.Value((Fable.CharConstant _ | Fable.NumberConstant _), _) -> Some(left, right)
| Fable.Value((Fable.CharConstant _ | Fable.NumberConstant _), _), _ -> Some(right, left)
| _ -> None
| Fable.Test(expr, Fable.OptionTest isSome, r) ->
// Only a plain-ident scrutinee is convertible: transformSwitch can't recover the
// `Option` type from anything else, and would emit bogus `0_i32` patterns.
| Fable.Test(Fable.IdentExpr _ as expr, Fable.OptionTest isSome, r) ->
let evalExpr =
Fable.Get(expr, Fable.UnionTag, Fable.Number(Int32, Fable.NumberInfo.Empty), r)

Expand Down Expand Up @@ -3545,18 +3583,16 @@ module Util =
let ctx = { ctx with DecisionTargets = targets }
transformSwitch com ctx (targetId |> Fable.IdentExpr) cases (Some defaultCase)

match tryTransformAsSwitch treeExpr with
| Some(evalExpr, cases, defaultCase) ->
// let cases = groupSwitchCases typ cases defaultCase
let switch1 = transformSwitch com ctx evalExpr cases (Some defaultCase)

varDecls @ [ switch1 |> mkSemiStmt ] @ [ switch2 |> mkExprStmt ]
|> mkStmtBlockExpr
| None ->
let decisionTree = com.TransformExpr(ctx, treeExpr)
// NOTE: switch1 must ASSIGN matchResult (+ bound idents) via DecisionTreeSuccess,
// not execute the target bodies directly. Transforming the tree as a switch here
// (via tryTransformAsSwitch) would run the target bodies with still-unassigned
// bindings and leave matchResult = 0, so switch2 would always dispatch to target 0.
// Instead, transform the tree directly so DecisionTreeSuccess nodes become
// assignments (via transformDecisionTreeSuccess), then let switch2 dispatch.
let decisionTree = com.TransformExpr(ctx, treeExpr)

varDecls @ [ decisionTree |> mkSemiStmt ] @ [ switch2 |> mkExprStmt ]
|> mkStmtBlockExpr
varDecls @ [ decisionTree |> mkSemiStmt ] @ [ switch2 |> mkExprStmt ]
|> mkStmtBlockExpr

let transformDecisionTree (com: IRustCompiler) ctx targets (treeExpr: Fable.Expr) : Rust.Expr =
let treeExpr = simplifyDecisionTree treeExpr
Expand Down
13 changes: 12 additions & 1 deletion src/Fable.Transforms/Rust/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ let convertTo com (ctx: Context) r t (args: Expr list) =
match t with
| Boolean ->
match sourceType with
| Boolean -> args.Head
| Number(Decimal, _) -> Helper.LibCall(com, "Decimal", "toBoolean", t, args, ?loc = r)
| Number(BigInt, _) -> Helper.LibCall(com, "BigInt", "toBoolean", t, args, ?loc = r)
| Number(_kind, _) -> Helper.LibCall(com, "Convert", "toBoolean", t, args, ?loc = r)
Expand Down Expand Up @@ -239,6 +240,10 @@ let convertTo com (ctx: Context) r t (args: Expr list) =

| Number(kind, _) ->
match sourceType with
| Boolean ->
// .NET Convert.ToXxx(bool) yields 0/1; route via i32 so float targets also work
let code = TypeCast(args.Head, Int32.Number)
TypeCast(code, t)
| Char ->
let code = TypeCast(args.Head, UInt32.Number)
TypeCast(code, t)
Expand Down Expand Up @@ -1640,7 +1645,10 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
Helper.LibCall(com, "String", "splitChars", t, [ c; arg1; arg2; arg3 ], ?loc = r)
|> Some

// TODO: handle arrays of string separators with more than one element
// Remaining gap: the count-bearing string[] overload Split(string[], int, options)
// for multi-element / non-literal arrays. splitStrings has no count parameter, and a
// correct global left-to-right count across multiple separators is non-trivial.
// The common Split(string[], options) form is handled above via splitStrings.
| _ -> None
| "StartsWith", Some c, _ ->
match args with
Expand Down Expand Up @@ -2240,6 +2248,9 @@ let decimals (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg:
| ("get_Zero" | "get_One" | "get_MinusOne" | "get_MinValue" | "get_MaxValue"), _ ->
Helper.LibValue(com, "Decimal", Naming.removeGetSetPrefix i.CompiledName, t)
|> Some
| ("IsInteger" | "IsEvenInteger" | "IsOddInteger" | "IsCanonical" | "IsNegative" | "IsPositive"), _ ->
Helper.LibCall(com, "Decimal", Naming.lowerFirst i.CompiledName, t, args, i.SignatureArgTypes, ?loc = r)
|> Some
| "get_Scale", [] ->
match thisArg with
| Some c ->
Expand Down
Loading
Loading