Skip to content

Fix numeric suffix rename - #1530

Merged
HosseinYousefi merged 6 commits into
mainfrom
fix_numeric_suffix_rename
Sep 10, 2024
Merged

HosseinYousefi merged 6 commits into
mainfrom
fix_numeric_suffix_rename

Conversation

@HosseinYousefi

@HosseinYousefi HosseinYousefi commented Sep 6, 2024 •

Copy link
Copy Markdown
Contributor

Closes #571.

Adding a $K to the end of the K–th overload of a method.
To be consistent, instead of adding a zero to the end of the keywords such as new we add a $.

In short, the renaming becomes:

foo, foo, foo1, foo1 -> foo, foo$1, foo1, foo1$1
yield, yield, -> yield$, yield$1

Wrote a doc in java_differences.md explaining the renaming for method overloading.

@github-actions

github-actions Bot commented Sep 6, 2024 •

Copy link
Copy Markdown

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

Coverage ⚠️
File Coverage
pkgs/jnigen/example/in_app_java/lib/android_utils.dart 💔 Not covered
pkgs/jnigen/example/in_app_java/lib/main.dart 💔 Not covered
pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart 💔 Not covered
pkgs/jnigen/example/notification_plugin/lib/notifications.dart 💔 Not covered
pkgs/jnigen/example/pdfbox_plugin/dart_example/bin/pdf_info.dart 💔 Not covered
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart 💔 Not covered
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart 💔 Not covered
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart 💔 Not covered
pkgs/jnigen/lib/src/bindings/dart_generator.dart 💔 Not covered
pkgs/jnigen/lib/src/bindings/excluder.dart 💔 Not covered
pkgs/jnigen/lib/src/bindings/renamer.dart 💔 Not covered
pkgs/jnigen/lib/src/bindings/unnester.dart 💔 Not covered
pkgs/jnigen/lib/src/elements/elements.dart 💔 Not covered

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

License Headers ⚠️
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files
pkgs/jnigen/example/in_app_java/lib/android_utils.dart
pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
pkgs/jnigen/test/renamer_test.dart

All source files should start with a license header.

Unrelated files missing license headers
Files
pkgs/ffigen/example/libclang-example/generated_bindings.dart
pkgs/ffigen/example/shared_bindings/generate.dart
pkgs/ffigen/example/shared_bindings/lib/generated/a_gen.dart
pkgs/ffigen/example/shared_bindings/lib/generated/a_shared_b_gen.dart
pkgs/ffigen/example/shared_bindings/lib/generated/base_gen.dart
pkgs/ffigen/example/simple/generated_bindings.dart
pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_decl_collision_bindings.dart
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_symbol_address_collision_bindings.dart
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_decl_type_name_collision_bindings.dart
pkgs/ffigen/test/collision_tests/expected_bindings/_expected_reserved_keyword_collision_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_comment_markup_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_dart_handle_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_enum_int_mimic_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_forward_decl_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_functions_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_imported_types_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_native_func_typedef_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_regress_384_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_struct_fptr_fields_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_typedef_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart
pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart
pkgs/ffigen/test/large_integration_tests/_expected_cjson_bindings.dart
pkgs/ffigen/test/large_integration_tests/_expected_libclang_bindings.dart
pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart
pkgs/ffigen/test/native_test/_expected_native_test_bindings.dart
pkgs/jni/lib/src/lang/jcharacter.dart
pkgs/jni/lib/src/third_party/generated_bindings.dart
pkgs/jni/lib/src/third_party/global_env_extensions.dart
pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
pkgs/jnigen/android_test_runner/lib/main.dart
pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart
pkgs/jnigen/example/kotlin_plugin/lib/kotlin_plugin.dart
pkgs/jnigen/example/pdfbox_plugin/lib/pdfbox_plugin.dart
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/_package.dart
pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/_package.dart
pkgs/jnigen/lib/src/bindings/descriptor.dart
pkgs/jnigen/lib/src/elements/elements.g.dart
pkgs/jnigen/test/jackson_core_test/third_party/bindings/com/fasterxml/jackson/core/_package.dart
pkgs/jnigen/tool/command_runner.dart
pkgs/native_assets_builder/test_data/native_dynamic_linking/bin/native_dynamic_linking.dart
pkgs/swift2objc/lib/src/config.dart
pkgs/swift2objc/lib/src/generate_wrapper.dart
pkgs/swift2objc/lib/src/generator/_core/utils.dart
pkgs/swift2objc/lib/src/generator/generator.dart
pkgs/swift2objc/lib/src/generator/generators/class_generator.dart
pkgs/swift2objc/lib/src/parser/parsers/declaration_parsers/parse_property_declaration.dart
pkgs/swift2objc/lib/src/transformer/_core/unique_namer.dart
pkgs/swift2objc/lib/src/transformer/_core/utils.dart
pkgs/swift2objc/lib/src/transformer/transformers/transform_property.dart

