Skip to content

Commit

Permalink
[Rust] Add support for null strings (#4033)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave authored Feb 5, 2025
1 parent 9949737 commit e381f0a
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 152 deletions.
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

* [Python] - Print root module and module function comments (by @alfonsogarciacaro)
* [Rust] Add support for null strings (by @ncave)

## 5.0.0-alpha.9 - 2025-01-28

Expand Down
1 change: 1 addition & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

* [Python] - Print root module and module function comments (by @alfonsogarciacaro)
* [Rust] Add support for null strings (by @ncave)

## 5.0.0-alpha.9 - 2025-01-28

Expand Down
27 changes: 17 additions & 10 deletions src/Fable.Transforms/Rust/Fable2Rust.fs
Original file line number Diff line number Diff line change
Expand Up @@ -998,10 +998,11 @@ module TypeInfo =
isNullable || isNotNullable && isReferenceType

let transformGenericParamType com ctx name isMeasure constraints : Rust.Ty =
if isNullableReferenceType com ctx constraints then
// primitiveType name |> makeNullableTy com ctx
primitiveType name |> makeLrcPtrTy com ctx
elif isInferredGenericParam com ctx name isMeasure then
// if isNullableReferenceType com ctx constraints then
// // primitiveType name |> makeNullableTy com ctx
// primitiveType name |> makeLrcPtrTy com ctx
// elif
if isInferredGenericParam com ctx name isMeasure then
mkInferTy () // mkNeverTy ()
else
primitiveType name
Expand Down Expand Up @@ -1734,7 +1735,7 @@ module Util =
makeLibCall com ctx None "String" "fromString" [ value ]

let makeNullCheck com ctx (value: Rust.Expr) =
makeLibCall com ctx None "Native" "isNull" [ value ]
makeLibCall com ctx None "Native" "is_null" [ value ]

let makeNull com ctx (typ: Fable.Type) =
let typ =
Expand All @@ -1743,7 +1744,7 @@ module Util =
| t -> t

let genArgsOpt = transformGenArgs com ctx [ typ ]
makeLibCall com ctx genArgsOpt "Native" "getNull" []
makeLibCall com ctx genArgsOpt "Native" "null" []

let makeInit com ctx (typ: Fable.Type) =
let genArgsOpt = transformGenArgs com ctx [ typ ]
Expand Down Expand Up @@ -2717,7 +2718,7 @@ module Util =
isByRefType com ident2.Type || ident2.IsMutable
->
transformIdent com ctx None ident2 |> Some
| Fable.Value(Fable.Null _t, _) -> None // no init value, just a name declaration, to be initialized later
| Fable.Value(Fable.Null Fable.MetaType, _) -> None // special init value to skip initialization
| Function(args, body, _name) -> transformLambda com ctx (Some ident.Name) args body |> Some
| _ ->
transformLeaveContext com ctx None value
Expand Down Expand Up @@ -3876,6 +3877,11 @@ module Util =
let genArgsOpt = mkConstraintArgs tys []
mkTypeTraitGenericBound names genArgsOpt

let makeImportBound com ctx moduleName typeName =
let importName = getLibraryImportName com ctx moduleName typeName
let objectBound = mkTypeTraitGenericBound [ importName ] None
objectBound

let makeRawBound id = makeGenBound [ rawIdent id ] []

let makeOpBound op =
Expand Down Expand Up @@ -3914,7 +3920,7 @@ module Util =
else
[]
| _ -> []
| Fable.Constraint.IsNullable -> []
| Fable.Constraint.IsNullable -> [ makeImportBound com ctx "Native" "NullableRef" ]
| Fable.Constraint.IsNotNullable -> []
| Fable.Constraint.IsValueType -> []
| Fable.Constraint.IsReferenceType -> []
Expand Down Expand Up @@ -4513,9 +4519,10 @@ module Util =
let body =
(body, fieldIdents |> List.rev)
||> List.fold (fun acc ident ->
let nullOfT = Fable.Value(Fable.Null ident.Type, None)
// use special init value to skip initialization (i.e. declaration only)
let nullOfT = Fable.Value(Fable.Null Fable.MetaType, None)
Fable.Let(ident, nullOfT, acc)
) // will be transformed as declaration only
)

