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 support for equality operators via new 'requires' keyword for interfaces #169

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
17466d4
Added support for 'requiring' (will change to 'requires' eventually)
AndrewAtAvenza Jan 7, 2025
3972ff2
Renamed 'requiring' to 'requires'
AndrewAtAvenza Jan 7, 2025
756e0f1
Implemented basic equality for C++ & Java
AndrewAtAvenza Jan 8, 2025
5aeed7e
Added support for hashCode()
AndrewAtAvenza Jan 8, 2025
7e31885
Accidentally changed 'Bool' to 'I32' -- reverting
AndrewAtAvenza Jan 8, 2025
0126489
Different approaching using methods equals() & hashCode() as static m…
AndrewAtAvenza Jan 10, 2025
118e85a
Switched from const std::shared_ptr<>& to const& for parameters to eq…
AndrewAtAvenza Jan 10, 2025
ff551fb
Moved prototype for native_hash_code() inside requires-if-statement b…
AndrewAtAvenza Jan 11, 2025
f6bb7aa
New way of getting sbt (#172)
a4z Jan 13, 2025
5fcecba
Added support for 'requiring' (will change to 'requires' eventually)
AndrewAtAvenza Jan 7, 2025
a89c144
Renamed 'requiring' to 'requires'
AndrewAtAvenza Jan 7, 2025
78809dd
Implemented basic equality for C++ & Java
AndrewAtAvenza Jan 8, 2025
a70bf9f
Added support for hashCode()
AndrewAtAvenza Jan 8, 2025
0fc7b2c
Accidentally changed 'Bool' to 'I32' -- reverting
AndrewAtAvenza Jan 8, 2025
5ed88ef
Different approaching using methods equals() & hashCode() as static m…
AndrewAtAvenza Jan 10, 2025
13b48a9
Switched from const std::shared_ptr<>& to const& for parameters to eq…
AndrewAtAvenza Jan 10, 2025
c214cb8
Moved prototype for native_hash_code() inside requires-if-statement b…
AndrewAtAvenza Jan 11, 2025
efc65b1
Merge branch 'AndrewAtAvenza/requires' of github.com:AndrewAtAvenza/d…
AndrewAtAvenza Jan 13, 2025
675b900
Reorganized logic to support call from C++ to Java (needs JNI hookups)
AndrewAtAvenza Jan 13, 2025
11442eb
Removed debugging lines (caused unit tests to fail)
AndrewAtAvenza Jan 14, 2025
19acc8b
Updated with scalafmtAll
AndrewAtAvenza Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 28 additions & 22 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- uses: actions/checkout@v4
- name: Caching dependencies
uses: actions/cache@v3
- name: Setup JDK
uses: actions/setup-java@v4
with:
path: |
~/.sbt
~/.ivy2
key: scala-build-deps
distribution: temurin
java-version: 17
cache: sbt
- uses: sbt/setup-sbt@v1
- name: Building
run: sbt assembly
- uses: actions/upload-artifact@v4
Expand All @@ -29,11 +31,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Caching dependencies
uses: actions/cache@v3
- name: Setup JDK
uses: actions/setup-java@v4
with:
path: ~/.sbt
key: scala-fmt-deps
distribution: temurin
java-version: 17
cache: sbt
- uses: sbt/setup-sbt@v1
- name: "Format check generator"
run: sbt scalafmtCheck
- name: "Format check integration test"
Expand All @@ -43,13 +47,13 @@ jobs:
needs: [build, formatCheck]
steps:
- uses: actions/checkout@v4
- name: Caching dependencies
uses: actions/cache@v3
- name: Setup JDK
uses: actions/setup-java@v4
with:
path: |
~/.sbt
~/.ivy2
key: scala-build-deps
distribution: temurin
java-version: 17
cache: sbt
- uses: sbt/setup-sbt@v1
- uses: actions/download-artifact@v4
with:
name: djinni-generator
Expand All @@ -61,15 +65,17 @@ jobs:

buildWindows:
runs-on: windows-latest
env:
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- uses: actions/checkout@v4
- name: Caching dependencies
uses: actions/cache@v3
- name: Setup JDK
uses: actions/setup-java@v4
with:
path: |
~/.sbt
~/.ivy2
key: scala-build-deps-windows
distribution: temurin
java-version: 17
cache: sbt
- uses: sbt/setup-sbt@v1
- name: Building
run: sbt assembly
- name: Testing
Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ name: Upload Release Assets
jobs:
buildUnix:
runs-on: ubuntu-latest
env:
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- uses: actions/checkout@v4
- name: Caching dependencies
uses: actions/cache@v3
- name: Setup JDK
uses: actions/setup-java@v4
with:
path: |
~/.sbt
~/.ivy2
key: scala-build-deps
distribution: temurin
java-version: 17
cache: sbt
- uses: sbt/setup-sbt@v1
- name: Building
run: sbt assembly
- uses: actions/upload-artifact@v4
Expand Down
33 changes: 33 additions & 0 deletions src/main/scala/djinni/CppGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package djinni

import djinni.ast.Interface.RequiresType
import djinni.ast.Record.DerivingType
import djinni.ast._
import djinni.generatorTools._
Expand Down Expand Up @@ -744,6 +745,38 @@ class CppGenerator(spec: Spec) extends Generator(spec) {
.mkString("(", ", ", ")")}$constFlag = 0;")
}
}
// Requires
if (!i.requiresTypes.isEmpty) {
if (i.ext.cpp) {
w.wl
w.w("class Operators").bracedSemi {
w.wlOutdent("public:")
if (i.requiresTypes.contains(RequiresType.Eq)) {
w.wl(
s"static bool equals(const ${self}& left, const ${self}& right);"
)
w.wl
w.wl(s"static int32_t hashCode(const ${self}& object);")
}
}
}

// TODO: how to apply formating rules to equals/compareTo/hashCode?

if (i.ext.java) {
if (i.requiresTypes.contains(RequiresType.Eq)) {
w.wl
w.wl(s"virtual bool equals(const ${self}& other) const = 0;")
w.wl
w.wl(s"virtual int hashCode() const = 0;")
}

if (i.requiresTypes.contains(RequiresType.Ord)) {
w.wl
w.wl(s"virtual int compareTo(const ${self}& other) const = 0;")
}
}
}
}
}
)
Expand Down
47 changes: 47 additions & 0 deletions src/main/scala/djinni/JNIGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package djinni

