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

[compiler] Add Enum.KNOWN__ as an intermediary interface #6248

Merged
merged 5 commits into from
Nov 8, 2024

Conversation

martinbonnin
Copy link
Contributor

Closes #6243

Generated code

At a high level, this change introduces an intermediate KNOWN__ interface, symmetrical with UNKNOWN__:

public sealed interface Color {
  public val rawValue: String

  public object BLUEBERRY : KNOWN__ {
    override val rawValue: String = "BLUEBERRY"
  }

  public object CHERRY : KNOWN__ {
    override val rawValue: String = "CHERRY"
  }

  public object CANDY : KNOWN__ {
    override val rawValue: String = "CANDY"
  }

  /**
   * An enum value that is known at build time.
   */
  @Suppress("ClassName")
  public sealed interface KNOWN__ : Color {
    override val rawValue: String
  }

  /**
   * An enum value that isn't known at build time.
   */
  @Suppress("ClassName")
  public class UNKNOWN__ @ApolloPrivateEnumConstructor constructor(
    override val rawValue: String,
  ) : Color 
}

Usage

This allows unwrapping an unknown value to a known one to provide a "default":

  /**
   * Turns a maybe unknown color value into a known one
   */
  private fun Color.orDefault(): Color.KNOWN__ = when (this) {
    is Color.UNKNOWN__ -> Color.CANDY
    is Color.KNOWN__ -> this
  }

This way, callers don't have to switch on the unknown case:

    when (color.orDefault()) {
      Color.BLUEBERRY -> TODO()
      Color.CANDY -> TODO()
      Color.CHERRY -> TODO()
      // no need to check for unknown here
    }

Compatibility

I contemplated adding another option but ultimately decided to reuse sealedClassesAsEnumMatching.

This is a source-compatible change but a binary breaking change because of the changing of UNKNOWN__ from interface to class.

See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.4:

If C is not an interface, interface method resolution throws an IncompatibleClassChangeError. 

You are impacted if you are shipping a library that exposes a generated SomeEnum.UNKNOWN__ in its public API.

I'm hoping that this pattern is not too widespread. If it is, and you're reading this, past me apologize in advance 😬 .

@martinbonnin martinbonnin requested a review from BoD as a code owner November 8, 2024 11:51
@svc-apollo-docs
Copy link
Collaborator

svc-apollo-docs commented Nov 8, 2024

❌ Docs Preview Failed

Error

Error: Failed to fetch GitHub source: Not Found

Comment on lines +11 to +13
public abstract interface annotation class com/apollographql/apollo/annotations/ApolloPrivateEnumConstructor : java/lang/annotation/Annotation {
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces the Opt-in marker that we removed in d160e98.

The underlying reason is that Kotlin doesn't support static factory method (companion obejcts cannot access private constructors of nested classes).

The previous trick of extracting a public interface doesn't work with sealed classes because the compiler insists on having UNKNOWN__Impl be part of the exhaustive when statements and it's private in the file. Maybe a future version of the compiler could fix that but as of today I haven't found a way.

Copy link
Contributor

@BoD BoD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

martinbonnin and others added 2 commits November 8, 2024 14:50
…ollographql/apollo/gradle/api/Service.kt

Co-authored-by: Benoit 'BoD' Lubek <[email protected]>
…ollo/compiler/codegen/kotlin/schema/EnumAsSealedInterfaceBuilder.kt

Co-authored-by: Benoit 'BoD' Lubek <[email protected]>
@martinbonnin martinbonnin merged commit b136060 into main Nov 8, 2024
2 checks passed
@martinbonnin martinbonnin deleted the add-known branch November 8, 2024 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Generate a Kotlin enum without the UNKNOWN__ case
3 participants