body
| e -> e
Expand Down
20 changes: 12 additions & 8 deletions src/Fable.Transforms/Rust/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ let objectHash (com: ICompiler) ctx r (arg: Expr) =
let referenceEquals (com: ICompiler) ctx r (left: Expr) (right: Expr) =
match left, right with
| Value(Null _, _), o
| o, Value(Null _, _) -> Helper.LibCall(com, "Native", "isNull", Boolean, [ o ], ?loc = r)
| o, Value(Null _, _) -> Helper.LibCall(com, "Native", "is_null", Boolean, [ o ], ?loc = r)
| _ ->
match left.Type with
| Boolean
Expand Down Expand Up @@ -631,7 +631,7 @@ let rec getZero (com: ICompiler) (ctx: Context) (t: Type) =
| Number(Decimal, _) -> Helper.LibValue(com, "Decimal", "Zero", t)
| Number(kind, uom) -> NumberConstant(NumberValue.GetZero kind, uom) |> makeValue None
| Char -> CharConstant '\u0000' |> makeValue None
| String -> makeStrConst "" // TODO: Use null for string?
| String -> Null t |> makeValue None
| Array(typ, _) -> makeArray typ []
| Builtin BclDateTime -> Helper.LibCall(com, "DateTime", "zero", t, [])
| Builtin BclDateTimeOffset -> Helper.LibCall(com, "DateTimeOffset", "zero", t, [])
Expand Down Expand Up @@ -875,10 +875,12 @@ let makeRustFormatString interpolated (fmt: string) =
| _ -> ""

let argFmt =
if g2 + g3 + g4 + g5 = "" then
let formatting = g2 + g3 + g4 + g5

if formatting = "" then
g1 + "{}"
else
g1 + "{:" + g2 + g3 + g4 + g5 + "}"
g1 + "{:" + formatting + "}"

