Skip to content

Commit c9628e3

Browse files
AndrewAtAvenzaa4z
andauthored
Add support for equality operators via new 'requires' keyword for interfaces (#169)
* Added support for 'requiring' (will change to 'requires' eventually) * Renamed 'requiring' to 'requires' * Implemented basic equality for C++ & Java * Added support for hashCode() * Accidentally changed 'Bool' to 'I32' -- reverting * Different approaching using methods equals() & hashCode() as static members of a class-in-a-class Operators. * Switched from const std::shared_ptr<>& to const& for parameters to equals() & hashCode() * Moved prototype for native_hash_code() inside requires-if-statement block; should fix unit test failure. * New way of getting sbt (#172) December 2024 CI runners don't have sbt anymore Therefore, install it via actions, fix #171 * Added support for 'requiring' (will change to 'requires' eventually) * Renamed 'requiring' to 'requires' * Implemented basic equality for C++ & Java * Added support for hashCode() * Accidentally changed 'Bool' to 'I32' -- reverting * Different approaching using methods equals() & hashCode() as static members of a class-in-a-class Operators. * Switched from const std::shared_ptr<>& to const& for parameters to equals() & hashCode() * Moved prototype for native_hash_code() inside requires-if-statement block; should fix unit test failure. * Reorganized logic to support call from C++ to Java (needs JNI hookups) * Removed debugging lines (caused unit tests to fail) * Updated with scalafmtAll * Added integration test * Applied formatting * Operator methods now conform to the ID settings * Removed old test code for reversing the 'requires' directive from C++ to Java * Fixed formatting * * added support for `ord` as implementing Java's Comparable<T>::compareTo() calling through to Operators::compare() * updated unit test to include tests for eq, ord & eq+ord * Add documentation for requires --------- Co-authored-by: Harald <harald.achitz@gmail.com>
1 parent f6bb7aa commit c9628e3

21 files changed

+808
-9
lines changed

docs/idl.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,45 @@ have special effects in C++:
298298
- `static` methods will become a static method of the C++ class, which can be called from other languages without an object.
299299
This is often useful for factory methods to act as a cross-language constructor.
300300

301+
#### Requires methods
302+
303+
```
304+
another_interface = interface +c +j {
305+
method_returning_nothing(value: i32);
306+
} requires (eq, ord)
307+
```
308+
309+
For interface types, Haskell-style "requires" declarations are supported to generate otherwise unavailable operator-like
310+
methods like equalTo(), hashCode(), and compare(). Similar to how Djinni is capable of generating equality and order
311+
comparators for records, "requires" declarations create overloaded operators in Java that call through to methods in C++
312+
that can be implemented using the desired logic.
313+
314+
In the example above, equality & ordering functions would be generated like so:
315+
316+
```
317+
class AnotherInterface {
318+
public:
319+
virtual ~AnotherInterface() {}
320+
321+
virtual void MethodReturningNothing(int32_t value) = 0;
322+
323+
class Operators {
324+
public:
325+
// these are generated by specifying 'requires(eq)'
326+
static bool Equals(const AnotherInterface& left, const AnotherInterface& right);
327+
static int32_6 Hash(const AnotherInterface& object);
328+
329+
// this is generated by specifying 'requires(ord)'
330+
static int32_t Compare(const AnotherInterface& left, const AnotherInterface& right);
331+
};
332+
};
333+
```
334+
335+
!!! note
336+
337+
- Adding "requires(ord)" will make the generated Java class derive from "Comparable<>".
338+
- Currently, only Java supports "requires"; other languages will simply ignore the notation.
339+
301340
#### Exception Handling
302341
When an interface implemented in C++ throws a `std::exception`, it will be translated to a
303342
`java.lang.RuntimeException` in Java, an `NSException` in Objective-C, a `RuntimeError` in Python,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
#pragma once
5+
6+
class RequiresAllInterface {
7+
public:
8+
virtual ~RequiresAllInterface() {}
9+
10+
virtual bool some_method() = 0;
11+
12+
class Operators {
13+
public:
14+
static bool equals(const RequiresAllInterface& left, const RequiresAllInterface& right);
15+
static int32_t hash_code(const RequiresAllInterface& object);
16+
static int32_t compare(const RequiresAllInterface& left, const RequiresAllInterface& right);
17+
};
18+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
#pragma once
5+
6+
class RequiresEqInterface {
7+
public:
8+
virtual ~RequiresEqInterface() {}
9+
10+
virtual bool some_method() = 0;
11+
12+
class Operators {
13+
public:
14+
static bool equals(const RequiresEqInterface& left, const RequiresEqInterface& right);
15+
static int32_t hash_code(const RequiresEqInterface& object);
16+
};
17+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
#pragma once
5+
6+
class RequiresOrdInterface {
7+
public:
8+
virtual ~RequiresOrdInterface() {}
9+
10+
virtual bool some_method() = 0;
11+
12+
class Operators {
13+
public:
14+
static int32_t compare(const RequiresOrdInterface& left, const RequiresOrdInterface& right);
15+
};
16+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
src/it/resources/result/requires/cpp-headers/requires_eq_interface.hpp
2+
src/it/resources/result/requires/cpp-headers/requires_ord_interface.hpp
3+
src/it/resources/result/requires/cpp-headers/requires_all_interface.hpp
4+
src/it/resources/result/requires/java/RequiresEqInterface.java
5+
src/it/resources/result/requires/java/RequiresOrdInterface.java
6+
src/it/resources/result/requires/java/RequiresAllInterface.java
7+
src/it/resources/result/requires/jni/djinni_jni_main.cpp
8+
src/it/resources/result/requires/jni-headers/requires_eq_interface.hpp
9+
src/it/resources/result/requires/jni/requires_eq_interface.cpp
10+
src/it/resources/result/requires/jni-headers/requires_ord_interface.hpp
11+
src/it/resources/result/requires/jni/requires_ord_interface.cpp
12+
src/it/resources/result/requires/jni-headers/requires_all_interface.hpp
13+
src/it/resources/result/requires/jni/requires_all_interface.cpp
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
package djinni.it;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
public abstract class RequiresAllInterface implements Comparable<RequiresAllInterface> {
9+
public abstract boolean someMethod();
10+
11+
private static final class CppProxy extends RequiresAllInterface
12+
{
13+
private final long nativeRef;
14+
private final AtomicBoolean destroyed = new AtomicBoolean(false);
15+
16+
private CppProxy(long nativeRef)
17+
{
18+
if (nativeRef == 0) throw new RuntimeException("nativeRef is zero");
19+
this.nativeRef = nativeRef;
20+
}
21+
22+
private native void nativeDestroy(long nativeRef);
23+
public void _djinni_private_destroy()
24+
{
25+
boolean destroyed = this.destroyed.getAndSet(true);
26+
if (!destroyed) nativeDestroy(this.nativeRef);
27+
}
28+
@SuppressWarnings("deprecation")
29+
protected void finalize() throws java.lang.Throwable
30+
{
31+
_djinni_private_destroy();
32+
super.finalize();
33+
}
34+
35+
@Override
36+
public boolean someMethod()
37+
{
38+
assert !this.destroyed.get() : "trying to use a destroyed object";
39+
return native_someMethod(this.nativeRef);
40+
}
41+
private native boolean native_someMethod(long _nativeRef);
42+
43+
@Override
44+
public boolean equals(Object obj) {
45+
assert !this.destroyed.get() : "trying to use a destroyed object";
46+
47+
if (!(obj instanceof RequiresAllInterface)) {
48+
return false;
49+
}
50+
51+
return native_operator_equals(this.nativeRef, (RequiresAllInterface)obj);
52+
}
53+
private native boolean native_operator_equals(long _nativeRef, RequiresAllInterface other);
54+
55+
@Override
56+
public int hashCode() {
57+
assert !this.destroyed.get() : "trying to use a destroyed object";
58+
return native_hash_code(this.nativeRef);
59+
}
60+
private native int native_hash_code(long _nativeRef);
61+
62+
@Override
63+
public int compareTo(RequiresAllInterface obj) {
64+
assert !this.destroyed.get() : "trying to use a destroyed object";
65+
66+
return native_compare(this.nativeRef, obj);
67+
}
68+
private native int native_compare(long _nativeRef, RequiresAllInterface other);
69+
}
70+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
package djinni.it;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
public abstract class RequiresEqInterface {
9+
public abstract boolean someMethod();
10+
11+
private static final class CppProxy extends RequiresEqInterface
12+
{
13+
private final long nativeRef;
14+
private final AtomicBoolean destroyed = new AtomicBoolean(false);
15+
16+
private CppProxy(long nativeRef)
17+
{
18+
if (nativeRef == 0) throw new RuntimeException("nativeRef is zero");
19+
this.nativeRef = nativeRef;
20+
}
21+
22+
private native void nativeDestroy(long nativeRef);
23+
public void _djinni_private_destroy()
24+
{
25+
boolean destroyed = this.destroyed.getAndSet(true);
26+
if (!destroyed) nativeDestroy(this.nativeRef);
27+
}
28+
@SuppressWarnings("deprecation")
29+
protected void finalize() throws java.lang.Throwable
30+
{
31+
_djinni_private_destroy();
32+
super.finalize();
33+
}
34+
35+
@Override
36+
public boolean someMethod()
37+
{
38+
assert !this.destroyed.get() : "trying to use a destroyed object";
39+
return native_someMethod(this.nativeRef);
40+
}
41+
private native boolean native_someMethod(long _nativeRef);
42+
43+
@Override
44+
public boolean equals(Object obj) {
45+
assert !this.destroyed.get() : "trying to use a destroyed object";
46+
47+
if (!(obj instanceof RequiresEqInterface)) {
48+
return false;
49+
}
50+
51+
return native_operator_equals(this.nativeRef, (RequiresEqInterface)obj);
52+
}
53+
private native boolean native_operator_equals(long _nativeRef, RequiresEqInterface other);
54+
55+
@Override
56+
public int hashCode() {
57+
assert !this.destroyed.get() : "trying to use a destroyed object";
58+
return native_hash_code(this.nativeRef);
59+
}
60+
private native int native_hash_code(long _nativeRef);
61+
}
62+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
package djinni.it;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
public abstract class RequiresOrdInterface implements Comparable<RequiresOrdInterface> {
9+
public abstract boolean someMethod();
10+
11+
private static final class CppProxy extends RequiresOrdInterface
12+
{
13+
private final long nativeRef;
14+
private final AtomicBoolean destroyed = new AtomicBoolean(false);
15+
16+
private CppProxy(long nativeRef)
17+
{
18+
if (nativeRef == 0) throw new RuntimeException("nativeRef is zero");
19+
this.nativeRef = nativeRef;
20+
}
21+
22+
private native void nativeDestroy(long nativeRef);
23+
public void _djinni_private_destroy()
24+
{
25+
boolean destroyed = this.destroyed.getAndSet(true);
26+
if (!destroyed) nativeDestroy(this.nativeRef);
27+
}
28+
@SuppressWarnings("deprecation")
29+
protected void finalize() throws java.lang.Throwable
30+
{
31+
_djinni_private_destroy();
32+
super.finalize();
33+
}
34+
35+
@Override
36+
public boolean someMethod()
37+
{
38+
assert !this.destroyed.get() : "trying to use a destroyed object";
39+
return native_someMethod(this.nativeRef);
40+
}
41+
private native boolean native_someMethod(long _nativeRef);
42+
43+
@Override
44+
public int compareTo(RequiresOrdInterface obj) {
45+
assert !this.destroyed.get() : "trying to use a destroyed object";
46+
47+
return native_compare(this.nativeRef, obj);
48+
}
49+
private native int native_compare(long _nativeRef, RequiresOrdInterface other);
50+
}
51+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
#pragma once
5+
6+
#include "djinni/jni/djinni_support.hpp"
7+
#include "requires_all_interface.hpp"
8+
9+
namespace djinni_generated {
10+
11+
class RequiresAllInterface final : ::djinni::JniInterface<::RequiresAllInterface, RequiresAllInterface> {
12+
public:
13+
using CppType = std::shared_ptr<::RequiresAllInterface>;
14+
using CppOptType = std::shared_ptr<::RequiresAllInterface>;
15+
using JniType = jobject;
16+
17+
using Boxed = RequiresAllInterface;
18+
19+
~RequiresAllInterface();
20+
21+
static CppType toCpp(JNIEnv* jniEnv, JniType j) { return ::djinni::JniClass<RequiresAllInterface>::get()._fromJava(jniEnv, j); }
22+
static ::djinni::LocalRef<JniType> fromCppOpt(JNIEnv* jniEnv, const CppOptType& c) { return {jniEnv, ::djinni::JniClass<RequiresAllInterface>::get()._toJava(jniEnv, c)}; }
23+
static ::djinni::LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c) { return fromCppOpt(jniEnv, c); }
24+
25+
private:
26+
RequiresAllInterface();
27+
friend ::djinni::JniClass<RequiresAllInterface>;
28+
friend ::djinni::JniInterface<::RequiresAllInterface, RequiresAllInterface>;
29+
30+
};
31+
32+
} // namespace djinni_generated
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// AUTOGENERATED FILE - DO NOT MODIFY!
2+
// This file was generated by Djinni from requires.djinni
3+
4+
#pragma once
5+
6+
#include "djinni/jni/djinni_support.hpp"
7+
#include "requires_eq_interface.hpp"
8+
9+
namespace djinni_generated {
10+
11+
class RequiresEqInterface final : ::djinni::JniInterface<::RequiresEqInterface, RequiresEqInterface> {
12+
public:
13+
using CppType = std::shared_ptr<::RequiresEqInterface>;
14+
using CppOptType = std::shared_ptr<::RequiresEqInterface>;
15+
using JniType = jobject;
16+
17+
using Boxed = RequiresEqInterface;
18+
19+
~RequiresEqInterface();
20+
21+
static CppType toCpp(JNIEnv* jniEnv, JniType j) { return ::djinni::JniClass<RequiresEqInterface>::get()._fromJava(jniEnv, j); }
22+
static ::djinni::LocalRef<JniType> fromCppOpt(JNIEnv* jniEnv, const CppOptType& c) { return {jniEnv, ::djinni::JniClass<RequiresEqInterface>::get()._toJava(jniEnv, c)}; }
23+
static ::djinni::LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c) { return fromCppOpt(jniEnv, c); }
24+
25+
private:
26+
RequiresEqInterface();
27+
friend ::djinni::JniClass<RequiresEqInterface>;
28+
friend ::djinni::JniInterface<::RequiresEqInterface, RequiresEqInterface>;
29+
30+
};
31+
32+
} // namespace djinni_generated

0 commit comments

Comments
 (0)