Skip to content

Commit 6f97e3c

Browse files
committed
Add GString == &str equality operator
Easier than converting to String/GString, and requires no allocation.
1 parent 3416265 commit 6f97e3c

File tree

10 files changed

+71
-51
lines changed

10 files changed

+71
-51
lines changed

godot-core/src/builtin/string/gstring.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,21 @@ impl fmt::Debug for GString {
336336
}
337337
}
338338

339+
// ----------------------------------------------------------------------------------------------------------------------------------------------
340+
// Comparison with Rust strings
341+
342+
// API design:
343+
// * StringName and NodePath don't implement PartialEq<&str> yet, because they require allocation (convert to GString).
344+
// == should ideally not allocate.
345+
// * Reverse `impl PartialEq<GString> for &str` is not implemented now. Comparisons usually take the form of variable == "literal".
346+
// Can be added later if there are good use-cases.
347+
348+
impl PartialEq<&str> for GString {
349+
fn eq(&self, other: &&str) -> bool {
350+
self.chars().iter().copied().eq(other.chars())
351+
}
352+
}
353+
339354
// ----------------------------------------------------------------------------------------------------------------------------------------------
340355
// Conversion from/into Rust string-types
341356

itest/rust/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fn collect_inputs() -> Vec<Input> {
120120
push!(inputs; float, f64, 127.83156478);
121121
push!(inputs; bool, bool, true);
122122
push!(inputs; Color, Color, Color(0.7, 0.5, 0.3, 0.2), Color::from_rgba(0.7, 0.5, 0.3, 0.2));
123-
push!(inputs; String, GString, "hello", "hello".into());
123+
push!(inputs; String, GString, "hello", GString::from("hello"));
124124
push!(inputs; StringName, StringName, &"hello", "hello".into());
125125
pushs!(inputs; NodePath, NodePath, r#"^"hello""#, "hello".into(), true, true, None);
126126
push!(inputs; Vector2, Vector2, Vector2(12.5, -3.5), Vector2::new(12.5, -3.5));
@@ -170,7 +170,7 @@ fn collect_inputs() -> Vec<Input> {
170170
push_newtype!(inputs; float, NewF64(f64), 127.83156478);
171171
push_newtype!(inputs; bool, NewBool(bool), true);
172172
push_newtype!(inputs; Color, NewColor(Color), Color(0.7, 0.5, 0.3, 0.2), NewColor(Color::from_rgba(0.7, 0.5, 0.3, 0.2)));
173-
push_newtype!(inputs; String, NewString(GString), "hello", NewString("hello".into()));
173+
push_newtype!(inputs; String, NewString(GString), "hello", NewString(GString::from("hello")));
174174
push_newtype!(inputs; StringName, NewStringName(StringName), &"hello", NewStringName("hello".into()));
175175
push_newtype!(@s inputs; NodePath, NewNodePath(NodePath), r#"^"hello""#, NewNodePath("hello".into()));
176176
push_newtype!(inputs; Vector2, NewVector2(Vector2), Vector2(12.5, -3.5), NewVector2(Vector2::new(12.5, -3.5)));

itest/rust/src/builtin_tests/containers/packed_array_test.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ fn packed_array_index() {
107107
array.push("first");
108108
array.push(&GString::from("second"));
109109

110-
assert_eq!(array[0], "first".into());
111-
assert_eq!(array[1], "second".into());
110+
assert_eq!(array[0], "first");
111+
assert_eq!(array[1], "second");
112112

113113
array[0] = GString::from("begin");
114-
assert_eq!(array[0], "begin".into());
114+
assert_eq!(array[0], "begin");
115115
}
116116

117117
#[itest]
@@ -202,7 +202,7 @@ fn packed_array_push() {
202202
let mut strings = PackedStringArray::from(&[GString::from("a")]);
203203
strings.push("b");
204204
assert_eq!(strings.len(), 2);
205-
assert_eq!(strings[1], "b".into());
205+
assert_eq!(strings[1], "b");
206206

207207
fn test<T: Generator>() {
208208
let mut array = PackedArray::<T>::new();
@@ -481,7 +481,7 @@ fn packed_array_as_slice() {
481481
);
482482

483483
let empty = PackedStringArray::new();
484-
assert_eq!(empty.as_slice(), &[]);
484+
assert_eq!(empty.as_slice(), &[] as &[GString]); // Ambiguity due to GString==&str op. Could use is_empty() but this uses explicit type.
485485
}
486486

487487
#[itest]
@@ -501,7 +501,7 @@ fn packed_array_as_mut_slice() {
501501
);
502502

503503
let mut empty = PackedStringArray::new();
504-
assert_eq!(empty.as_mut_slice(), &mut []);
504+
assert_eq!(empty.as_mut_slice(), &mut [] as &mut [GString]);
505505
}
506506

507507
// ----------------------------------------------------------------------------------------------------------------------------------------------

itest/rust/src/builtin_tests/string/gstring_test.rs

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ fn string_equality() {
4141
assert_ne!(string, different);
4242
}
4343

44+
#[itest]
45+
fn string_eq_str() {
46+
let gstring = GString::from("hello");
47+
assert_eq!(gstring, "hello");
48+
assert_ne!(gstring, "hallo");
49+
}
50+
4451
#[itest]
4552
fn string_ordering() {
4653
let low = GString::from("Alpha");
@@ -130,12 +137,12 @@ fn string_with_null() {
130137
#[itest]
131138
fn string_substr() {
132139
let string = GString::from("stable");
133-
assert_eq!(string.substr(..), "stable".into());
134-
assert_eq!(string.substr(1..), "table".into());
135-
assert_eq!(string.substr(..4), "stab".into());
136-
assert_eq!(string.substr(..=3), "stab".into());
137-
assert_eq!(string.substr(2..5), "abl".into());
138-
assert_eq!(string.substr(2..=4), "abl".into());
140+
assert_eq!(string.substr(..), "stable");
141+
assert_eq!(string.substr(1..), "table");
142+
assert_eq!(string.substr(..4), "stab");
143+
assert_eq!(string.substr(..=3), "stab");
144+
assert_eq!(string.substr(2..5), "abl");
145+
assert_eq!(string.substr(2..=4), "abl");
139146
}
140147

141148
#[itest]
@@ -217,42 +224,42 @@ fn gstring_erase() {
217224
let s = GString::from("Hello World");
218225
assert_eq!(s.erase(..), GString::new());
219226
assert_eq!(s.erase(4..4), s);
220-
assert_eq!(s.erase(2..=2), "Helo World".into());
221-
assert_eq!(s.erase(1..=3), "Ho World".into());
222-
assert_eq!(s.erase(1..4), "Ho World".into());
223-
assert_eq!(s.erase(..6), "World".into());
224-
assert_eq!(s.erase(5..), "Hello".into());
227+
assert_eq!(s.erase(2..=2), "Helo World");
228+
assert_eq!(s.erase(1..=3), "Ho World");
229+
assert_eq!(s.erase(1..4), "Ho World");
230+
assert_eq!(s.erase(..6), "World");
231+
assert_eq!(s.erase(5..), "Hello");
225232
}
226233

227234
#[itest]
228235
fn gstring_insert() {
229236
let s = GString::from("H World");
230-
assert_eq!(s.insert(1, "i"), "Hi World".into());
231-
assert_eq!(s.insert(1, "ello"), "Hello World".into());
232-
assert_eq!(s.insert(7, "."), "H World.".into());
233-
assert_eq!(s.insert(0, "¿"), "¿H World".into());
237+
assert_eq!(s.insert(1, "i"), "Hi World");
238+
assert_eq!(s.insert(1, "ello"), "Hello World");
239+
assert_eq!(s.insert(7, "."), "H World.");
240+
assert_eq!(s.insert(0, "¿"), "¿H World");
234241

235242
// Special behavior in Godot, but maybe the idea is to allow large constants to mean "end".
236-
assert_eq!(s.insert(123, "!"), "H World!".into());
243+
assert_eq!(s.insert(123, "!"), "H World!");
237244
}
238245

239246
#[itest]
240247
fn gstring_pad() {
241248
let s = GString::from("123");
242-
assert_eq!(s.lpad(5, '0'), "00123".into());
243-
assert_eq!(s.lpad(2, ' '), "123".into());
244-
assert_eq!(s.lpad(4, ' '), " 123".into());
249+
assert_eq!(s.lpad(5, '0'), "00123");
250+
assert_eq!(s.lpad(2, ' '), "123");
251+
assert_eq!(s.lpad(4, ' '), " 123");
245252

246-
assert_eq!(s.rpad(5, '+'), "123++".into());
247-
assert_eq!(s.rpad(2, ' '), "123".into());
248-
assert_eq!(s.rpad(4, ' '), "123 ".into());
253+
assert_eq!(s.rpad(5, '+'), "123++");
254+
assert_eq!(s.rpad(2, ' '), "123");
255+
assert_eq!(s.rpad(4, ' '), "123 ");
249256

250257
let s = GString::from("123.456");
251-
assert_eq!(s.pad_decimals(5), "123.45600".into());
252-
assert_eq!(s.pad_decimals(2), "123.45".into()); // note: Godot rounds down
258+
assert_eq!(s.pad_decimals(5), "123.45600");
259+
assert_eq!(s.pad_decimals(2), "123.45"); // note: Godot rounds down
253260

254-
assert_eq!(s.pad_zeros(5), "00123.456".into());
255-
assert_eq!(s.pad_zeros(2), "123.456".into());
261+
assert_eq!(s.pad_zeros(5), "00123.456");
262+
assert_eq!(s.pad_zeros(2), "123.456");
256263
}
257264

258265
// Byte and C-string conversions.

itest/rust/src/engine_tests/match_class_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ fn match_class_named_fallback_matched() {
126126
// Named fallback with access to original object.
127127
other => {
128128
require_object(&other);
129-
assert_eq!(other.get_class(), "Resource".into());
129+
assert_eq!(other.get_class(), "Resource");
130130
3
131131
}
132132
};
@@ -145,7 +145,7 @@ fn match_class_named_mut_fallback_matched() {
145145
// Named fallback with access to original object.
146146
mut other => {
147147
require_mut_object(&mut other);
148-
assert_eq!(other.get_class(), "Resource".into());
148+
assert_eq!(other.get_class(), "Resource");
149149
3
150150
}
151151
};

itest/rust/src/engine_tests/utilities_test.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ fn utilities_str() {
3939

4040
let empty = str(&[]);
4141

42-
// TODO: implement GString==&str operator. Then look for "...".into() patterns and replace them.
43-
assert_eq!(concat, "12 is a true number".into());
42+
assert_eq!(concat, "12 is a true number");
4443
assert_eq!(concat, godot_str!("{a}{b}{c}{d}"));
4544
assert_eq!(empty, GString::new());
4645
}

itest/rust/src/object_tests/base_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ fn base_refcounted_weak_reference() {
176176

177177
// Call an API to ensure Base is functional.
178178
let class_name = base_guard.get_class();
179-
assert_eq!(class_name, "RefcBased".into());
179+
assert_eq!(class_name, "RefcBased");
180180
}
181181

182182
let final_refcount = obj.get_reference_count();

itest/rust/src/object_tests/property_test.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -414,14 +414,11 @@ fn derive_property() {
414414
fn enum_var_hint() {
415415
let int_prop = <Behavior as Var>::var_hint();
416416
assert_eq!(int_prop.hint, PropertyHint::ENUM);
417-
assert_eq!(
418-
int_prop.hint_string,
419-
"Peaceful:0,Defend:1,Aggressive:7".into()
420-
);
417+
assert_eq!(int_prop.hint_string, "Peaceful:0,Defend:1,Aggressive:7");
421418

422419
let str_prop = <StrBehavior as Var>::var_hint();
423420
assert_eq!(str_prop.hint, PropertyHint::ENUM);
424-
assert_eq!(str_prop.hint_string, "Peaceful,Defend,Aggressive".into());
421+
assert_eq!(str_prop.hint_string, "Peaceful,Defend,Aggressive");
425422
}
426423

427424
#[derive(GodotClass)]

itest/rust/src/register_tests/derive_godotconvert_test.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ fn enum_stringy() {
7777
roundtrip(EnumStringy::E);
7878
roundtrip(EnumStringy::F);
7979

80-
assert_eq!(EnumStringy::A.to_godot(), "A".into());
81-
assert_eq!(EnumStringy::B.to_godot(), "B".into());
82-
assert_eq!(EnumStringy::C.to_godot(), "C".into());
83-
assert_eq!(EnumStringy::D.to_godot(), "D".into());
84-
assert_eq!(EnumStringy::E.to_godot(), "E".into());
85-
assert_eq!(EnumStringy::F.to_godot(), "F".into());
80+
assert_eq!(EnumStringy::A.to_godot(), "A");
81+
assert_eq!(EnumStringy::B.to_godot(), "B");
82+
assert_eq!(EnumStringy::C.to_godot(), "C");
83+
assert_eq!(EnumStringy::D.to_godot(), "D");
84+
assert_eq!(EnumStringy::E.to_godot(), "E");
85+
assert_eq!(EnumStringy::F.to_godot(), "F");
8686

8787
// Rust-side discriminants.
8888
assert_eq!(EnumStringy::A as isize, 0);

itest/rust/src/register_tests/gdscript_ffi_test.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
#![allow(dead_code)]
99

10+
// While some of these lints are legitimate, no one cares for this generated code, and it's definitely not worth complicating the generator.
1011
#[rustfmt::skip]
11-
#[allow(clippy::partialeq_to_none)]
12+
#[allow(clippy::partialeq_to_none)] // i == None -> i.is_none()
13+
#[allow(clippy::cmp_owned)] // i == GString::from("hello") -> i == "hello"
1214
pub mod gen_ffi {
1315
include!(concat!(env!("OUT_DIR"), "/gen_ffi.rs"));
1416
}

0 commit comments

Comments
 (0)