import djinni.ast.Interface.RequiresType
import djinni.ast._
import djinni.generatorTools._
import djinni.meta._
Expand Down Expand Up @@ -591,6 +592,52 @@ class JNIGenerator(spec: Spec) extends Generator(spec) {
}
)
}
if (i.requiresTypes.contains(RequiresType.Eq)) {
val equalsName = "native_operator_equals"
val equalsMethodNameMunged = equalsName.replaceAllLiterally("_", "_1")
w.wl(
s"CJNIEXPORT jboolean JNICALL ${prefix}_00024CppProxy_$equalsMethodNameMunged(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef, jobject j_obj)"
).braced {
w.w("try")
.bracedEnd(
s" JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)"
) {
w.wl(s"DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);")
w.wl(
s"const auto& ref = ::djinni::objectFromHandleAddress<$cppSelf>(nativeRef);"
)
w.wl(
s"const auto& otherRef = ${withNs(Some(spec.jniNamespace), jniSelf)}::toCpp(jniEnv, j_obj);"
)
w.wl(s"auto r = $cppSelf::Operators::equals(*ref, *otherRef);")
w.wl(
"return ::djinni::release(::djinni::Bool::fromCpp(jniEnv, r));"
)
}
}

val hashCodeName = "native_hash_code"
val hashCodeMethodNameMunged =
hashCodeName.replaceAllLiterally("_", "_1")
w.wl(
s"CJNIEXPORT jint JNICALL ${prefix}_00024CppProxy_$hashCodeMethodNameMunged(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)"
).braced {
w.w("try")
.bracedEnd(
s" JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)"
) {
w.wl(s"DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);")
w.wl(
s"const auto& ref = ::djinni::objectFromHandleAddress<$cppSelf>(nativeRef);"
)
w.wl(s"auto r = $cppSelf::Operators::hashCode(*ref);")
w.wl(
"return ::djinni::release(::djinni::I32::fromCpp(jniEnv, r));"
)
}
}

}
}
}

Expand Down
70 changes: 68 additions & 2 deletions src/main/scala/djinni/JavaGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package djinni

import djinni.ast.Interface.RequiresType
import djinni.ast.Record.DerivingType
import djinni.ast._
import djinni.generatorTools._
Expand Down Expand Up @@ -196,6 +197,13 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {

javaAnnotationHeader.foreach(w.wl)

val interfaces = scala.collection.mutable.ArrayBuffer[String]()
if (i.requiresTypes.contains(RequiresType.Ord))
interfaces += s"Comparable<$javaClass>"
val implementsSection =
if (interfaces.isEmpty) ""
else " implements " + interfaces.mkString(", ")

// Generate an interface or an abstract class depending on whether the use
// of Java interfaces was requested.
val classPrefix =
Expand All @@ -206,7 +214,7 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
val innerClassAccessibility =
if (spec.javaGenerateInterfaces) "" else "private "
w.w(
s"${javaClassAccessModifierString}$classPrefix $javaClass$typeParamList"
s"${javaClassAccessModifierString}$classPrefix $javaClass$typeParamList$implementsSection"
).braced {
val skipFirst = SkipFirst()
generateJavaConstants(w, i.consts, spec.javaGenerateInterfaces)
Expand Down Expand Up @@ -262,6 +270,24 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
}
}

if (i.ext.java) {
if (i.requiresTypes.contains(RequiresType.Eq)) {
w.wl
w.wl("@Override")
if (i.ext.java) {
w.wl("public abstract boolean equals(@Nullable Object obj);")
}
}

if (i.requiresTypes.contains(RequiresType.Ord)) {
w.wl
w.wl("@Override")
if (i.ext.java) {
w.wl(s"public abstract int compareTo($javaClass other);")
}
}
}

if (i.ext.cpp) {
w.wl
javaAnnotationHeader.foreach(w.wl)
Expand Down Expand Up @@ -305,7 +331,7 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
m.params.map(p => idJava.local(p.ident)).mkString(", ")
val meth = idJava.method(m.ident)
w.wl
w.wl(s"@Override")
w.wl("@Override")
w.wl(s"public $ret $meth($params)$throwException").braced {
w.wl(
"assert !this.destroyed.get() : \"trying to use a destroyed object\";"
Expand All @@ -319,6 +345,46 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
)
}

if (i.requiresTypes.contains(RequiresType.Eq)) {
// equals() override
w.wl
w.wl("@Override")
val nullableAnnotation =
javaNullableAnnotation.map(_ + " ").getOrElse("")
w.w(s"public boolean equals(${nullableAnnotation}Object obj)")
.braced {
w.wl(
"assert !this.destroyed.get() : \"trying to use a destroyed object\";"
)
w.wl
w.w(s"if (!(obj instanceof $javaClass))").braced {
w.wl("return false;")
}
w.wl
w.wl(
s"return native_operator_equals(this.nativeRef, ($javaClass)obj);"
)
}
w.wl(
s"private native boolean native_operator_equals(long _nativeRef, $javaClass other);"
)

// hashCode() override
w.wl
w.wl("@Override")
w.w("public int hashCode()").braced {
w.wl(
"assert !this.destroyed.get() : \"trying to use a destroyed object\";"
)
w.wl(
s"return native_hash_code(this.nativeRef);"
)
}
w.wl(
s"private native int native_hash_code(long _nativeRef);"
)
}

// Declare a native method for each of the interface's static methods.
for (m <- i.methods if m.static) {
skipFirst { w.wl }
Expand Down
8 changes: 7 additions & 1 deletion src/main/scala/djinni/ast/ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package djinni.ast

import djinni.ast.Record.DerivingType.DerivingType
import djinni.ast.Interface.RequiresType.RequiresType
import djinni.meta.MExpr
import djinni.syntax.Loc

Expand Down Expand Up @@ -110,9 +111,14 @@ object Record {
case class Interface(
ext: Ext,
methods: Seq[Interface.Method],
consts: Seq[Const]
consts: Seq[Const],
requiresTypes: Set[RequiresType]
) extends TypeDef
object Interface {
object RequiresType extends Enumeration {
type RequiresType = Value
val Eq, Ord = Value
}
case class Method(
ident: Ident,
params: Seq[Field],
Expand Down
Loading
Loading