Skip to content

Commit 866dce6

Browse files
[jnigen] Rename identifiers starting with underscore (#1551)
1 parent 69dd6d1 commit 866dce6

File tree

6 files changed

+137
-38
lines changed

6 files changed

+137
-38
lines changed

pkgs/jnigen/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
is done to avoid name collision between methods that originally end with
77
numeric suffices and the renamed overloads. Similarly names that are Dart
88
keywords get a dollar sign suffix now. For more information, check out the
9-
[documentation](https://github.com/dart-lang/native/tree/main/pkgs/jnigen/docs/java_differences.md#method_overloading).
9+
[documentation](https://github.com/dart-lang/native/tree/main/pkgs/jnigen/docs/java_differences.md#method-overloading).
1010
- **Breaking Change**: Each single dollar sign is replaced with two dollar signs
1111
in the identifier names.
12+
- Generating identifiers that start with an underscore (`_`) and making them
13+
public by prepending a dollar sign.
1214
- Fixed an issue where inheriting a generic class could generate incorrect code.
1315
- No longer generating constructors for abstract classes.
1416
- No longer generating `protected` elements.

pkgs/jnigen/docs/java_differences.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,30 @@ JNIgen replaces each single dollar sign with two dollar signs. For example
186186
`generated$method$2` will turn into `generated$$method$$2`. This prevents name
187187
collision as JNIgen-renamed identifiers will end with an odd number of dollar
188188
signs (optionally followed by a numeric suffix).
189+
190+
### Identifier starting with underscore (`_`)
191+
192+
Unlike Java, Dart identifiers that start with an underscore are private. This
193+
means they are inaccessible outside their defining scope. Users should still be
194+
able to access public Java identifiers that start with an underscore from the
195+
generated Dart bindings. To allow this, JNIgen prepends such identifiers with a
196+
dollar sign to keep them public in Dart.
197+
198+
For example, `_Example` in Java will be converted to `$_Example` in the Dart
199+
bindings:
200+
201+
```java
202+
// Java
203+
public class _Example {
204+
public void _printMessage() {
205+
System.out.println("Hello from Java!");
206+
}
207+
}
208+
```
209+
210+
```dart
211+
// Dart Bindings - Boilerplate omitted for clarity.
212+
class $_Example extends JObject {
213+
void $_printMessage() { /* ... */ }
214+
}
215+
```

pkgs/jnigen/lib/src/bindings/excluder.dart

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'visitor.dart';
99

1010
extension on ClassMember {
1111
bool get isPrivate => !isPublic;
12-
bool get hasPrivateName => name.startsWith('_');
1312
}
1413

1514
class Excluder extends Visitor<Classes, void> {
@@ -21,7 +20,6 @@ class Excluder extends Visitor<Classes, void> {
2120
void visit(Classes node) {
2221
node.decls.removeWhere((_, classDecl) {
2322
final excluded = classDecl.isPrivate ||
24-
classDecl.hasPrivateName ||
2523
!(config.exclude?.classes?.included(classDecl) ?? true);
2624
if (excluded) {
2725
log.fine('Excluded class ${classDecl.binaryName}');
@@ -44,25 +42,20 @@ class _ClassExcluder extends Visitor<ClassDecl, void> {
4442
void visit(ClassDecl node) {
4543
node.methods = node.methods.where((method) {
4644
final isPrivate = method.isPrivate;
47-
final hasPrivateName = method.hasPrivateName;
4845
final isAbstractCtor = method.isConstructor && node.isAbstract;
4946
final isBridgeMethod = method.isSynthetic && method.isBridge;
5047
final isExcludedInConfig =
5148
config.exclude?.methods?.included(node, method) ?? false;
52-
final excluded = isPrivate ||
53-
hasPrivateName ||
54-
isAbstractCtor ||
55-
isBridgeMethod ||
56-
isExcludedInConfig;
49+
final excluded =
50+
isPrivate || isAbstractCtor || isBridgeMethod || isExcludedInConfig;
5751
if (excluded) {
5852
log.fine('Excluded method ${node.binaryName}#${method.name}');
5953
}
6054
return !excluded;
6155
}).toList();
6256
node.fields = node.fields.where((field) {
63-
final excluded = field.isPrivate ||
64-
field.hasPrivateName &&
65-
(config.exclude?.fields?.included(node, field) ?? true);
57+
final excluded = field.isPrivate &&
58+
(config.exclude?.fields?.included(node, field) ?? true);
6659
if (excluded) {
6760
log.fine('Excluded field ${node.binaryName}#${field.name}');
6861
}

pkgs/jnigen/lib/src/bindings/renamer.dart

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,24 @@ const Map<String, int> _definedSyms = {
9393
'type': 1,
9494
};
9595

96-
/// Replaces each dollar sign with two dollar signs in [name].
97-
///
98-
/// Examples:
99-
/// * `$foo$$bar$` -> `$$foo$$$$bar$$`
100-
/// * `foo` -> `foo`
101-
String _doubleDollarSigns(String name) {
102-
return name.replaceAll(r'$', r'$$');
96+
String _preprocess(String name) {
97+
// Replaces the `_` prefix with `$_` to prevent hiding public members in Dart.
98+
// For example `_foo` -> `$_foo`.
99+
String makePublic(String name) =>
100+
name.startsWith('_') ? '\$_${name.substring(1)}' : name;
101+
102+
// Replaces each dollar sign with two dollar signs in [name].
103+
// For example `$foo$$bar$` -> `$$foo$$$$bar$$`.
104+
String doubleDollarSigns(String name) => name.replaceAll(r'$', r'$$');
105+
106+
return makePublic(doubleDollarSigns(name));
103107
}
104108

105109
/// Appends `$` to [name] if [name] is a Dart keyword.
106110
///
107111
/// Examples:
108112
/// * `yield` -> `yield$`
109-
/// * `i` -> `i`
113+
/// * `foo` -> `foo`
110114
String _keywordRename(String name) =>
111115
_keywords.contains(name) ? '$name\$' : name;
112116

@@ -159,7 +163,7 @@ class _ClassRenamer implements Visitor<ClassDecl, void> {
159163
// should continue to use dollar sign.
160164
// TODO(https://github.com/dart-lang/native/issues/1544): Class names can
161165
// have dollar signs even if not nested.
162-
final className = node.name.replaceAll(r'$', '_');
166+
final className = _preprocess(node.name.replaceAll(r'$', '_'));
163167

164168
// When generating all the classes in a single file
165169
// the names need to be unique.
@@ -201,7 +205,7 @@ class _MethodRenamer implements Visitor<Method, void> {
201205

202206
@override
203207
void visit(Method node) {
204-
final name = _doubleDollarSigns(node.isConstructor ? 'new' : node.name);
208+
final name = _preprocess(node.isConstructor ? 'new' : node.name);
205209
final sig = node.javaSig;
206210
// If node is in super class, assign its number, overriding it.
207211
final superClass =
@@ -243,7 +247,7 @@ class _FieldRenamer implements Visitor<Field, void> {
243247

244248
@override
245249
void visit(Field node) {
246-
final fieldName = _doubleDollarSigns(node.name);
250+
final fieldName = _preprocess(node.name);
247251
node.finalName = _renameConflict(nameCounts, fieldName);
248252
log.fine('Field ${node.classDecl.binaryName}#${node.name}'
249253
' is named ${node.finalName}');

pkgs/jnigen/test/jackson_core_test/third_party/bindings/com/fasterxml/jackson/core/JsonFactory.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,37 @@ class JsonFactory extends jni.JObject {
27152715
out.reference.pointer)
27162716
.object(const jni.JObjectType());
27172717
}
2718+
2719+
static final _id_$_getBufferRecycler = _class.instanceMethodId(
2720+
r'_getBufferRecycler',
2721+
r'()Lcom/fasterxml/jackson/core/util/BufferRecycler;',
2722+
);
2723+
2724+
static final _$_getBufferRecycler = ProtectedJniExtensions.lookup<
2725+
ffi.NativeFunction<
2726+
jni.JniResult Function(
2727+
ffi.Pointer<ffi.Void>,
2728+
jni.JMethodIDPtr,
2729+
)>>('globalEnv_CallObjectMethod')
2730+
.asFunction<
2731+
jni.JniResult Function(
2732+
ffi.Pointer<ffi.Void>,
2733+
jni.JMethodIDPtr,
2734+
)>();
2735+
2736+
/// from: `public com.fasterxml.jackson.core.util.BufferRecycler _getBufferRecycler()`
2737+
/// The returned object must be released after use, by calling the [release] method.
2738+
///
2739+
/// Method used by factory to create buffer recycler instances
2740+
/// for parsers and generators.
2741+
///
2742+
/// Note: only public to give access for {@code ObjectMapper}
2743+
///@return Buffer recycler instance to use
2744+
jni.JObject $_getBufferRecycler() {
2745+
return _$_getBufferRecycler(
2746+
reference.pointer, _id_$_getBufferRecycler as jni.JMethodIDPtr)
2747+
.object(const jni.JObjectType());
2748+
}
27182749
}
27192750

27202751
final class $JsonFactoryType extends jni.JObjType<JsonFactory> {

pkgs/jnigen/test/renamer_test.dart

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ extension on Iterable<ClassMember> {
1111
List<String> get finalNames => map((c) => c.finalName).toList();
1212
}
1313

14-
Future<Config> testConfig() async {
14+
Future<void> rename(Classes classes) async {
1515
final config = Config(
1616
outputConfig: OutputConfig(
1717
dartConfig: DartCodeOutputConfig(
1818
path: Uri.file('test.dart'),
1919
structure: OutputStructure.singleFile,
2020
),
2121
),
22-
classes: ['Foo'],
22+
classes: [],
2323
);
24-
return config;
24+
await classes.accept(Linker(config));
25+
classes.accept(Renamer(config));
2526
}
2627

2728
void main() {
@@ -76,9 +77,7 @@ void main() {
7677
superclass: TypeUsage.object,
7778
),
7879
});
79-
final config = await testConfig();
80-
await classes.accept(Linker(config));
81-
classes.accept(Renamer(config));
80+
await rename(classes);
8281

8382
final renamedClasses = classes.decls.values.finalNames;
8483
expect(renamedClasses, [
@@ -137,9 +136,7 @@ void main() {
137136
],
138137
),
139138
});
140-
final config = await testConfig();
141-
await classes.accept(Linker(config));
142-
classes.accept(Renamer(config));
139+
await rename(classes);
143140

144141
final renamedMethods = classes.decls['Player']!.methods.finalNames;
145142
expect(renamedMethods, ['duck']);
@@ -168,9 +165,7 @@ void main() {
168165
superclass: TypeUsage.object,
169166
),
170167
});
171-
final config = await testConfig();
172-
await classes.accept(Linker(config));
173-
classes.accept(Renamer(config));
168+
await rename(classes);
174169

175170
final renamedMethods = classes.decls['Foo']!.methods.finalNames;
176171
expect(renamedMethods, [r'yield$', r'const$1', r'const$2']);
@@ -203,9 +198,7 @@ void main() {
203198
],
204199
),
205200
});
206-
final config = await testConfig();
207-
await classes.accept(Linker(config));
208-
classes.accept(Renamer(config));
201+
await rename(classes);
209202

210203
final renamedMethods = classes.decls['Foo']!.methods.finalNames;
211204
expect(renamedMethods, [
@@ -223,4 +216,53 @@ void main() {
223216
r'alsoAField$$1',
224217
]);
225218
});
219+
220+
test('Names that start with underscores', () async {
221+
final classes = Classes({
222+
'_Foo': ClassDecl(
223+
binaryName: '_Foo',
224+
declKind: DeclKind.classKind,
225+
superclass: TypeUsage.object,
226+
methods: [
227+
Method(name: '_foo', returnType: TypeUsage.object),
228+
],
229+
fields: [
230+
Field(name: '_bar', type: TypeUsage.object),
231+
],
232+
),
233+
});
234+
await rename(classes);
235+
236+
final renamedMethods = classes.decls['_Foo']!.methods.finalNames;
237+
expect(renamedMethods, [r'$_foo']);
238+
final renamedFields = classes.decls['_Foo']!.fields.finalNames;
239+
expect(renamedFields, [r'$_bar']);
240+
final renamedClasses = classes.decls.values.finalNames;
241+
expect(renamedClasses, [r'$_Foo']);
242+
});
243+
244+
test('Combination of underscore, dollar signs, and overloading', () async {
245+
// TODO(https://github.com/dart-lang/native/issues/1544): Test class names
246+
// containing dollar signs.
247+
final classes = Classes({
248+
r'_Foo$': ClassDecl(
249+
binaryName: r'_Foo$',
250+
declKind: DeclKind.classKind,
251+
superclass: TypeUsage.object,
252+
methods: [
253+
Method(name: r'_foo$', returnType: TypeUsage.object),
254+
Method(name: r'_foo$', returnType: TypeUsage.object),
255+
],
256+
fields: [
257+
Field(name: r'_foo$', type: TypeUsage.object),
258+
],
259+
),
260+
});
261+
await rename(classes);
262+
263+
final renamedMethods = classes.decls[r'_Foo$']!.methods.finalNames;
264+
expect(renamedMethods, [r'$_foo$$$1', r'$_foo$$$2']);
265+
final renamedFields = classes.decls[r'_Foo$']!.fields.finalNames;
266+
expect(renamedFields, [r'$_foo$$']);
267+
});
226268
}

0 commit comments

Comments
 (0)