Skip to content

Allow custom discriminant values for enum types #2114

Open

Description

Issue To Be Solved

Currently, we allow for C-style enumerations in Cadence, for instance:

pub enum Colour: UInt8 {
  pub case Red
  pub case Green
  pub case Blue
}

While we offer the developer flexibility in choosing the underlying type for their enum, we restrict the possible enum values themselves to a monotonic sequence [0..]. This is unnecessarily restrictive and prevents patterns like bitmasks, enum values that map directly to foreign types, etc.

For clarity, I will use discriminant and value interchangeably to refer to the raw integer representation of an enum.

A similar issue, #1528, suggests that enums be extended to support strings as well. Without the ability to specify custom discriminant values, any implementation for that issue would be stuck using Cadence-formatted string values for enums. For instance,

pub enum Colour: String {
  pub case Red
  pub case Green
  pub case Blue
}

Colour.Red.rawValue // "Red"
Colour.Green.rawValue // "Green"
Colour.Blue.rawValue // "Blue"

In this example, what happens if a script wants to marshal a string key into a string-backed enum? While it makes sense to use each case's raw name as its string value in most cases, it's unnecessarily restrictive in situations where a foreign type already exists and uses a different naming convention. For organizations that use json-schema or OpenAPI, it may be clearer to use a consistent key scheme across components, rather than having Cadence values be the sole exception and require string conversions.

Extending enums to support strings is outside the scope of this feature, but this is a stepping stone to adding that functionality to Cadence.

Suggested Solution

Modify enum declarations to allow developers to supply custom values. If an assignment is omitted for a given enum case, then the value is inferred to be either 0 if it's the first case, or the previous case + 1.

A few examples:

Bitmasks:

pub enum KittyTraits: UInt8 {
  pub case Floofy = 1 << 0 // 0x1
  pub case Rude = 1 << 1 // 0x2
  ...
  pub case Cute = 1 << 7 // 0x80
}

Starting an enum sequence from a different number:

pub enum HTTPError: UInt16 {
  pub case BadRequest = 400
  pub case Unauthorized // automatically set to 401
  pub case PaymentRequired // 402
  pub case Forbidden // 403
  ...
  pub case InternalServerError = 500 // count resumes from this value
  pub case NotImplemented // 501
  ...
}

Declaring enum cases in a different order from their raw values, which can improve code clarity:

pub enum TopShotPlayersChicago: UInt16 {
  pub case Antetokounmpo = 37
  pub case Ball = 2
  pub case Bradley = 13
  pub case Caruso = 6
  pub case DeRozan = 11
  ...
}

Considerations

Allowing custom discriminant values comes with some caveats:

  1. Overflow checking
    Any custom values supplied by the developer need to fit within the enum's backing type. For example, 257 is not a valid rawValue for an enum backed by UInt8. I believe we already have some logic in the checker to verify for a given integer type type that numCases <= maxValue(type).

  2. Uniqueness
    No two enum tags may share the same rawValue. This can be verified in the typechecking stage.

  3. Enum values must be constant expressions.
    We currently restrict enums to be backed by integer types only, but allowing arbitrary discriminant values raises the question of how much computation to allow when verifying and rewriting enum definitions. We currently build lookup tables for enum constructors and switch statements, which requires all discriminant values to be statically known.

@dsainati1 has recently added view functions to Cadence, which are an opt-in feature that allows pure functions to be statically declared as such. Purity in this case however, denotes observable side effects, not constant expressions.

Therefore, a potential approach to this problem is to require that custom enum values consist solely of integer literals and arithmetic expressions. This proposal prohibits function calls in constant contexts for the time being. In the future, once Cadence becomes a compiled language and we formalize constant expressions in the language, this restriction could be lifted to permit certain classes of functions to be called when computing enum values, similar to Rust's const fn feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions