Skip to content
Open
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
15 changes: 15 additions & 0 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,21 @@ impl fmt::Debug for GString {
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Comparison with Rust strings

// API design:
// * StringName and NodePath don't implement PartialEq<&str> yet, because they require allocation (convert to GString).
// == should ideally not allocate.
// * Reverse `impl PartialEq<GString> for &str` is not implemented now. Comparisons usually take the form of variable == "literal".
// Can be added later if there are good use-cases.

impl PartialEq<&str> for GString {
fn eq(&self, other: &&str) -> bool {
self.chars().iter().copied().eq(other.chars())
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Conversion from/into Rust string-types

Expand Down
17 changes: 17 additions & 0 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,23 @@ impl_shared_string_api! {
split_builder: ExStringNameSplit,
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Comparison with Rust strings

// API design: see PartialEq for GString.
impl PartialEq<&str> for StringName {
#[cfg(since_api = "4.5")]
fn eq(&self, other: &&str) -> bool {
self.chars().iter().copied().eq(other.chars())
}

// Polyfill for older Godot versions -- StringName->GString conversion still requires allocation in older versions.
#[cfg(before_api = "4.5")]
fn eq(&self, other: &&str) -> bool {
GString::from(self) == *other
}
}

impl fmt::Display for StringName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = GString::from(self);
Expand Down
2 changes: 1 addition & 1 deletion godot-core/src/obj/on_ready.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ use crate::registry::property::Var;
/// impl INode for MyClass {
/// fn ready(&mut self) {
/// // self.node is now ready with the node found at path `ChildPath`.
/// assert_eq!(self.auto.get_name(), "ChildPath".into());
/// assert_eq!(self.auto.get_name(), "ChildPath");
///
/// // self.manual needs to be initialized manually.
/// self.manual.init(22);
Expand Down
12 changes: 6 additions & 6 deletions itest/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ fn collect_inputs() -> Vec<Input> {
push!(inputs; float, f64, 127.83156478);
push!(inputs; bool, bool, true);
push!(inputs; Color, Color, Color(0.7, 0.5, 0.3, 0.2), Color::from_rgba(0.7, 0.5, 0.3, 0.2));
push!(inputs; String, GString, "hello", "hello".into());
push!(inputs; StringName, StringName, &"hello", "hello".into());
pushs!(inputs; NodePath, NodePath, r#"^"hello""#, "hello".into(), true, true, None);
push!(inputs; String, GString, "hello", GString::from("hello"));
push!(inputs; StringName, StringName, &"hello", StringName::from("hello"));
pushs!(inputs; NodePath, NodePath, r#"^"hello""#, NodePath::from("hello"), true, true, None);
push!(inputs; Vector2, Vector2, Vector2(12.5, -3.5), Vector2::new(12.5, -3.5));
push!(inputs; Vector3, Vector3, Vector3(117.5, 100.0, -323.25), Vector3::new(117.5, 100.0, -323.25));
push!(inputs; Vector4, Vector4, Vector4(-18.5, 24.75, -1.25, 777.875), Vector4::new(-18.5, 24.75, -1.25, 777.875));
Expand Down Expand Up @@ -170,9 +170,9 @@ fn collect_inputs() -> Vec<Input> {
push_newtype!(inputs; float, NewF64(f64), 127.83156478);
push_newtype!(inputs; bool, NewBool(bool), true);
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)));
push_newtype!(inputs; String, NewString(GString), "hello", NewString("hello".into()));
push_newtype!(inputs; StringName, NewStringName(StringName), &"hello", NewStringName("hello".into()));
push_newtype!(@s inputs; NodePath, NewNodePath(NodePath), r#"^"hello""#, NewNodePath("hello".into()));
push_newtype!(inputs; String, NewString(GString), "hello", NewString(GString::from("hello")));
push_newtype!(inputs; StringName, NewStringName(StringName), &"hello", NewStringName(StringName::from("hello")));
push_newtype!(@s inputs; NodePath, NewNodePath(NodePath), r#"^"hello""#, NewNodePath(NodePath::from("hello")));
push_newtype!(inputs; Vector2, NewVector2(Vector2), Vector2(12.5, -3.5), NewVector2(Vector2::new(12.5, -3.5)));
push_newtype!(inputs; Vector3, NewVector3(Vector3), Vector3(117.5, 100.0, -323.25), NewVector3(Vector3::new(117.5, 100.0, -323.25)));
push_newtype!(inputs; Vector4, NewVector4(Vector4), Vector4(-18.5, 24.75, -1.25, 777.875), NewVector4(Vector4::new(-18.5, 24.75, -1.25, 777.875)));
Expand Down
4 changes: 2 additions & 2 deletions itest/rust/src/builtin_tests/containers/array_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,10 +664,10 @@ func make_array() -> Array[CustomScriptForArrays]:
let script = script.script().expect("script object should be alive");
assert_eq!(script, gdscript.upcast());
assert_eq!(script.get_name(), GString::new()); // Resource name.
assert_eq!(script.get_instance_base_type(), "RefCounted".into());
assert_eq!(script.get_instance_base_type(), "RefCounted");

#[cfg(since_api = "4.3")]
assert_eq!(script.get_global_name(), "CustomScriptForArrays".into());
assert_eq!(script.get_global_name(), "CustomScriptForArrays");
}

// Test that proper type has been set&cached while creating new Array.
Expand Down
5 changes: 1 addition & 4 deletions itest/rust/src/builtin_tests/containers/dictionary_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,8 +822,5 @@ func variant_script_dict() -> Dictionary[Variant, CustomScriptForDictionaries]:
assert_match!(dict.value_element_type(), ElementType::ScriptClass(script));
let script = script.script().expect("script object should be alive");
assert_eq!(script, gdscript.upcast());
assert_eq!(
script.get_global_name(),
"CustomScriptForDictionaries".into()
);
assert_eq!(script.get_global_name(), "CustomScriptForDictionaries");
}
12 changes: 6 additions & 6 deletions itest/rust/src/builtin_tests/containers/packed_array_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ fn packed_array_index() {
array.push("first");
array.push(&GString::from("second"));

assert_eq!(array[0], "first".into());
assert_eq!(array[1], "second".into());
assert_eq!(array[0], "first");
assert_eq!(array[1], "second");

array[0] = GString::from("begin");
assert_eq!(array[0], "begin".into());
assert_eq!(array[0], "begin");
}

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

fn test<T: Generator>() {
let mut array = PackedArray::<T>::new();
Expand Down Expand Up @@ -481,7 +481,7 @@ fn packed_array_as_slice() {
);

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

#[itest]
Expand All @@ -501,7 +501,7 @@ fn packed_array_as_mut_slice() {
);

let mut empty = PackedStringArray::new();
assert_eq!(empty.as_mut_slice(), &mut []);
assert_eq!(empty.as_mut_slice(), &mut [] as &mut [GString]);
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ impl ScriptInstance for TestScriptInstance {
}

fn set_property(mut this: SiMut<Self>, name: StringName, value: &Variant) -> bool {
if name.to_string() == "script_property_b" {
if name == "script_property_b" {
this.script_property_b = FromGodot::from_variant(value);
true
} else {
Expand Down
59 changes: 33 additions & 26 deletions itest/rust/src/builtin_tests/string/gstring_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ fn string_equality() {
assert_ne!(string, different);
}

#[itest]
fn string_eq_str() {
let gstring = GString::from("hello");
assert_eq!(gstring, "hello");
assert_ne!(gstring, "hallo");
}

#[itest]
fn string_ordering() {
let low = GString::from("Alpha");
Expand Down Expand Up @@ -130,12 +137,12 @@ fn string_with_null() {
#[itest]
fn string_substr() {
let string = GString::from("stable");
assert_eq!(string.substr(..), "stable".into());
assert_eq!(string.substr(1..), "table".into());
assert_eq!(string.substr(..4), "stab".into());
assert_eq!(string.substr(..=3), "stab".into());
assert_eq!(string.substr(2..5), "abl".into());
assert_eq!(string.substr(2..=4), "abl".into());
assert_eq!(string.substr(..), "stable");
assert_eq!(string.substr(1..), "table");
assert_eq!(string.substr(..4), "stab");
assert_eq!(string.substr(..=3), "stab");
assert_eq!(string.substr(2..5), "abl");
assert_eq!(string.substr(2..=4), "abl");
}

#[itest]
Expand Down Expand Up @@ -217,42 +224,42 @@ fn gstring_erase() {
let s = GString::from("Hello World");
assert_eq!(s.erase(..), GString::new());
assert_eq!(s.erase(4..4), s);
assert_eq!(s.erase(2..=2), "Helo World".into());
assert_eq!(s.erase(1..=3), "Ho World".into());
assert_eq!(s.erase(1..4), "Ho World".into());
assert_eq!(s.erase(..6), "World".into());
assert_eq!(s.erase(5..), "Hello".into());
assert_eq!(s.erase(2..=2), "Helo World");
assert_eq!(s.erase(1..=3), "Ho World");
assert_eq!(s.erase(1..4), "Ho World");
assert_eq!(s.erase(..6), "World");
assert_eq!(s.erase(5..), "Hello");
}

#[itest]
fn gstring_insert() {
let s = GString::from("H World");
assert_eq!(s.insert(1, "i"), "Hi World".into());
assert_eq!(s.insert(1, "ello"), "Hello World".into());
assert_eq!(s.insert(7, "."), "H World.".into());
assert_eq!(s.insert(0, "¿"), "¿H World".into());
assert_eq!(s.insert(1, "i"), "Hi World");
assert_eq!(s.insert(1, "ello"), "Hello World");
assert_eq!(s.insert(7, "."), "H World.");
assert_eq!(s.insert(0, "¿"), "¿H World");

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

#[itest]
fn gstring_pad() {
let s = GString::from("123");
assert_eq!(s.lpad(5, '0'), "00123".into());
assert_eq!(s.lpad(2, ' '), "123".into());
assert_eq!(s.lpad(4, ' '), " 123".into());
assert_eq!(s.lpad(5, '0'), "00123");
assert_eq!(s.lpad(2, ' '), "123");
assert_eq!(s.lpad(4, ' '), " 123");

assert_eq!(s.rpad(5, '+'), "123++".into());
assert_eq!(s.rpad(2, ' '), "123".into());
assert_eq!(s.rpad(4, ' '), "123 ".into());
assert_eq!(s.rpad(5, '+'), "123++");
assert_eq!(s.rpad(2, ' '), "123");
assert_eq!(s.rpad(4, ' '), "123 ");

let s = GString::from("123.456");
assert_eq!(s.pad_decimals(5), "123.45600".into());
assert_eq!(s.pad_decimals(2), "123.45".into()); // note: Godot rounds down
assert_eq!(s.pad_decimals(5), "123.45600");
assert_eq!(s.pad_decimals(2), "123.45"); // note: Godot rounds down

assert_eq!(s.pad_zeros(5), "00123.456".into());
assert_eq!(s.pad_zeros(2), "123.456".into());
assert_eq!(s.pad_zeros(5), "00123.456");
assert_eq!(s.pad_zeros(2), "123.456");
}

// Byte and C-string conversions.
Expand Down
14 changes: 7 additions & 7 deletions itest/rust/src/builtin_tests/string/node_path_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,23 @@ fn node_path_subpath() {
#[itest]
fn node_path_get_name() {
let path = NodePath::from("../RigidBody2D/Sprite2D");
assert_eq!(path.get_name(0), "..".into());
assert_eq!(path.get_name(1), "RigidBody2D".into());
assert_eq!(path.get_name(2), "Sprite2D".into());
assert_eq!(path.get_name(0), "..");
assert_eq!(path.get_name(1), "RigidBody2D");
assert_eq!(path.get_name(2), "Sprite2D");

expect_panic_or_nothing("NodePath::get_name() out of bounds", || {
assert_eq!(path.get_name(3), "".into());
assert_eq!(path.get_name(3), "");
})
}

#[itest]
fn node_path_get_subname() {
let path = NodePath::from("Sprite2D:texture:resource_name");
assert_eq!(path.get_subname(0), "texture".into());
assert_eq!(path.get_subname(1), "resource_name".into());
assert_eq!(path.get_subname(0), "texture");
assert_eq!(path.get_subname(1), "resource_name");

expect_panic_or_nothing("NodePath::get_subname() out of bounds", || {
assert_eq!(path.get_subname(2), "".into());
assert_eq!(path.get_subname(2), "");
})
}

Expand Down
7 changes: 7 additions & 0 deletions itest/rust/src/builtin_tests/string/string_name_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ fn string_name_equality() {
assert_ne!(string, different);
}

#[itest]
fn string_name_eq_str() {
let apple = StringName::from("apple");
assert_eq!(apple, "apple");
assert_ne!(apple, "orange");
}

#[itest]
#[allow(clippy::eq_op)]
fn string_name_transient_ord() {
Expand Down
4 changes: 2 additions & 2 deletions itest/rust/src/engine_tests/match_class_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn match_class_named_fallback_matched() {
// Named fallback with access to original object.
other => {
require_object(&other);
assert_eq!(other.get_class(), "Resource".into());
assert_eq!(other.get_class(), "Resource");
3
}
};
Expand All @@ -145,7 +145,7 @@ fn match_class_named_mut_fallback_matched() {
// Named fallback with access to original object.
mut other => {
require_mut_object(&mut other);
assert_eq!(other.get_class(), "Resource".into());
assert_eq!(other.get_class(), "Resource");
3
}
};
Expand Down
3 changes: 1 addition & 2 deletions itest/rust/src/engine_tests/utilities_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ fn utilities_str() {

let empty = str(&[]);

// TODO: implement GString==&str operator. Then look for "...".into() patterns and replace them.
assert_eq!(concat, "12 is a true number".into());
assert_eq!(concat, "12 is a true number");
assert_eq!(concat, godot_str!("{a}{b}{c}{d}"));
assert_eq!(empty, GString::new());
}
Expand Down
2 changes: 1 addition & 1 deletion itest/rust/src/object_tests/base_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ fn base_refcounted_weak_reference() {

// Call an API to ensure Base is functional.
let class_name = base_guard.get_class();
assert_eq!(class_name, "RefcBased".into());
assert_eq!(class_name, "RefcBased");
}

let final_refcount = obj.get_reference_count();
Expand Down
2 changes: 1 addition & 1 deletion itest/rust/src/object_tests/dyn_gd_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ fn dyn_gd_call_godot_method() {
let mut node = foreign::NodeHealth::new_alloc().into_dyn::<dyn Health>();

node.set_name("dyn-name!");
assert_eq!(node.get_name(), "dyn-name!".into());
assert_eq!(node.get_name(), "dyn-name!");

node.free();
}
Expand Down
2 changes: 1 addition & 1 deletion itest/rust/src/object_tests/object_arg_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ where

assert_eq!(a, global::Error::OK);
assert_eq!(b, global::Error::OK);
assert_eq!(manual2.get_name(), "hello".into());
assert_eq!(manual2.get_name(), "hello");
assert_eq!(refc2.bind().value, -123);

manual2.free();
Expand Down
4 changes: 2 additions & 2 deletions itest/rust/src/object_tests/onready_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ fn init_attribute_node_key_lifecycle() {

{
let obj = obj.bind();
assert_eq!(obj.node.get_name(), "child".into());
assert_eq!(obj.node.get_name(), "child");
assert_eq!(obj.self_name.as_str(), "CustomNodeName");
}

Expand Down Expand Up @@ -374,7 +374,7 @@ struct InitWithNodeOrBase {
#[godot_api]
impl INode for InitWithNodeOrBase {
fn ready(&mut self) {
assert_eq!(self.node.get_name(), "child".into());
assert_eq!(self.node.get_name(), "child");
assert_eq!(self.self_name.as_str(), "CustomNodeName");
}
}
7 changes: 2 additions & 5 deletions itest/rust/src/object_tests/property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,14 +414,11 @@ fn derive_property() {
fn enum_var_hint() {
let int_prop = <Behavior as Var>::var_hint();
assert_eq!(int_prop.hint, PropertyHint::ENUM);
assert_eq!(
int_prop.hint_string,
"Peaceful:0,Defend:1,Aggressive:7".into()
);
assert_eq!(int_prop.hint_string, "Peaceful:0,Defend:1,Aggressive:7");

let str_prop = <StrBehavior as Var>::var_hint();
assert_eq!(str_prop.hint, PropertyHint::ENUM);
assert_eq!(str_prop.hint_string, "Peaceful,Defend,Aggressive".into());
assert_eq!(str_prop.hint_string, "Peaceful,Defend,Aggressive");
}

#[derive(GodotClass)]
Expand Down
2 changes: 1 addition & 1 deletion itest/rust/src/object_tests/validate_property_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct ValidatePropertyTest {
#[godot_api]
impl IObject for ValidatePropertyTest {
fn validate_property(&self, property: &mut PropertyInfo) {
if property.property_name.to_string() == "my_var" {
if property.property_name == "my_var" {
property.usage = PropertyUsageFlags::NO_EDITOR;
property.property_name = StringName::from("SuperNewTestPropertyName");
property.hint_info.hint_string = GString::from("SomePropertyHint");
Expand Down
Loading
Loading