jextract
jextract copied to clipboard
Generate Java enums
We are using jextract in our cuVS Java project. As of now, jextract generates individual getter functions for the C enums. This results in code where it is difficult to disambiguate which getter functions are logically grouped together for a particular Enum. Additionally, given the lack of this capability, we also have to manually create and maintain Java enums for each enum in the C layer.
In this PR, I wish to introduce an option to additionally generate Java enums by passing an optional flag --generate-java-enums. In case a Java user wishes to use bitwise operations on the enums, they can use the enum's .ordinal() method for this. The original static getter methods will still be generated as before.
Example C enum:
enum SIZE {
S,
M,
L
};
Produces the following with jextract:
private static final int S = (int)0L;
public static int S() {
return S;
}
private static final int M = (int)1L;
public static int M() {
return M;
}
private static final int L = (int)2L;
public static int L() {
return L;
}
With this feature enabled using the --generate-java-enums flag, the following enum is generated (in addition to the above):
public enum Size {
S(0),
M(1),
L(2);
private final int value;
private Size(int value) {;
this.value = value;
}
public int getValue() {
return this.value;
}
}
This PR is created against the jdk22 branch. I will rebase it to master branch if needed.
Progress
- [x] Change must not contain extraneous whitespace
- [x] Change must be properly reviewed (no review required)
Reviewing
Using git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jextract.git pull/284/head:pull/284
$ git checkout pull/284
Update a local copy of the PR:
$ git checkout pull/284
$ git pull https://git.openjdk.org/jextract.git pull/284/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 284
View PR using the GUI difftool:
$ git pr show -t 284
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jextract/pull/284.diff
Using Webrev
Hi @narangvivek10, welcome to this OpenJDK project and thanks for contributing!
We do not recognize you as Contributor and need to ensure you have signed the Oracle Contributor Agreement (OCA). If you have not signed the OCA, please follow the instructions. Please fill in your GitHub username in the "Username" field of the application. Once you have signed the OCA, please let us know by writing /signed in a comment in this pull request.
If you already are an OpenJDK Author, Committer or Reviewer, please click here to open a new issue so that we can record that fact. Please use "Add GitHub user narangvivek10" as summary for the issue.
If you are contributing this work on behalf of your employer and your employer has signed the OCA, please let us know by writing /covered in a comment in this pull request.
@narangvivek10 This change now passes all automated pre-integration checks.
After integration, the commit message for the final commit will be:
Generate Java enums
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.
At the time when this comment was updated there had been no new commits pushed to the jdk22 branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.
As you do not have Committer status in this project an existing Committer must agree to sponsor your change.
➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).
/signed
Thank you! Please allow for up to two weeks to process your OCA, although it is usually done within one to two business days. Also, please note that pull requests that are pending an OCA check will not usually be evaluated, so your patience is appreciated!
In general we have not used the translation to Java enum because it's not 100% semantics preserving. In C enum constants are more of loose constants -- given:
enum Color
{
Red,
Green,
Blue,
}
C code can do things like Red | Blue -- which are not possible with Java enums.
Moreover, C enum constants live in the same namespace as global variables. So it seemed again honest to allow clients to access enum constants directly after having imported (statically) the main generated header class.
While in some ways using Java enums to model C enums look more natural, it also represents a significant departure from C. I can imagine that some simple uses of C enums would be quite happy with the approach you propose -- but this approach would not scale for more complex uses.
The philosophy of jextract has, so far, been to generate a set of bindings that was as close as possible to the semantics of the extracted header. That has been the main guiding principle. There's a lot of "creativity" jextract could provide on top (and enums is a good example), but adding creativity almost always results in binding that work better in some cases and worse in others. For these reasons, I don't think this PR fits well with the design principles of jextract.
One less radical move could be to give up to the "single namespace" property, and at least group related enum constants together, under a separate generated class. That would mean the Java code would require more "qualifiers" than the associated C code, but with some better separation. Would something like that be enough? Or do you really need these constants to be enum constants (e.g. because you use them in exhaustive switches etc.) ?
Another thing that came to mind: if we translate C enums as Java enums -- how is a native function accepting a C enum type translated? If we translate it as an int-accepting function (what we do now) then the Java enum constants cannot be used to call into native functions. If we translate it as a Java-enum-accepting function, then we would make these function stricter than their C counterparts.
One less radical move could be to give up to the "single namespace" property, and at least group related enum constants together, under a separate generated class. That would mean the Java code would require more "qualifiers" than the associated C code, but with some better separation. Would something like that be enough? Or do you really need these constants to be enum constants (e.g. because you use them in exhaustive switches etc.) ?
Thanks @mcimadamore for your feedback. Yes, if we can at least group related enum constants together under a separate generated class, that will be awesome. Would it be okay if I update this PR to have this change? Please let me know. Thanks!
enum Color { Red, Green, Blue, }C code can do things like
Red | Blue-- which are not possible with Java enums.
Hey @mcimadamore please excuse my gap in understanding here, but won't this operation be possible in Java by doing: Color.Red.ordinal() | Color.Blue.ordinal()?
Mailing list message from David Alayachew on jextract-dev:
No, because ordinal returns 0, 1, 2, 3, 4, 5, etc for each subsequent value. Whereas, it sounds like you need it to return 1, 2, 4, 8, etc. You could do the transformation yourself, of course.
But if that doesn't work, maybe come at this from the opposite direction?
What, in Java, gives you this OR-like functionality for enums? EnumSet! Maybe the better answer would be to have some way to return a long or long[] from an EnumSet to represent the included values?
On Mon, Jul 7, 2025, 2:40?PM Vivek Narang <duke at openjdk.org> wrote:
-------------- next part -------------- An HTML attachment was scrubbed... URL: <https://mail.openjdk.org/pipermail/jextract-dev/attachments/20250707/87802601/attachment.htm>
Hey @mcimadamore please excuse my gap in understanding here, but won't this operation be possible in Java by doing:
Color.Red.ordinal() | Color.Blue.ordinal()?
As said in another comment, there's no guarantee that the Java enum ordinal and the value of the C enum constant will be the same. So, no, that would not be a good translation. Also, please consider clients passing enums to native functions -- right now they can just say e.g.
native_func(RED);
It would be sad if they had to do:
native_func(RED.ordinal());
Of course one could imagine generating one extra overload for each enum-accepting function -- but that has issues:
- you can only use the Java enum-accepting overload in some cases, but not other (where you want to use bitwise ops)
- if a function uses N enum types, you need 2^N overloads (at least if you want to generate all possible combinations)
I don't think much joy is to be had by modelling C enums with Java enums. We've been at it several times, and we keep retracing our steps back to the same answers.
Thanks @mcimadamore for your feedback. Yes, if we can at least group related enum constants together under a separate generated class, that will be awesome. Would it be okay if I update this PR to have this change? Please let me know. Thanks!
I think we should ponder this some more. For simple use cases, it is really great to just have all the enum constants "in scope" in the same way as it happens with C. W/o that, the user would be left to try and figure out "what to include" for each enum. This might not be too obvious because the C source file one is trying to convert might only refer to enum constants but never to the enum type bringing these constants into existence. E.g. a C source might just use Red and Green and pass them to functions (and maybe these functions just take an int) -- and the code might never utter Color, ever. As a jextract user, how would I know that I need to import static foo.Color.* to get there?
I'm not saying this is a blocker -- just listing all the various potential issues with changing the status quo (also as a way to explain why we are where we are).
What, in Java, gives you this OR-like functionality for enums? EnumSet!
Modelling each enum value as an enum set is certainly possible. It also sounds as overkill. One of the principles of jextract is to add as little overhead as possible. I think here we should just be honest and admit that C enums and Java enums don't have much in common, except for the use of the word enum.
What, in Java, gives you this OR-like functionality for enums? EnumSet!
Modelling each enum value as an enum set is certainly possible. It also sounds as overkill. One of the principles of jextract is to add as little overhead as possible. I think here we should just be honest and admit that C enums and Java enums don't have much in common, except for the use of the word
enum.
(note -- this does NOT mean that other, more specialized, code generators could not model C enums as Java enums. Perhaps in some code bases that makes total sense. But jextract is intended to provide reliable bindings for all C headers -- which sort of restricts the kind of tricks we can play)
Hey @mcimadamore please excuse my gap in understanding here, but won't this operation be possible in Java by doing:
Color.Red.ordinal() | Color.Blue.ordinal()?As said in another comment, there's no guarantee that the Java enum ordinal and the value of the C enum constant will be the same. So, no, that would not be a good translation. Also, please consider clients passing enums to native functions -- right now they can just say e.g.
native_func(RED);
Isn't this what the enum getValue() method included in this PR is for? E.g. Color.Red.getValue() | Color.Blue.getValue() and native_func(RED.getValue()).
But the other concerns mentioned above still remain, such as making it difficult to discover which enum to use when a jextract-generated method just accepts an int, and that users might still accidentally use ordinal() or MyEnum.values()[i] instead of the getValue() method.
@narangvivek10 This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!
/keepalive
@narangvivek10 The pull request is being re-evaluated and the inactivity timeout has been reset.
@narangvivek10 This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!
/keepalive
@narangvivek10 The pull request is being re-evaluated and the inactivity timeout has been reset.
@narangvivek10 This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!
Apologies for the significant delay of several weeks on this. I will revisit this feature and the suggestions/feedback and will create a fresh PR (if needed) in the near future. Thanks!