This check can be disabled by tagging the PR with skip-license-check.

Package publish validation ✔️
Package Version Status
package:ffi 2.1.3 already published at pub.dev
package:ffigen 14.0.0 already published at pub.dev
package:jni 0.11.0 already published at pub.dev
package:jnigen 0.12.0-wip WIP (no publish necessary)
package:native_assets_cli 0.8.0-wip WIP (no publish necessary)
package:objective_c 2.0.0 already published at pub.dev
package:swift2objc 0.0.1-wip WIP (no publish necessary)
package:swiftgen 0.0.1-wip WIP (no publish necessary)

Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

@coveralls

coveralls commented Sep 6, 2024 •

Copy link
Copy Markdown

Coverage Status

coverage: 91.069% (-0.001%) from 91.07%
when pulling f67b95c on fix_numeric_suffix_rename
into cb2c8fa on main.

@liamappelbe

Copy link
Copy Markdown
Contributor

Oops, I misread this suggestion in the bug. I thought you were going to add a $ as a delimiter between the original method name and the deduping number. Like foo, foo, foo1, foo1 => foo, foo$1, foo1, foo1$1. I think that might be less confusing to users, since only the deduped methods actually get changed. WDYT?

@HosseinYousefi

HosseinYousefi commented Sep 9, 2024 •

Copy link
Copy Markdown
Contributor Author

Oops, I misread this suggestion in the bug.

No you correctly read it. It also was one of the options. I then made another comment: #571 (comment)

I thought you were going to add a $ as a delimiter between the original method name and the deduping number. Like foo, foo, foo1, foo1 => foo, foo$1, foo1, foo1$1. I think that might be less confusing to users, since only the deduped methods actually get changed. WDYT?

My argument was the fact that methods ending with a number are fewer than the methods that have overloads. This way users don't have to write a ton of $s.

@dcharkes

dcharkes commented Sep 9, 2024 •

Copy link
Copy Markdown
Collaborator

I thought you were going to add a $ as a delimiter between the original method name and the deduping number. Like foo, foo, foo1, foo1 => foo, foo$1, foo1, foo1$1. I think that might be less confusing to users, since only the deduped methods actually get changed. WDYT?

My argument was the fact that methods ending with a number are fewer than the methods that have overloads. This way users don't have to write a ton of $s.

I might align more with @liamappelbe here. Appending $1 for deduped things means that the method name before the $ is unchanged, which is less confusing. I don't expect users to type the $ that often, they'd likely use autocomplete in their IDE. (But I don't work with the generated APIs, so I'm not the one to ask! Happy with any solution that's usable for users.)

(Side note: If possible with transformers, we should implement some heuristic renamers which for example check if there's a single argument difference, and if yes use $argument as postfix instead of a number.)

@HosseinYousefi

Copy link
Copy Markdown
Contributor Author

I might align more with @liamappelbe here. Appending $1 for deduped things means that the method name before the $ is unchanged, which is less confusing. I don't expect users to type the $ that often, they'd likely use autocomplete in their IDE.

I agree, it may be subjective but aesthetically the code is going to look cryptic as overloadings are quite common. @lrhn @mkustermann wdyt?

@HosseinYousefi

Copy link
Copy Markdown
Contributor Author

@dcharkes @liamappelbe I changed the renaming strategy to use $K suffices for overloads. PTAL.

@dcharkes

dcharkes commented Sep 9, 2024

Copy link
Copy Markdown
Collaborator

@dcharkes @liamappelbe I changed the renaming strategy to use $K suffices for overloads. PTAL.

Both isEnabled, isEnabled1, isEnabled2 and isEnabled, isEnabled$1, isEnabled$2 are exceedingly ugly! 😄 I see your argument for making overloading nicer and methods with numbers at the end more ugly. But really, we should have those transformers and some default ones that do a better job. isEnabledJsonParserFeature, isEnabledJsonFactoryFeature, and isEnabledStreamReadFeature would be better, still ugly, but better.

@HosseinYousefi

Copy link
Copy Markdown
Contributor Author

@dcharkes @liamappelbe I changed the renaming strategy to use $K suffices for overloads. PTAL.

Both isEnabled, isEnabled1, isEnabled2 and isEnabled, isEnabled$1, isEnabled$2 are exceedingly ugly! 😄 I see your argument for making overloading nicer and methods with numbers at the end more ugly. But really, we should have those transformers and some default ones that do a better job. isEnabledJsonParserFeature, isEnabledJsonFactoryFeature, and isEnabledStreamReadFeature would be better, still ugly, but better.

We already discussed the numeric vs non numeric in another issue: #585

Maybe the ugliness is a feature, not a bug! Having $ in the code signals the fact that there is something that could be improved if the user takes the time to provide exact renaming.

Also a lot of method overloading in Java happens because Java does not have default parameter values like Dart has. The user could transform such family of functions to use default parameters in Dart instead. Here's an example of such case: https://github.com/dart-lang/native/blob/main/pkgs/jni/lib/src/nio/jbyte_buffer.dart#L136-L155

@mkustermann

Copy link
Copy Markdown
Member

I agree, it may be subjective but aesthetically the code is going to look cryptic as overloadings are quite common. @lrhn @mkustermann wdyt?

IMHO it makes sense to have the first part of the name be the same, then some separator making it clear to users what comes afterwards is to select the overloaded method. Separator could be a $, _, ...

The last part being a number is probably fine as users have code-completion and see signatures in their completion window. Otherwise one could combine the argument type names - which is more verbose (especially if one wants guarantee to avoid collisions). One could think of making this pluggable - so there's an option for users to manually name them.

@HosseinYousefi Is there a strategy to avoid breaking APIs when new overloads are added (and therefore cause change in numbering)? e.g. the generator could not just output bindings but also the naming choice it used. When running the generator again in future (with possibly updated jars), the generator could initialize it's chosen names from that naming choice and new overloads will get seperate names. That is also beneficial for code across packages: If one uses JNIgen to generate package:base and then package:special that imports package:base and extends classes there - it would want to use the same naming scheme as package:special.

@dcharkes

Copy link
Copy Markdown
Collaborator

Is there a strategy to avoid breaking APIs when new overloads are added (and therefore cause change in numbering)?

We so far have not tried to prevent renumbering (see the discussion in #585 (comment)). In previous discussions we preferred conciseness over the risk of renumbering (which can be avoided by appending a hash of the types/params or the types themselves).

@HosseinYousefi

HosseinYousefi commented Sep 10, 2024 •

Copy link
Copy Markdown
Contributor Author

@HosseinYousefi Is there a strategy to avoid breaking APIs when new overloads are added (and therefore cause change in numbering)?

👍 I opened an issue for this a few days ago: #1529

(The mechanism already exists for interop between generated code.)

@mkustermann

Copy link
Copy Markdown
Member

We so far have not tried to prevent renumbering (see the discussion in #585 (comment)). In previous discussions we preferred conciseness over the risk of renumbering (which can be avoided by appending a hash of the types/params or the types themselves).

In a world where bindings are not single-file, single-package but across packages that import each other - or where bindings are part of the public API (e.g. us publishing packages with bindings like package:android_bluetooth) - then consistent naming becomes much more important.

Yes, using a hash is the default and simplest solution to this problem (also utilized by e.g. kotlin to java compiler) - but that would require adding the hash to all names, even if there's only one method - which makes it not very ergonomic.

An alternative would be to make the generator "remember" the naming choices it made before and bootstrap from that naming choice when generating dependent code or code for updated JARs - making the choice sticky and therefore much less breaking.

@lrhn

lrhn commented Sep 10, 2024 •

Copy link
Copy Markdown
Member

I also worry about renumbering. If there are overloadings, it's not unreasonable that there will be more.
Or if it's very common name that many things may want to have a function for, like WhatNot add(WhatNot, WhatNot), which can exist for numbers, lists, strings, points, vectors.

I do prefer $ as separator. It screams "not part of original name, inserted by automatic code generation" to me.
If the original native name contains a $, then ... maybe double it. Or just add a $0 at the end, even if not overloaded, so that the last $ is always introduced by this tool.

We could give users a way to control the naming. It could be the same functionality we use for avoiding renumbering.

Say ffigen/jnigen reads a file before doing the renaming, a file which contains the result of prior renamings, so that it always gives the same name to a declaration that it has already seen once, and it writes the file back when it has seen any changes to declarations.
(That file may need to contain all declarations, even those that are not overloaded, so it knows which one was the original when the first overloading declaration is added. It can drop members that don't exist any more.)

Then users can check the file into source control to preserve the naming, and they can hand edit the file to give a specific different naming to a function.
If I want Point add(Point p1, Point p2) to be named addPoints instead of add$7, I can change the entry to, fx, "add(Point,Point):Point": "addPoints" (or however the receiver signature is represented in a unique way).

Using a hash of the signature makes for long names (or higher risk of conflict if we truncate to fewer bits).
Could use a hash of the signature in the renaming file, so it maps the name foo and signature with hash 0xA512B9F0 to foo$1.

(Do we have a way to choose a name today when using xxigen, or is that something you're expected to do at the Dart end, with a Point addPoints(Point p1, Point p2) => prefix.add$7(p1, p2); forwarder?)

@HosseinYousefi
HosseinYousefi merged commit cd4f039 into main Sep 10, 2024
@HosseinYousefi
HosseinYousefi deleted the fix_numeric_suffix_rename branch September 10, 2024 09:33

You might wonder why the method isn't renamed to `add1` instead of `add$1`. The
reason is that a method named `add1` could already exist in the Java class. On
the other hand, Java identifiers cannot contain dollar signs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Java identifiers can contain $: https://docs.oracle.com/javase/specs/jls/se11/html/jls-3.html#jls-3.8

They shouldn't, and the character is morally reserved for code generation and referencing legacy code that blatantly used $.
However, Dart code may be used to interact with code-generated Java code, so it cannot assume that there are no $s in the source names.

I recommend either changing existing $s to $$, or always adding a suffix to names containing $, starting with $0 where you would otherwise hav done nothing. Then any Dart name that ends with $<digits>, (or if using the $-doubling, one that is not preceded by an odd number of $s) is a JNIGen rename, and cannot conflict with an un-renamed name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤦‍♂️ I didn't know this! Will add the logic, thanks


/// The type which includes information such as the signature of this class.
static const type = $ExampleType();
static final _id_new0 = _class.constructorId(

@lrhn lrhn Sep 10, 2024 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This does not look consistent with the description above, it's not $+digits?
There are several cases for 0 -> $ above and below. Is there some extra rule not mentioned in the documentation?

Found it. This is a "keyword rename", which adds $ after words that may conflict with Dart names or keywords.
Not sure why _id_new is one, but I guess it is derived from new, and therefore from new$, somehow.

(We don't need to add the keyword-$ on names that are made private by prefixing _. Don't know if it's better to be consistent, though.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As it's private, I didn't add a special case for it.

classes.decls.values.map((c) => c.finalName).toList();
expect(renamedClasses, [
'Foo',
'Foo\$1',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd use raw strings here. It's safer.

]);

final renamedMethods =
classes.decls['Foo']!.methods.map((m) => m.finalName).toList();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(The .map((m) => m.finalName).toList() happens a lot. Consider a helper function:

List<String> finalNames(List<Declaration> declarations) => 
    declarations.map((d) => d.finalName).toList();

(If it can be typed like that.)


int add1(int a) { /* ... */ }

double add1$2(double a) { /* ... */ }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be add1$1?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

};

/// Appends 0 to [name] if [name] is a keyword.
/// Appends $ to [name] if [name] is a keyword.

@lrhn lrhn Sep 10, 2024 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it a Dart or Java keywords. (Very related, not exactly the same. Looks like it's Dart "keyword", aka. reserved word, built-in identifier or contextual keyword. We don't necessarily need to add $ to these in all cases, like built-in identifiers are not reserved for non-type names, but I guess it's simpler to just always add it.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. I plan to reduce them. #664

///
/// Examples:
/// * `int` -> `int0`
/// * `int` -> `int$`

@lrhn lrhn Sep 10, 2024 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not a good example, since int is not a Dart keyword, and is also not in the keywords set.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already merged, will address in a another PR.

@HosseinYousefi

Copy link
Copy Markdown
Contributor Author

Do we have a way to choose a name today when using xxigen

ffigen has it. jnigen will have it soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect renaming when the method/field already has a numeric suffix

6 participants