Skip to content

Implement the safe_ident strategy for virtual call parameter identifier generation #822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 29, 2024

Conversation

ambeeeeee
Copy link
Contributor

@ambeeeeee ambeeeeee commented Jul 27, 2024

The behavior in maybe_rename_parameter as currently implemented strips leading underscores from parameter names.

The case where _ is stripped presents a need to sanitize identifiers where we otherwise would not have to, as the removal of the leading underscore may make the identifier match a language keyword. A concrete example someone ran into was an argument named type in IEditorExportPlugin::export_file in #817.

There appears to be an existing approach to keyword-named parameters, as the parameter in the codegen-generated interface is named type_. I just implemented that exact strategy in the way maybe_rename_parameter generates identifiers after removing a prefixing _.

Codegen Comparison

With the following code from #817

use godot::{classes::{EditorExportPlugin, IEditorExportPlugin}, prelude::*};

#[derive(GodotClass)]
#[class(base=EditorExportPlugin)]
pub struct MyExportPlugin {
    base: Base<EditorExportPlugin>,
}

#[godot_api]
impl IEditorExportPlugin for MyExportPlugin {
    fn init(base: Base<EditorExportPlugin>) -> Self {
        Self {
            base,
        }
    }

    fn export_file(&mut self, _path: GString, _type: GString, _features: PackedStringArray) {
        todo!()
    }
}

I've included only the changed output from the expanded macro for brevity.

Pre-Change

fn __virtual_call(name: &str) ->  ::godot::sys::GDExtensionClassCallVirtual {
    use::godot::obj::UserClass as _;
    match name {
        "_export_file" => {
            use::godot::sys;
            type Sig = ((),GString,GString,PackedStringArray);
            unsafe extern "C" fn virtual_fn(instance_ptr:sys::GDExtensionClassInstancePtr,args_ptr: *const sys::GDExtensionConstTypePtr,ret:sys::GDExtensionTypePtr,){
                let call_ctx =  ::godot::meta::CallContext::func("MyExportPlugin","export_file");
                let _success =  ::godot::private::handle_ptrcall_panic(&call_ctx, || <Sig as ::godot::meta::PtrcallSignatureTuple> ::in_ptrcall(instance_ptr, &call_ctx,args_ptr,ret, |instance_ptr,params|{
                    let(path,type,features,) = params;
                    let storage = unsafe {
                        ::godot::private::as_storage:: <MyExportPlugin>(instance_ptr)
                    };
                    let mut instance =  ::godot::private::Storage::get_mut(storage);
                    instance.export_file(path,type,features)
                },sys::PtrcallType::Virtual,));
            }
            Some(virtual_fn)
        },
// ...

Specific lines that cause problems:

  • let(path,type,features,) = params;
  • instance.export_file(path,type,features)

Post-Change

fn __virtual_call(name: &str) -> ::godot::sys::GDExtensionClassCallVirtual {
    use ::godot::obj::UserClass as _;
    match name {
        "_export_file" => {
            use ::godot::sys;
            type Sig = ((), GString, GString, PackedStringArray);
            unsafe extern "C" fn virtual_fn(
                instance_ptr: sys::GDExtensionClassInstancePtr,
                args_ptr: *const sys::GDExtensionConstTypePtr,
                ret: sys::GDExtensionTypePtr,
            ) {
                let call_ctx =
                    ::godot::meta::CallContext::func("MyExportPlugin", "export_file");
                let _success = ::godot::private::handle_ptrcall_panic(&call_ctx, || {
                    <Sig as ::godot::meta::PtrcallSignatureTuple>::in_ptrcall(
                        instance_ptr,
                        &call_ctx,
                        args_ptr,
                        ret,
                        |instance_ptr, params| {
                            let (path, type_, features) = params;
                            let storage = unsafe {
                                ::godot::private::as_storage::<MyExportPlugin>(instance_ptr)
                            };
                            let mut instance = ::godot::private::Storage::get_mut(storage);
                            instance.export_file(path, type_, features)
                        },
                        sys::PtrcallType::Virtual,
                    )
                });
            }
            Some(virtual_fn)
        }
// ...

@ambeeeeee
Copy link
Contributor Author

ambeeeeee commented Jul 27, 2024

Another solution which would work in this case is generating the identifiers with the escaped identifier syntax, which is how I originally implemented a solution. I figured I'd just use the same solution that already exists in the codegen implementation even though prepending r# to the prefix-stripped identifier is "simpler".

@Bromeon Bromeon added bug c: register Register classes, functions and other symbols to GDScript labels Jul 27, 2024
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-822

@Bromeon
Copy link
Member

Bromeon commented Jul 27, 2024

Thanks a lot! 🙂

The code duplication isn't great, but I agree it's probably more consistent to stick with the existing approach for now 🤔 could you maybe add a comment in both functions (macro + codegen) to indicate there's duplicated code which would need to be kept in sync?

Would it be possible to add a small test that verifies that it compiles? You may need to extend SELECTED_CLASSES, or maybe there's an existing one which has also a type parameter or so...

@ambeeeeee ambeeeeee force-pushed the ported-escaping branch 4 times, most recently from 6699c6f to 87cbe77 Compare July 27, 2024 23:45
@ambeeeeee
Copy link
Contributor Author

ambeeeeee commented Jul 27, 2024

Added documentation about the code duplication and what I hope to be a suitable test.

I'd rather have implemented more test cases, but I think in this case we can get away with one. My thinking is that the test case is testing whether we call safe_ident and whether it returns a safe identifier, and if we wanted more testing we could just test safe_ident since we know it will be called as expected as long as this case compiles.

(Sorry about the force pushes, somehow ended up duplicating the squashed commit while attempting to rebase on the latest master and things got far out of hand so very quickly)

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Don't worry about force pushes 🙂

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Once you remove the clippy/rustfmt attributes, change import and squash the commits, should be good to go!

…ter names that might collide with keywords

Document duplicated definition of `safe_ident`

Add test for `safe_ident` parameter identifier generation for `#[godot-api]` virtual call generation

Remove redundant code in keyword parameter test

Directly import symbols rather than using prelude
@Bromeon Bromeon added this pull request to the merge queue Jul 29, 2024
@Bromeon
Copy link
Member

Bromeon commented Jul 29, 2024

Thanks a lot!

Merged via the queue into godot-rust:master with commit 71b869f Jul 29, 2024
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug c: register Register classes, functions and other symbols to GDScript
Projects
None yet
3 participants