diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index f8d7f1c80..d12d06acb 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -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 diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index d61905d0c..280808a7a 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -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 diff --git a/src/Fable.Transforms/Rust/Fable2Rust.fs b/src/Fable.Transforms/Rust/Fable2Rust.fs index 243c479bc..f57b55f72 100644 --- a/src/Fable.Transforms/Rust/Fable2Rust.fs +++ b/src/Fable.Transforms/Rust/Fable2Rust.fs @@ -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 @@ -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 = @@ -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 ] @@ -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 @@ -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 = @@ -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 -> [] @@ -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 diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 25100cf59..7f7082ebe 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -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 @@ -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, []) @@ -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 ) @@ -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 @@ -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 diff --git a/src/fable-library-rust/src/Array.fs b/src/fable-library-rust/src/Array.fs index 922bd457d..9a6c393c7 100644 --- a/src/fable-library-rust/src/Array.fs +++ b/src/fable-library-rust/src/Array.fs @@ -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 diff --git a/src/fable-library-rust/src/FuncType.rs b/src/fable-library-rust/src/FuncType.rs index 41ec66e7c..1bfa56d2a 100644 --- a/src/fable-library-rust/src/FuncType.rs +++ b/src/fable-library-rust/src/FuncType.rs @@ -1,4 +1,4 @@ -use crate::Native_::{referenceEquals, referenceHash, Lrc}; +use crate::Native_::{referenceEquals, referenceHash, Lrc, NullableRef}; macro_rules! func { ($f:ident $(,$i:ident)*) => { @@ -12,7 +12,38 @@ macro_rules! func { #[derive(Clone)] pub enum $f<$($i, )*R> { Static(fn($($i, )*) -> R), - Shared(Option R>>), + Shared(Lrc 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> { @@ -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 } } @@ -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."), } } } @@ -96,7 +128,7 @@ macro_rules! func { $f::Static(f) } pub fn new R + 'static>(f: F) -> Self { - $f::Shared(Some(Lrc::new(f) as Lrc R>)) + $f::Shared(Lrc::new(f) as Lrc R>) } } diff --git a/src/fable-library-rust/src/List.fs b/src/fable-library-rust/src/List.fs index 43379a4bb..14ea5cf7f 100644 --- a/src/fable-library-rust/src/List.fs +++ b/src/fable-library-rust/src/List.fs @@ -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 diff --git a/src/fable-library-rust/src/LrcPtr.rs b/src/fable-library-rust/src/LrcPtr.rs index 9540413e1..b18814f27 100644 --- a/src/fable-library-rust/src/LrcPtr.rs +++ b/src/fable-library-rust/src/LrcPtr.rs @@ -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 @@ -11,6 +12,9 @@ use core::ops::*; #[repr(transparent)] pub struct LrcPtr(Option>); +impl UnwindSafe for LrcPtr {} +impl RefUnwindSafe for LrcPtr {} + impl LrcPtr { #[inline] pub fn new(value: T) -> Self { @@ -18,24 +22,18 @@ impl LrcPtr { } } -impl LrcPtr { +impl NullableRef for LrcPtr { #[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 Default for LrcPtr { -// fn default() -> Self { -// Self::null() -// } -// } - impl From> for LrcPtr { #[inline] fn from(value: Lrc) -> Self { diff --git a/src/fable-library-rust/src/Native.rs b/src/fable-library-rust/src/Native.rs index b6cbbe7fd..d1b947b34 100644 --- a/src/fable-library-rust/src/Native.rs +++ b/src/fable-library-rust/src/Native.rs @@ -64,6 +64,61 @@ pub mod Native_ { pub type RefCell = LrcPtr>; pub type Nullable = Option; + // pub trait AsAny: Any { + // fn as_any(&self) -> &dyn Any; + // } + + // impl AsAny for T { + // #[inline(always)] + // fn as_any(&self) -> &dyn Any { + // self + // } + // } + + // pub trait Downcast: AsAny { + // #[inline] + // fn is(&self) -> bool { + // self.as_any().is::() + // } + + // #[inline] + // fn downcast_ref(&self) -> Option<&T> { + // self.as_any().downcast_ref::() + // } + // } + + // impl Downcast for T {} + + pub trait NullableRef { + fn null() -> Self; + fn is_null(&self) -> bool; + } + + #[cfg(not(feature = "lrc_ptr"))] + impl NullableRef for Lrc { + #[inline] + fn null() -> Self { + null_ptr::() + } + + #[inline] + fn is_null(&self) -> bool { + is_null_ptr(self) + } + } + + impl NullableRef for Option { + #[inline] + fn null() -> Self { + None:: + } + + #[inline] + fn is_null(&self) -> bool { + self.is_none() + } + } + use core::cmp::Ordering; use core::fmt::{Debug, Display, Formatter, Result}; use core::hash::{BuildHasher, Hash, Hasher}; @@ -77,43 +132,25 @@ pub mod Native_ { pub fn ignore(arg: &T) -> () {} - #[cfg(not(feature = "lrc_ptr"))] - pub fn null_ptr() -> *const T { - static NULL: OnceInit> = OnceInit::new(); - let null_rc = NULL.get_or_init(move || LrcPtr::new(())); - LrcPtr::into_raw(null_rc.clone()) as *const T + #[inline] + pub fn null() -> T { + T::null() } - #[cfg(not(feature = "lrc_ptr"))] - pub fn getNull() -> LrcPtr { - let nullPtr = null_ptr::(); - unsafe { LrcPtr::from_raw(nullPtr) } + #[inline] + pub fn is_null(o: T) -> bool { + o.is_null() } - #[cfg(not(feature = "lrc_ptr"))] - pub fn isNull(o: LrcPtr) -> bool { - let null_T: LrcPtr = getNull::<()>(); - LrcPtr::ptr_eq(&o, &null_T) - } - - // #[cfg(not(feature = "lrc_ptr"))] - // pub fn isNull(x: T) -> bool { - // if let Some(o) = (&x as &dyn Any).downcast_ref::>() { - // let null_T = getNull::(); - // LrcPtr::ptr_eq(o, &null_T) - // } else { - // false - // } - // } - - #[cfg(feature = "lrc_ptr")] - pub fn getNull() -> LrcPtr { - LrcPtr::null() + pub fn null_ptr() -> Lrc { + static NULL: OnceInit> = OnceInit::new(); + let null_rc = NULL.get_or_init(move || Lrc::new(())); + unsafe { Lrc::from_raw(Lrc::into_raw(null_rc.clone()) as *const T) } } - #[cfg(feature = "lrc_ptr")] - pub fn isNull(o: LrcPtr) -> bool { - o.is_null() + pub fn is_null_ptr(o: &Lrc) -> bool { + let null_T: Lrc = null_ptr::(); + Lrc::ptr_eq(o, &null_T) } pub fn getZero() -> T { @@ -380,40 +417,40 @@ pub mod Native_ { LrcPtr::new(MutCell::from(x)) } - #[inline] - pub fn box_(x: T) -> LrcPtr { - LrcPtr::new(x) + #[cfg(not(feature = "lrc_ptr"))] + pub fn box_(x: T) -> LrcPtr { + match (&x as &dyn Any).downcast_ref::>() { + Some(o) => o.clone(), + None => LrcPtr::new(x) as LrcPtr, + } + } + + #[cfg(feature = "lrc_ptr")] + pub fn box_(x: T) -> LrcPtr { + match (&x as &dyn Any).downcast_ref::>() { + Some(o) => o.clone(), + None => LrcPtr::from(Lrc::new(x) as Lrc), + } } - #[inline] pub fn unbox(o: LrcPtr) -> T { try_downcast::<_, T>(&o).unwrap().clone() } - // #[cfg(not(feature = "lrc_ptr"))] - // pub fn ofObj(value: LrcPtr) -> Option { - // if isNull(value.clone()) { - // None:: - // } else { - // Some(unbox::(value)) - // } - // } - - // #[cfg(feature = "lrc_ptr")] - // pub fn ofObj(value: LrcPtr) -> Option { - // if value.is_null() { - // None:: - // } else { - // Some(unbox::(value)) - // } - // } + pub fn ofObj(value: T) -> Option { + if is_null(value.clone()) { + None:: + } else { + Some(value) + } + } - // pub fn toObj(opt: Option) -> LrcPtr { - // match opt { - // Some(opt_0_0) => box_(opt_0_0), - // _ => getNull::(), - // } - // } + pub fn toObj(opt: Option) -> T { + match &opt { + Some(opt_0_0) => opt_0_0.clone(), + _ => null::(), + } + } // ----------------------------------------------------------- // Sequences diff --git a/src/fable-library-rust/src/NativeArray.rs b/src/fable-library-rust/src/NativeArray.rs index bf8c7650c..8244fe0b8 100644 --- a/src/fable-library-rust/src/NativeArray.rs +++ b/src/fable-library-rust/src/NativeArray.rs @@ -1,23 +1,35 @@ pub mod NativeArray_ { use crate::Global_::SR::indexOutOfBounds; use crate::Native_::{alloc, make_compare, mkRefMut, partial_compare, seq_to_iter}; - use crate::Native_::{Func1, Func2, LrcPtr, MutCell, Seq, Vec}; + use crate::Native_::{Func1, Func2, Lrc, LrcPtr, MutCell, NullableRef, Seq, Vec}; use crate::System::Collections::Generic::IComparer_1; // ----------------------------------------------------------- // Arrays // ----------------------------------------------------------- - type MutArray = MutCell>; + type MutArray = Lrc>>; #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] #[repr(transparent)] - pub struct Array(LrcPtr>); + pub struct Array(Option>); + + impl NullableRef for Array { + #[inline] + fn null() -> Self { + Array(None) + } + + #[inline] + fn is_null(&self) -> bool { + self.0.is_none() + } + } impl core::ops::Deref for Array { - type Target = LrcPtr>; + type Target = MutArray; fn deref(&self) -> &Self::Target { - &self.0 + &self.0.as_ref().expect("Null reference exception.") } } @@ -47,7 +59,7 @@ pub mod NativeArray_ { } pub fn array_from(v: Vec) -> Array { - Array(mkRefMut(v)) + Array(Some(Lrc::new(MutCell::from(v)))) } pub fn new_array(a: &[T]) -> Array { diff --git a/src/fable-library-rust/src/Option.fs b/src/fable-library-rust/src/Option.fs index 1fb44b0cf..d7df73949 100644 --- a/src/fable-library-rust/src/Option.fs +++ b/src/fable-library-rust/src/Option.fs @@ -135,15 +135,15 @@ let ofNullable (value: System.Nullable<'T>) = else None -let ofObj (value: 'T) = - match value with - | null -> None - | _ -> Some value - -let toObj (opt: 'T option) = - match opt with - | None -> null - | Some x -> box x +// let ofObj (value: 'T) = +// match box value with +// | null -> None +// | _ -> Some value + +// let toObj (opt: 'T option) = +// match opt with +// | None -> null +// | Some x -> box x let ofValueOption (voption: 'T voption) = match voption with diff --git a/src/fable-library-rust/src/String.rs b/src/fable-library-rust/src/String.rs index f54fe906c..28eef2062 100644 --- a/src/fable-library-rust/src/String.rs +++ b/src/fable-library-rust/src/String.rs @@ -5,7 +5,7 @@ pub mod String_ { // Strings // ----------------------------------------------------------- - use crate::Native_::{compare, Func1, Func2, Lrc, String, ToString, Vec}; + use crate::Native_::{compare, Func1, Func2, NullableRef, String, ToString, Vec}; use crate::NativeArray_::{array_from, Array}; use core::cmp::Ordering; @@ -16,35 +16,47 @@ pub mod String_ { // ----------------------------------------------------------- mod HeapString { - use crate::Native_::{Lrc, String}; + use crate::Native_::{Lrc, NullableRef, String}; #[repr(transparent)] #[derive(Clone)] - pub struct LrcStr(Lrc); + pub struct LrcStr(Option>); pub type string = LrcStr; + impl NullableRef for string { + #[inline] + fn null() -> Self { + LrcStr(None) + } + + #[inline] + fn is_null(&self) -> bool { + self.0.is_none() + } + } + impl string { pub fn as_str(&self) -> &str { - self.0.as_ref() + self.0.as_ref().expect("Null reference exception.") } } pub fn string(s: &'static str) -> string { - LrcStr(Lrc::from(s)) + LrcStr(Some(Lrc::from(s))) } pub fn fromSlice(s: &str) -> string { - LrcStr(Lrc::from(s)) + LrcStr(Some(Lrc::from(s))) } pub fn fromString(s: String) -> string { - LrcStr(Lrc::from(s)) + LrcStr(Some(Lrc::from(s))) } pub fn fromIter(iter: impl Iterator + Clone) -> string { let s = iter.collect::(); - LrcStr(Lrc::from(s)) + LrcStr(Some(Lrc::from(s))) } } @@ -54,7 +66,7 @@ pub mod String_ { // TODO: maybe intern strings, maybe add length in chars. mod EnumString { - use crate::Native_::{Lrc, String}; + use crate::Native_::{Lrc, NullableRef, String}; const INLINE_MAX: usize = 22; @@ -63,10 +75,26 @@ pub mod String_ { Static(&'static str), Inline { len: u8, buf: [u8; INLINE_MAX] }, Shared(Lrc), + Null, } pub type string = LrcStr; + impl NullableRef for string { + #[inline] + fn null() -> Self { + LrcStr::Null + } + + #[inline] + fn is_null(&self) -> bool { + match self { + LrcStr::Null => true, + _ => false, + } + } + } + impl string { pub fn as_str(&self) -> &str { match self { @@ -75,6 +103,7 @@ pub mod String_ { LrcStr::Inline { len, buf } => unsafe { core::str::from_utf8_unchecked(&buf[0..*len as usize]) }, + LrcStr::Null => { panic!("Null reference exception."); }, } } } @@ -246,7 +275,13 @@ pub mod String_ { impl PartialEq for string { #[inline] fn eq(&self, other: &Self) -> bool { - self.as_str().eq(other.as_str()) + match (self.is_null(), other.is_null()) { + (true, true) => true, + (true, false) => false, + (false, true) => false, + (false, false) => self.as_str().eq(other.as_str()), + } + } } @@ -255,34 +290,24 @@ pub mod String_ { impl PartialOrd for string { #[inline] fn partial_cmp(&self, other: &Self) -> Option { - self.as_str().partial_cmp(other.as_str()) - } - - #[inline] - fn lt(&self, other: &Self) -> bool { - self.as_str() < other.as_str() - } - - #[inline] - fn le(&self, other: &Self) -> bool { - self.as_str() <= other.as_str() - } - - #[inline] - fn gt(&self, other: &Self) -> bool { - self.as_str() > other.as_str() - } - - #[inline] - fn ge(&self, other: &Self) -> bool { - self.as_str() >= other.as_str() + match (self.is_null(), other.is_null()) { + (true, true) => Some(Ordering::Equal), + (true, false) => Some(Ordering::Less), + (false, true) => Some(Ordering::Greater), + (false, false) => self.as_str().partial_cmp(other.as_str()), + } } } impl Ord for string { #[inline] fn cmp(&self, other: &Self) -> Ordering { - self.as_str().cmp(other.as_str()) + match (self.is_null(), other.is_null()) { + (true, true) => Ordering::Equal, + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + (false, false) => self.as_str().cmp(other.as_str()), + } } } @@ -467,12 +492,12 @@ pub mod String_ { endsWith2(s, p, comparison) } - pub fn isEmpty(s: string) -> bool { - s.is_empty() + pub fn isNullOrEmpty(s: string) -> bool { + s.is_null() || s.is_empty() } - pub fn isWhitespace(s: string) -> bool { - s.trim().is_empty() + pub fn isNullOrWhitespace(s: string) -> bool { + s.is_null() || s.trim().is_empty() } pub fn trim(s: string) -> string {