argFmt
)
Expand Down Expand Up @@ -1400,8 +1402,8 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
Helper.LibCall(com, "String", methName, t, c :: args, ?loc = r) |> Some
| _ -> None
| "Insert", Some c, _ -> Helper.LibCall(com, "String", "insert", t, c :: args, ?loc = r) |> Some
| "IsNullOrEmpty", None, _ -> Helper.LibCall(com, "String", "isEmpty", t, args, ?loc = r) |> Some
| "IsNullOrWhiteSpace", None, _ -> Helper.LibCall(com, "String", "isWhitespace", t, args, ?loc = r) |> Some
| "IsNullOrEmpty", None, _ -> Helper.LibCall(com, "String", "isNullOrEmpty", t, args, ?loc = r) |> Some
| "IsNullOrWhiteSpace", None, _ -> Helper.LibCall(com, "String", "isNullOrWhitespace", t, args, ?loc = r) |> Some
| "Join", None, _ ->
let args =
match args with
Expand Down Expand Up @@ -1992,8 +1994,10 @@ let optionModule isStruct (com: ICompiler) (ctx: Context) r (t: Type) (i: CallIn
| "GetValue", [ c ] -> Get(c, OptionValue, t, r) |> Some
| "IsSome", [ c ] -> Test(c, OptionTest true, r) |> Some
| "IsNone", [ c ] -> Test(c, OptionTest false, r) |> Some
| "ToArray", [ arg ] -> Helper.LibCall(com, "Array", "ofOption", t, args, ?loc = r) |> Some
| "ToList", [ arg ] -> Helper.LibCall(com, "List", "ofOption", t, args, ?loc = r) |> Some
| "OfObj", [ arg ] -> Helper.LibCall(com, "Native", "ofObj", t, args, ?loc = r) |> Some
| "ToObj", [ arg ] -> Helper.LibCall(com, "Native", "toObj", t, args, ?loc = r) |> Some
// | "ToArray", [ arg ] -> Helper.LibCall(com, "Array", "ofOption", t, args, ?loc = r) |> Some
// | "ToList", [ arg ] -> Helper.LibCall(com, "List", "ofOption", t, args, ?loc = r) |> Some
| meth, args ->
Helper.LibCall(com, "Option", Naming.lowerFirst meth, t, args, i.SignatureArgTypes, ?loc = r)
|> Some
Expand Down
10 changes: 5 additions & 5 deletions src/fable-library-rust/src/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -893,11 +893,11 @@ let inline averageBy (projection: 'T -> 'U) (source: 'T[]) : 'U =

LanguagePrimitives.DivideByInt total source.Length

// Option.toArray redirects here to avoid dependency (see Replacements)
let ofOption<'T> (opt: 'T option) : 'T[] =
match opt with
| Some x -> Array.singleton x
| None -> Array.empty
// // Option.toArray redirects here to avoid dependency (see Replacements)
// let ofOption<'T> (opt: 'T option) : 'T[] =
// match opt with
// | Some x -> Array.singleton x
// | None -> Array.empty

// Redirected to List.toArray to avoid dependency (see Replacements)
// let ofList (xs: 'T list): 'T[] = List.toArray
Expand Down
42 changes: 37 additions & 5 deletions src/fable-library-rust/src/FuncType.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Native_::{referenceEquals, referenceHash, Lrc};
use crate::Native_::{referenceEquals, referenceHash, Lrc, NullableRef};

macro_rules! func {
($f:ident $(,$i:ident)*) => {
Expand All @@ -12,7 +12,38 @@ macro_rules! func {
#[derive(Clone)]
pub enum $f<$($i, )*R> {
Static(fn($($i, )*) -> R),
Shared(Option<Lrc<dyn Fn($($i, )*) -> R>>),
Shared(Lrc<dyn Fn($($i, )*) -> R>),
Null
}

#[cfg(not(feature = "enum_func"))]
impl<$($i, )*R> NullableRef for $f<$($i, )*R> {
#[inline]
fn null() -> Self {
$f(None)
}

#[inline]
fn is_null(&self) -> bool {
self.0.is_none()
}
}

#[cfg(feature = "enum_func")]
impl<$($i, )*R> NullableRef for $f<$($i, )*R> {
#[inline]
fn null() -> Self {
$f::Null
}

#[inline]
fn is_null(&self) -> bool {
match self {
$f::Static(f) => false,
$f::Shared(p) => false,
$f::Null => true,
}
}
}

impl<$($i, )*R> core::fmt::Debug for $f<$($i, )*R> {
Expand Down Expand Up @@ -53,7 +84,7 @@ macro_rules! func {
#[cfg(feature = "enum_func")]
impl<$($i, )*R> Default for $f<$($i, )*R> {
fn default() -> Self {
$f::Shared(None)
$f::Null
}
}

Expand All @@ -75,7 +106,8 @@ macro_rules! func {
fn deref(&self) -> &Self::Target {
match self {
$f::Static(f) => f,
$f::Shared(p) => p.as_ref().expect("Null reference exception.").as_ref(),
$f::Shared(p) => p.as_ref(),
$f::Null => panic!("Null reference exception."),
}
}
}
Expand All @@ -96,7 +128,7 @@ macro_rules! func {
$f::Static(f)
}
pub fn new<F: Fn($($i, )*) -> R + 'static>(f: F) -> Self {
$f::Shared(Some(Lrc::new(f) as Lrc<dyn Fn($($i, )*) -> R>))
$f::Shared(Lrc::new(f) as Lrc<dyn Fn($($i, )*) -> R>)
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/fable-library-rust/src/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ let last (xs: 'T list) =
| Some x -> x
| None -> failwith SR.inputListWasEmpty

// Option.toList redirects here to avoid dependency
let ofOption<'T> (opt: 'T option) : 'T list =
match opt with
| Some x -> singleton x
| None -> empty ()
// // Option.toList redirects here to avoid dependency
// let ofOption<'T> (opt: 'T option) : 'T list =
// match opt with
// | Some x -> singleton x
// | None -> empty ()

let ofSeq (xs: 'T seq) =
let mutable root = None
Expand Down
18 changes: 8 additions & 10 deletions src/fable-library-rust/src/LrcPtr.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::Native_::{Any, Lrc};
use crate::Native_::{Any, Lrc, NullableRef};
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
use core::ops::*;
use core::panic::{RefUnwindSafe, UnwindSafe};

// -----------------------------------------------------------
// LrcPtr
Expand All @@ -11,31 +12,28 @@ use core::ops::*;
#[repr(transparent)]
pub struct LrcPtr<T: ?Sized>(Option<Lrc<T>>);

impl<T: RefUnwindSafe + ?Sized> UnwindSafe for LrcPtr<T> {}
impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for LrcPtr<T> {}

impl<T> LrcPtr<T> {
#[inline]
pub fn new(value: T) -> Self {
LrcPtr(Some(Lrc::new(value)))
}
}

impl<T: ?Sized> LrcPtr<T> {
impl<T: ?Sized> NullableRef for LrcPtr<T> {
#[inline]
pub fn null() -> Self {
fn null() -> Self {
LrcPtr(None)
}

#[inline]
pub fn is_null(&self) -> bool {
fn is_null(&self) -> bool {
self.0.is_none()
}
}

// impl<T: ?Sized> Default for LrcPtr<T> {
// fn default() -> Self {
// Self::null()
// }
// }

impl<T: ?Sized> From<Lrc<T>> for LrcPtr<T> {
#[inline]
fn from(value: Lrc<T>) -> Self {
Expand Down
Loading

0 comments on commit e381f0a

Please sign in to comment.