Skip to content

Commit 4612e59

Browse files
authored
[Rust] Updated string comparisons (#3950)
1 parent f0bcb6c commit 4612e59

File tree

4 files changed

+165
-54
lines changed

4 files changed

+165
-54
lines changed

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Rust] Updated string comparisons (by @ncave)
1213
* [Rust] Fixed derived traits mapping (by @ncave)
1314
* [JS/TS] Added missing ICollection helpers (#3914) (by @ncave)
1415

src/Fable.Transforms/Rust/Replacements.fs

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,15 +1235,15 @@ let getEnumerator com r t i (expr: Expr) =
12351235
makeInstanceCall r t i expr "GetEnumerator" []
12361236

12371237
let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
1238-
let isIgnoreCase args =
1239-
match args with
1240-
| [] -> false
1241-
| [ BoolConst ignoreCase ] -> ignoreCase
1242-
| [ BoolConst ignoreCase; _cultureInfo ] -> ignoreCase
1243-
| [ NumberConst(NumberValue.Int32 kind, NumberInfo.IsEnum _) ] -> kind = 1 || kind = 3 || kind = 5
1244-
| [ _cultureInfo; NumberConst(NumberValue.Int32 options, NumberInfo.IsEnum _) ] ->
1245-
(options &&& 1 <> 0) || (options &&& 268435456 <> 0)
1246-
| _ -> false
1238+
// let isIgnoreCase args =
1239+
// match args with
1240+
// | [] -> false
1241+
// | [ BoolConst ignoreCase ] -> ignoreCase
1242+
// | [ BoolConst ignoreCase; _cultureInfo ] -> ignoreCase
1243+
// | [ NumberConst(NumberValue.Int32 kind, NumberInfo.IsEnum _) ] -> kind = 1 || kind = 3 || kind = 5
1244+
// | [ _cultureInfo; NumberConst(NumberValue.Int32 options, NumberInfo.IsEnum _) ] ->
1245+
// (options &&& 1 <> 0) || (options &&& 268435456 <> 0)
1246+
// | _ -> false
12471247

12481248
match i.CompiledName, thisArg, args with
12491249
| ".ctor", _, _ ->
@@ -1255,28 +1255,44 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
12551255
| _ -> None
12561256
| "get_Length", Some c, _ -> Helper.LibCall(com, "String", "length", t, c :: args, ?loc = r) |> Some
12571257
| "get_Chars", Some c, _ -> Helper.LibCall(com, "String", "getCharAt", t, c :: args, ?loc = r) |> Some
1258-
| ("Compare" | "CompareOrdinal"), None, _ ->
1259-
if i.CompiledName = "Compare" then
1260-
$"String.Compare will be compiled as String.CompareOrdinal"
1261-
|> addWarning com ctx.InlinePath r
1262-
1258+
| "CompareOrdinal", None, _ ->
12631259
match args with
1264-
| ExprType String :: ExprType String :: restArgs ->
1265-
let args = (args |> List.take 2) @ [ makeBoolConst (isIgnoreCase restArgs) ]
1266-
1260+
| [ ExprType String; ExprType String ] ->
12671261
Helper.LibCall(com, "String", "compareOrdinal", t, args, ?loc = r) |> Some
1268-
| ExprType String :: ExprType(Number(Int32, _)) :: ExprType String :: ExprType(Number(Int32, _)) :: ExprType(Number(Int32,
1269-
_)) :: restArgs ->
1270-
let args = (args |> List.take 5) @ [ makeBoolConst (isIgnoreCase restArgs) ]
1271-
1272-
Helper.LibCall(com, "String", "compareOrdinal2", t, args, ?loc = r) |> Some
1262+
| [ ExprType String
1263+
ExprType(Number(Int32, _))
1264+
ExprType String
1265+
ExprType(Number(Int32, _))
1266+
ExprType(Number(Int32, _)) ] -> Helper.LibCall(com, "String", "compareOrdinal2", t, args, ?loc = r) |> Some
12731267
| _ -> None
12741268
| "CompareTo", Some c, [ ExprTypeAs(String, arg) ] ->
12751269
$"String.CompareTo will be compiled as String.CompareOrdinal"
12761270
|> addWarning com ctx.InlinePath r
12771271

1278-
Helper.LibCall(com, "String", "compareOrdinal", t, [ c; arg; makeBoolConst false ], ?loc = r)
1279-
|> Some
1272+
Helper.LibCall(com, "String", "compareOrdinal", t, [ c; arg ], ?loc = r) |> Some
1273+
| "Compare", None, _ ->
1274+
$"String.Compare will be compiled as String.CompareOrdinal"
1275+
|> addWarning com ctx.InlinePath r
1276+
1277+
match args with
1278+
| [ ExprType String; ExprType String ] ->
1279+
Helper.LibCall(com, "String", "compareOrdinal", t, args, ?loc = r) |> Some
1280+
| ExprType String :: ExprType String :: ExprType Boolean :: restArgs ->
1281+
Helper.LibCall(com, "String", "compareCase", t, args, ?loc = r) |> Some
1282+
| [ ExprType String; ExprType String; comparison ] ->
1283+
Helper.LibCall(com, "String", "compareWith", t, args, ?loc = r) |> Some
1284+
| [ ExprType String
1285+
ExprType(Number(Int32, _))
1286+
ExprType String
1287+
ExprType(Number(Int32, _))
1288+
ExprType(Number(Int32, _)) ] -> Helper.LibCall(com, "String", "compareOrdinal2", t, args, ?loc = r) |> Some
1289+
| ExprType String :: ExprType(Number(Int32, _)) :: ExprType String :: ExprType(Number(Int32, _)) :: ExprType(Number(Int32,
1290+
_)) :: ExprType Boolean :: restArgs ->
1291+
Helper.LibCall(com, "String", "compareCase2", t, args, ?loc = r) |> Some
1292+
| ExprType String :: ExprType(Number(Int32, _)) :: ExprType String :: ExprType(Number(Int32, _)) :: ExprType(Number(Int32,
1293+
_)) :: comparison :: restArgs ->
1294+
Helper.LibCall(com, "String", "compareWith2", t, args, ?loc = r) |> Some
1295+
| _ -> None
12801296
| "Concat", None, _ ->
12811297
match args with
12821298
| [ ExprTypeAs(IEnumerable, arg) ] ->
@@ -1292,32 +1308,28 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
12921308
| "Contains", Some c, _ ->
12931309
match args with
12941310
| [ ExprType Char ] -> Helper.LibCall(com, "String", "containsChar", t, c :: args, ?loc = r) |> Some
1311+
| [ ExprType Char; _comparison ] ->
1312+
Helper.LibCall(com, "String", "containsChar2", t, c :: args, ?loc = r) |> Some
12951313
| [ ExprType String ] -> Helper.LibCall(com, "String", "contains", t, c :: args, ?loc = r) |> Some
1314+
| [ ExprType String; _comparison ] -> Helper.LibCall(com, "String", "contains2", t, c :: args, ?loc = r) |> Some
12961315
| _ -> None
12971316
| "EndsWith", Some c, _ ->
12981317
match args with
12991318
| [ ExprType Char ] -> Helper.LibCall(com, "String", "endsWithChar", t, c :: args, ?loc = r) |> Some
1300-
| ExprType String :: restArgs ->
1301-
let args = (args |> List.take 1) @ [ makeBoolConst (isIgnoreCase restArgs) ]
1302-
1303-
Helper.LibCall(com, "String", "endsWith", t, c :: args, ?loc = r) |> Some
1319+
| [ ExprType String ] -> Helper.LibCall(com, "String", "endsWith", t, c :: args, ?loc = r) |> Some
1320+
| [ ExprType String; _comparison ] -> Helper.LibCall(com, "String", "endsWith2", t, c :: args, ?loc = r) |> Some
1321+
| [ pattern; ignoreCase; _culture ] ->
1322+
Helper.LibCall(com, "String", "endsWith3", t, [ c; pattern; ignoreCase ], ?loc = r)
1323+
|> Some
13041324
| _ -> None
13051325
| "Equals", _, _ ->
13061326
match thisArg, args with
13071327
| Some x, [ ExprTypeAs(String, y) ]
13081328
| None, [ ExprTypeAs(String, x); ExprTypeAs(String, y) ] ->
1309-
Helper.LibCall(com, "String", "equalsOrdinal", t, [ x; y; makeBoolConst false ], ?loc = r)
1310-
|> Some
1311-
| Some x, [ ExprTypeAs(String, y); NumberConst(NumberValue.Int32 kind, NumberInfo.IsEnum _) ]
1312-
| None,
1313-
[ ExprTypeAs(String, x); ExprTypeAs(String, y); NumberConst(NumberValue.Int32 kind, NumberInfo.IsEnum _) ] ->
1314-
if kind <> 4 && kind <> 5 then
1315-
$"String.Equals will be compiled with ordinal equality"
1316-
|> addWarning com ctx.InlinePath r
1317-
1318-
let ignoreCase = kind = 1 || kind = 3 || kind = 5
1319-
1320-
Helper.LibCall(com, "String", "equalsOrdinal", t, [ x; y; makeBoolConst ignoreCase ], ?loc = r)
1329+
Helper.LibCall(com, "String", "equalsOrdinal", t, [ x; y ], ?loc = r) |> Some
1330+
| Some x, [ ExprTypeAs(String, y); comparison ]
1331+
| None, [ ExprTypeAs(String, x); ExprTypeAs(String, y); comparison ] ->
1332+
Helper.LibCall(com, "String", "equals2", t, [ x; y; comparison ], ?loc = r)
13211333
|> Some
13221334
| _ -> None
13231335
| "Format", None, _ ->
@@ -1487,10 +1499,12 @@ let strings (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opt
14871499
| "StartsWith", Some c, _ ->
14881500
match args with
14891501
| [ ExprType Char ] -> Helper.LibCall(com, "String", "startsWithChar", t, c :: args, ?loc = r) |> Some
1490-
| ExprType String :: restArgs ->
1491-
let args = (args |> List.take 1) @ [ makeBoolConst (isIgnoreCase restArgs) ]
1492-
1493-
Helper.LibCall(com, "String", "startsWith", t, c :: args, ?loc = r) |> Some
1502+
| [ ExprType String ] -> Helper.LibCall(com, "String", "startsWith", t, c :: args, ?loc = r) |> Some
1503+
| [ ExprType String; _comparison ] ->
1504+
Helper.LibCall(com, "String", "startsWith2", t, c :: args, ?loc = r) |> Some
1505+
| [ pattern; ignoreCase; _culture ] ->
1506+
Helper.LibCall(com, "String", "startsWith3", t, [ c; pattern; ignoreCase ], ?loc = r)
1507+
|> Some
14941508
| _ -> None
14951509
| "Substring", Some c, _ ->
14961510
match args with

src/fable-library-rust/src/String.rs

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -339,60 +339,134 @@ pub mod String_ {
339339
fromIter(a.iter().copied().skip(i as usize).take(count as usize))
340340
}
341341

342+
// pub mod StringComparison {
343+
// pub const CurrentCulture: i32 = 0;
344+
// pub const CurrentCultureIgnoreCase: i32 = 1;
345+
// pub const InvariantCulture: i32 = 2;
346+
// pub const InvariantCultureIgnoreCase: i32 = 3;
347+
// pub const Ordinal: i32 = 4;
348+
// pub const OrdinalIgnoreCase: i32 = 5;
349+
// }
350+
351+
fn isIgnoreCase(comparison: i32) -> bool {
352+
comparison == 1 || comparison == 3 || comparison == 5
353+
}
354+
342355
pub fn containsChar(s: string, c: char) -> bool {
343356
s.contains(c)
344357
}
345358

359+
pub fn containsChar2(s: string, c: char, comparison: i32) -> bool {
360+
if isIgnoreCase(comparison) {
361+
let mut buf = [0u8; 4];
362+
let c_str = c.encode_utf8(&mut buf);
363+
s.to_uppercase().contains(&c_str.to_uppercase())
364+
} else {
365+
s.contains(c)
366+
}
367+
}
368+
346369
pub fn contains(s: string, p: string) -> bool {
347370
s.contains(p.as_str())
348371
}
349372

350-
pub fn equalsOrdinal(s1: string, s2: string, ignoreCase: bool) -> bool {
351-
if ignoreCase {
373+
pub fn contains2(s: string, p: string, comparison: i32) -> bool {
374+
if isIgnoreCase(comparison) {
375+
s.to_uppercase().contains(&p.to_uppercase())
376+
} else {
377+
s.contains(p.as_str())
378+
}
379+
}
380+
381+
pub fn equalsOrdinal(s1: string, s2: string) -> bool {
382+
s1.eq(&s2)
383+
}
384+
385+
pub fn equals2(s1: string, s2: string, comparison: i32) -> bool {
386+
if isIgnoreCase(comparison) {
352387
s1.to_uppercase().eq(&s2.to_uppercase())
353388
} else {
354389
s1.eq(&s2)
355390
}
356391
}
357392

358-
pub fn compareOrdinal(s1: string, s2: string, ignoreCase: bool) -> i32 {
359-
if ignoreCase {
393+
pub fn compareOrdinal(s1: string, s2: string) -> i32 {
394+
compare(&s1, &s2)
395+
}
396+
397+
pub fn compareOrdinal2(s1: string, i1: i32, s2: string, i2: i32, count: i32) -> i32 {
398+
let s1 = substring2(s1, i1, count);
399+
let s2 = substring2(s2, i2, count);
400+
compareOrdinal(s1, s2)
401+
}
402+
403+
pub fn compareWith(s1: string, s2: string, comparison: i32) -> i32 {
404+
if isIgnoreCase(comparison) {
360405
compare(&s1.to_uppercase(), &s2.to_uppercase())
361406
} else {
362407
compare(&s1, &s2)
363408
}
364409
}
365410

366-
pub fn compareOrdinal2(s1: string, i1: i32, s2: string, i2: i32, count: i32, ignoreCase: bool) -> i32 {
411+
pub fn compareWith2(s1: string, i1: i32, s2: string, i2: i32, count: i32, comparison: i32) -> i32 {
412+
let s1 = substring2(s1, i1, count);
413+
let s2 = substring2(s2, i2, count);
414+
compareWith(s1, s2, comparison)
415+
}
416+
417+
pub fn compareCase(s1: string, s2: string, ignoreCase: bool) -> i32 {
418+
let comparison = if ignoreCase { 5 } else { 4 };
419+
compareWith(s1, s2, comparison)
420+
}
421+
422+
pub fn compareCase2(s1: string, i1: i32, s2: string, i2: i32, count: i32, ignoreCase: bool) -> i32 {
367423
let s1 = substring2(s1, i1, count);
368424
let s2 = substring2(s2, i2, count);
369-
compareOrdinal(s1, s2, ignoreCase)
425+
compareCase(s1, s2, ignoreCase)
370426
}
371427

372428
pub fn startsWithChar(s: string, c: char) -> bool {
373429
s.starts_with(c)
374430
}
375431

376-
pub fn startsWith(s: string, p: string, ignoreCase: bool) -> bool {
377-
if ignoreCase {
432+
pub fn startsWith(s: string, p: string) -> bool {
433+
s.starts_with(p.as_str())
434+
}
435+
436+
pub fn startsWith2(s: string, p: string, comparison: i32) -> bool {
437+
if isIgnoreCase(comparison) {
378438
s.to_uppercase().starts_with(&p.to_uppercase())
379439
} else {
380440
s.starts_with(p.as_str())
381441
}
382442
}
383443

444+
pub fn startsWith3(s: string, p: string, ignoreCase: bool) -> bool {
445+
let comparison = if ignoreCase { 5 } else { 4 };
446+
startsWith2(s, p, comparison)
447+
}
448+
384449
pub fn endsWithChar(s: string, c: char) -> bool {
385450
s.ends_with(c)
386451
}
387452

388-
pub fn endsWith(s: string, p: string, ignoreCase: bool) -> bool {
389-
if ignoreCase {
453+
pub fn endsWith(s: string, p: string) -> bool {
454+
s.ends_with(p.as_str())
455+
}
456+
457+
pub fn endsWith2(s: string, p: string, comparison: i32) -> bool {
458+
if isIgnoreCase(comparison) {
390459
s.to_uppercase().ends_with(&p.to_uppercase())
391460
} else {
392461
s.ends_with(p.as_str())
393462
}
394463
}
395464

465+
pub fn endsWith3(s: string, p: string, ignoreCase: bool) -> bool {
466+
let comparison = if ignoreCase { 5 } else { 4 };
467+
endsWith2(s, p, comparison)
468+
}
469+
396470
pub fn isEmpty(s: string) -> bool {
397471
s.is_empty()
398472
}

tests/Rust/tests/src/StringTests.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,11 @@ let ``String.Compare substring works`` () =
813813
String.Compare("abc", 0, "bcd", 0, 3) |> equal -1
814814
String.Compare("abc", 1, "bcd", 0, 2) |> equal 0
815815

816+
[<Fact>]
817+
let ``String.Compare substring case-insensitive works`` () =
818+
String.Compare("ABC", 0, "bcd", 0, 3, true) |> equal -1
819+
String.Compare("ABC", 1, "bcd", 0, 2, true) |> equal 0
820+
816821
[<Fact>]
817822
let ``String.Compare with comparison works`` () =
818823
// String.Compare("ABC", "abc", StringComparison.InvariantCulture) > 0 |> equal true
@@ -846,6 +851,23 @@ let ``String.Contains works`` () =
846851
"ABC".Contains("B") |> equal true
847852
"ABC".Contains("Z") |> equal false
848853

854+
[<Fact>]
855+
let ``String.Contains with char works`` () =
856+
"ABC".Contains('B') |> equal true
857+
"ABC".Contains('Z') |> equal false
858+
859+
[<Fact>]
860+
let ``String.Contains with comparison works`` () =
861+
"ABC".Contains("b", StringComparison.OrdinalIgnoreCase) |> equal true
862+
"Abc".Contains("B", StringComparison.OrdinalIgnoreCase) |> equal true
863+
"Abc".Contains("B", StringComparison.Ordinal) |> equal false
864+
865+
[<Fact>]
866+
let ``String.Contains with char comparison works`` () =
867+
"ABC".Contains('b', StringComparison.OrdinalIgnoreCase) |> equal true
868+
"Abc".Contains('B', StringComparison.OrdinalIgnoreCase) |> equal true
869+
"Abc".Contains('B', StringComparison.Ordinal) |> equal false
870+
849871
[<Fact>]
850872
let ``String.PadLeft works`` () =
851873
"3.14".PadLeft(10) |> equal " 3.14"

0 commit comments

Comments
 (0)