Skip to content
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

Add name mangling for method and type names to fit foreign language naming conventions #2

Merged
merged 7 commits into from
Jul 10, 2020

Conversation

jhugman
Copy link
Contributor

@jhugman jhugman commented Jun 22, 2020

This PR may not be useful, but starts as a way of exploring the codebase. Absenting some issues ;)

  • The IDL may specify method names in camel case or snake case.
  • The generated kotlin method names are in camel case.
  • The generated python method names are in snake case.
  • The rust method names are in snake case.

Is this useful?

  • If the rust devs are delivering generated APIs to App developers, then yes.
  • If the rust devs are delivering handmade wrappers around the generated APIs, then no.

This should probably be a candidate for project specific/language specific uniffi config.

@rfk
Copy link
Collaborator

rfk commented Jun 22, 2020

Great, thanks for diving in!! I had heck in the list of dependencies here with the intention of experimenting with this sort of thing at some point, so very glad to have you take a look.

I think it makes sense to pursue the " rust devs are delivering generated APIs to App developers" philosophy, at least to start. So being able to generate bindings that feel like they fit in the target language will be important.

I'll try to carve out some time to take a detailed look later today.

Copy link
Collaborator

@rfk rfk left a comment

Choose a reason for hiding this comment

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

This all looks pretty reasonable to me! Some thoughts below that it would be good to get your take on, but if you think it's useful and ready to merge, I'd be happy to take it 👍


namespace naming_conventions {
snake_case_object snake_case_method(u32 id);
CamelCaseObject camelCaseMethod(u32 id);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting philosophical point here about what to allow in the IDL, and I'm kind of in two minds about it. On one hand, it seems wrong to outright error out if something declared in this file does not match a particular naming convention, but on the other hand, allowing mixing conventions like this might lead to confusion. Perhaps it's the sort of thing that might one day be a candidate for a warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See discussion below. I think the chance of collision is too high here.

@@ -283,13 +284,13 @@ internal interface _UniFFILib : Library {
// Public interface members begin here.

{% for e in ci.iter_enum_definitions() %}
enum class {{ e.name() }} {
enum class {{ e.name()|decl_name_kt }} {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's probably not clear from the code, but in my head I'd originally intended the "decl" prefix on these filter function to be read as "declare type". Something like "class_name_kt" might be a clearer name for this filter (and the existing "decl_kt" might be better as something like "type_kt" for clarity).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll change functions as you suggest.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@@ -34,7 +35,7 @@ ffi_support::define_bytebuffer_destructor!({{ ci.ffi_bytebuffer_free().name() }}
// of items as declared in the rust code, but no harm will come from it.

{% for e in ci.iter_enum_definitions() %}
unsafe impl uniffi::support::ViaFfi for {{ e.name() }} {
unsafe impl uniffi::support::ViaFfi for {{ e.name()|decl_name_rs }} {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than doing this conversion when generating the Rust scaffolding, what do you think about the following approach:

  • Decree that the naming convention in the IDL should be the same as the naming convention in the Rust code (but allow users to write it differently if they want, maybe with a warning in future).
  • When parsing the IDL, canonicalize all names to the rust convention as part of building the ComponentInterface struct (this would be the place where we warn about it in future).
  • Allow each foreign language binding to choose whether and how it wants to deviate from that convention to fit in with the norms of that language.

Alternately, we could just insist that the names in Rust and IDL must match exactly, however you've written them, and not try to do any conversion.

Keeping the IDL and the Rust more tightly bound makes sense to me in a vague kind of way, especially if we imagine that one day we might general definitions using inline rust macros rather than a separate IDL file. It would also mean there are no "control knobs" for callers to fiddle with when interfacing between the IDL and the Rust code, while allowing the possibility of such knobs when generating the bindings (which there is also a placeholder Config class for in the kotlin and python bindings generators).

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I understand correctly, I agree with your:

insist that the names in Rust and IDL must match
while allowing the possibility of such knobs when generating the bindings

There will be some back pressure from the hand written rust from the linter or rustc so that the rust and WebIDL will naturally tend to conform to a rust naming convention— because the rust compiler will insist, rather than uniffi. If users really want to have funky rust identifiers, they'll have to add their own annotations in rust.

The foreign language bindings are a view onto the WebIDL, so I think they should have a little bit of leeway to convert/mangle names to suite the language/project. I don't think we should do it without configuration in the long run because:

  • collisions are still possible, (the webIDL could define foo_bar and fooBar, which would both convert to the same methods). This might correspond to a normalize + check step before generating any bindings.
  • generating source-backward-compatible bindings for existing projects may still be an aim for the project.

If all this is desirable, then we should also consider doing the same for the property names.

Copy link
Collaborator

Choose a reason for hiding this comment

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

collisions are still possible, (the webIDL could define foo_bar and fooBar, which would both convert to the same methods).
This might correspond to a normalize + check step before generating any bindings.

This is a good point; I think I'd be interested in trying out a normalize+check step when processing the IDL. In the same way that we'd error out if you tried to define two methods with exactly the same name, we could error out if you try to define two methods that would be ambiguous under common case conversions. (And it seems easier to error out now and loosen the rules later, than to start with loose rules and try to tighten them up later).

Copy link
Contributor Author

@jhugman jhugman Jul 7, 2020

Choose a reason for hiding this comment

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

Removed name mangling in scaffolding.rs and filed #13.

@rfk
Copy link
Collaborator

rfk commented Jun 23, 2020

What I also need to do, is figure out some basic test infrastructure here. I'd like to be able to turn the existing example.py and example.kts files into actual tests with assertions that can be executed to make sure the generated bindings are working as expected. So far I've just been running them by hand and eyeballing the output, but that's clearly not going to scale.

@jhugman jhugman changed the title [tentative] Add name mangling for method and type names to fit host language naming conventions Add name mangling for method and type names to fit foreign language naming conventions Jul 3, 2020
@rfk rfk changed the base branch from master to main July 5, 2020 05:07
@rfk
Copy link
Collaborator

rfk commented Jul 5, 2020

(heads-up that I edited this PR to target the new main branch rather than master)

@jhugman jhugman requested a review from rfk July 7, 2020 15:21
@rfk
Copy link
Collaborator

rfk commented Jul 8, 2020

Thanks for pushing on this! Just wanted to comment to set expectations, I should be able to find time to look at it by end-of-week.

Copy link
Collaborator

@rfk rfk left a comment

Choose a reason for hiding this comment

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

The naming cleanups/consistency really made a difference here, I like the way this reads. Thanks!

throw RuntimeException("invalid enum value, something is very wrong!!")
}
internal fun lift(n: Int) =
try { values()[n - 1] }
Copy link
Collaborator

Choose a reason for hiding this comment

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

oh, neat!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants