Skip to content

Conversation

dmlloyd
Copy link
Member

@dmlloyd dmlloyd commented May 28, 2025

Add a mechanism to define the default flags of members. Fixes #222.

This is ready for review now. I dropped the idea of categories as it just wasn't working.

I've made a couple usability improvements. If a flag is always required by some specific kind of member or entity, then adding that flag is always allowed (as a no-op). If a flag is forbidden by some kind of member or entity, then removing that flag is also allowed (as a no-op).

I've added some missing flags and fixed some logical inconsistencies around interface default vs private methods and native methods. I've moved the logic about mapping modifiers to their corresponding allowed members to the ModifierLocation enum, so it's all centralized in one place. I've also moved the initial defaults to this enum. I've also introduced logic for mutually-exclusive modifiers like FINAL/ABSTRACT or FINAL/VOLATILE; setting one will clear any flags which are mutually exclusive.

The implementation mechanism for setting the initial modifiers now centralized (for the most part).

@dmlloyd dmlloyd added the 2.x Issue applies to Gizmo 2.x label May 28, 2025
@dmlloyd dmlloyd marked this pull request as ready for review May 28, 2025 19:29
@Ladicek
Copy link
Contributor

Ladicek commented May 29, 2025

The refactoring of how modifiers are applied by default very much LGTM! I also see you opted for ergonomic defaults (classes/interfaces/methods public, fields private), which is OK (I actually like these defaults more than the specification defaults).

I'm not sure about the utility of the public API to change the defaults though, especially with the ergonomic defaults. If I think about what I might want to do, I can imagine:

  • make classes/interfaces public by default (they already are with the new defaults)
  • make classes/interfaces package-private by default (I don't think they can be private or protected?)
  • make methods public by default (they already are with the new defaults)
  • make methods private / protected / package-private by default
  • make fields private by default (they already are with the new defaults)
  • make fields public / protected / package-private by default
  • make classes/methods/fields final by default

That's all I can think of. I can't see anyone wanting to add any other modifier by default. If we really need to expose an API, something like this might be enough?

public interface DefaultsConfigurator {
    /**
     * Classes and interfaces are package-private by default. If not called, they are {@code public} by default.
     */
    void typesPackagePrivate();
    // alternatively, we could have: types(AccessLevel)

    /**
     * Methods on classes have the given {@code accessLevel} by default. If not called, they are {@code public} by default.
     * Methods on interfaces have a pre-determined access level that cannot be changed.
     */
    void methods(AccessLevel accessLevel);

    /**
     * Fields on classes have the given {@code accessLevel} by default. If not called, they are {@code private} by default.
     * Fields on interfaces are always {@code public static final}.
     */
    void fields(AccessLevel accessLevel);

    /**
     * Classes are {@code final} by default. If not called, they are not.
     * Interfaces are never {@code final}.
     */
    void classesFinal();

    /**
     * Methods on classes are {@code final} by default. If not called, they are not.
     * Methods on interfaces are never {@code final}.
     */
    void methodsFinal();

    /**
     * Fields on classes are {@code final} by default. If not called, they are not.
     * Fields on interfaces are always {@code public static final}.
     */
    void fieldsFinal();
}

I realize this is purpose-built and not entirely regular, and would probably have to change later with the introduction of member classes (which I'm not sure we need, frankly). On the other hand, it is a lot smaller and doesn't give rise to a huge number of combinations noone is ever going to use.

- The mutual-exclusion mechanism was faulty due to `ACC_VOLATILE` and `ACC_BRIDGE` sharing a bit (setting `FINAL` would clear `BRIDGE`)
- Fix missed builder delegation in static native builder
@dmlloyd
Copy link
Member Author

dmlloyd commented May 29, 2025

I think the general API is simple enough, and having it be internally uniform keeps things consistent and makes it easier to add new flags in the future (value types? who knows).

The problem with specific methods for categories is the same as the categories themselves: it's a little tricky to get them right, and useful categories might overlap: for example once we introduce nested classes, if I set "classes" to "package-private" I might be surprised that my nested classes didn't get affected - or I might be surprised if they did.

By spelling out each thing specifically, we'll never have to deal with these kinds of opinions. And, we keep things internally simple too, since adding creators for new kinds of things would require new modifier locations but otherwise modifiers would just work.

@Ladicek
Copy link
Contributor

Ladicek commented May 29, 2025

I don't particularly like the API, but it's small enough for me to not bother anymore :-)

@Ladicek Ladicek merged commit 492817f into quarkusio:main May 29, 2025
1 check passed
@dmlloyd dmlloyd deleted the flag-defaults branch May 29, 2025 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.x Issue applies to Gizmo 2.x
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Gizmo2: consider the default flags for classes and members
2 participants