Skip to content

Instantly share code, notes, and snippets.

@iscai-msft
Last active March 12, 2024 02:25
Show Gist options
  • Save iscai-msft/4174b2e13811c13d91af86c1296e9ed1 to your computer and use it in GitHub Desktop.
Save iscai-msft/4174b2e13811c13d91af86c1296e9ed1 to your computer and use it in GitHub Desktop.

Unions and Enums

Hello everyone, this gist is going to describe the canonical behavior of unions and enums.

Here's the conclusion that Laurent and I have come to. Once there is agreement across languages, we will be adding cadl-ranch tests to solidify this behavior.

Synopsis

  • Anonymous unions are always exposed as unions
  • Named unions are always exposed as enums
    • If there is a base type included anywhere at any level, it's extensible (non-fixed)
    • Otherwise, it's a regular enum (fixed)
    • Named unions also flatten all entries (including other enums and named unions) into itself, collapsing the hierarchy

Notes

isFixed means non-extensible. This doc completely follows the new semantic for what is determined as a fixed vs. non-fixed enum. It doesn't take azure or unbranded into account

Anonymous

All anonymous unions will be generated as unions.

Anonymous with base type

model Test {
  color: "red" | "blue" | string;
}
const enums = getAllModels(sdkContext).filter(x => x.kind === "enum");
strictEqual(enums.length, 0);
const colorTcgcProperty = {
  kind: "property",
  type: {
    kind: "union",
    name: undefined,
    generatedName: "TestColor",
    values: [
      {
        kind: "constant",
        value: "red",
      },
      {
        kind: "constant",
        value: "blue",
      },
      {
        kind: "string",
      }
    ],
  }
}

Anonymous without base type

model Test {
  color: "red" | "blue";
}
const enums = getAllModels.filter(x => x.kind === "enum");
strictEqual(enums.length, 0);
const colorTcgcProperty = {
  kind: "property",
  type: {
    kind: "union",
    name: undefined,
    generatedName: "TestColor",
    values: [
      {
        kind: "constant",
        value: "red",
      },
      {
        kind: "constant",
        value: "blue",
      },
    ],
  }
}

Anonymous union of enums

enum LR {
  left,
  right,
}
enum UD {
  up,
  down,
}

model Test {
  orientation: LR | UD;
}
const enums = getAllModels(sdkContext).find(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["LR", "UD"])
const orientationTcgcOutput = {
  kind: "property",
  type: {
    kind: "union",
    name: undefined,
    generatedName: "TestOrientation",
    values: [
      {
        kind: "enum",
        name: "LR",
        isFixed: true,
        values: [
          {
            kind: "enumvalue",
            name: "left",
            value: "left",
          },
          {
            kind: "enumvalue",
            name: "right",
            value: "right",
          }
        ],
      },
      {
        kind: "enum",
        name: "UD",
        isFixed: true,
        values: [
          {
            kind: "enumvalue",
            name: "up",
            value: "up",
          },
          {
            kind: "enumvalue",
            name: "down",
            value: "down",
          }
        ],
      },
    ]
  }
}

Named

All named unions will be generated as enums. This involves flattening out enums if they are the values in a named union.

Named union with base type

union Colors {
  red: "red",
  blue: "blue",
  string
}

model Test {
  color: Colors,
}
const enums = getAllModels(sdkContext).filter(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Colors"]);
const colorTcgcProperty = {
  kind: "property",
  type: {
    kind: "enum",
    name: Colors,
    generatedName: undefined,
    isFixed: false,
    values: [
      {
        kind: "enumvalue",
        value: "red",
      },
      {
        kind: "enumvalue",
        value: "blue",
      },
    ],
  }
}

Named union without base type

union Colors {
  red: "red",
  blue: "blue",
}

model Test {
  color: Colors,
}
const enums = getAllModels(sdkContext).filter(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Colors"]);
const colorTcgcProperty = {
  kind: "property",
  type: {
    kind: "enum",
    name: Colors,
    isFixed: true,
    values: [
      {
        kind: "enumvalue",
        name: "red",
        value: "red",
      },
      {
        kind: "enumvalue",
        name: "blue",
        value: "blue",
      },
    ],
  }
}

Named union of enums with base type

enum LR {
  left,
  right,
}
enum UD {
  up,
  down,
}

union Orientation {
  lr: LR,
  ud: UD,
  string,
}

model Test {
  orientation: Orientation;
}
const enums = getAllModels(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Orientation"]);
const orientationTcgcProperty = {
  kind: "property",
  type: {
    kind: "enum",
    name: "Orientation",
    isFixed: false,
    values: [
      {
        kind: "enumvalue",
        name: "left",
        value: "left",
      },
      {
        kind: "enumvalue",
        name: "right",
        value: "right",
      },
      {
        kind: "enumvalue",
        name: "up",
        value: "up",
      },
      {
        kind: "enumvalue",
        name: "down",
        value: "down",
      },
    ],
  }
}

Named union of enums without base type

enum LR {
  left,
  right,
}
enum UD {
  up,
  down,
}

union Orientation {
  lr: LR,
  ud: UD,
}

model Test {
  orientation: Orientation;
}
const enums = getAllModels(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Orientation"]);
const orientationTcgcProperty = {
  kind: "property",
  type: {
    kind: "enum",
    name: "Orientation",
    isFixed: true,
    values: [
      {
        kind: "enumvalue",
        name: "left",
        value: "left",
      },
      {
        kind: "enumvalue",
        name: "right",
        value: "right",
      },
      {
        kind: "enumvalue",
        name: "up",
        value: "up",
      },
      {
        kind: "enumvalue",
        name: "down",
        value: "down",
      },
    ],
  }
}

Named union of named unions with base type

union LR {
  left,
  right,
  string, // just LR is listed as extensible
}
union UD {
  up,
  down,
}

union Orientation {
  lr: LR,
  ud: UD,
}

model Test {
  orientation: Orientation;
}
const enums = getAllModels(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Orientation"]);
const orientationTcgcProperty = {
  kind: "property",
  type: {
    kind: "enum",
    name: "Orientation",
    isFixed: false,
    values: [
      {
        kind: "enumvalue",
        name: "left",
        value: "left",
      },
      {
        kind: "enumvalue",
        name: "right",
        value: "right",
      },
      {
        kind: "enumvalue",
        name: "up",
        value: "up",
      },
      {
        kind: "enumvalue",
        name: "down",
        value: "down",
      },
    ],
  }
}

Mix of Anonymous and Named

Anonymous union of named unions with base type

union LR {
  left: "left",
  right: "right",
  string,
}
union UD {
  up: "left",
  down: "down",
  string,
}

model Test {
  orientation: LR | UD;
}
const enums = getAllModels(sdkContext).find(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["LR", "UD"])
const orientationTcgcOutput = {
  kind: "property",
  type: {
    kind: "union",
    name: undefined,
    generatedName: "TestOrientation",
    values: [
      {
        kind: "enum",
        name: "LR",
        isFixed: false,
        values: [
          {
            kind: "enumvalue",
            name: "left",
            value: "left",
          },
          {
            kind: "enumvalue",
            name: "right",
            value: "right",
          }
        ],
      },
      {
        kind: "enum",
        name: "UD",
        isFixed: false,
        values: [
          {
            kind: "enumvalue",
            name: "up",
            value: "up",
          },
          {
            kind: "enumvalue",
            name: "down",
            value: "down",
          }
        ],
      },
    ]
  }
}

Anonymous union of named unions without base type

union LR {
  left: "left",
  right: "right",
}
union UD {
  up: "left",
  down: "down",
}

model Test {
  orientation: LR | UD;
}
const enums = getAllModels(sdkContext).find(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["LR", "UD"])
const orientationTcgcOutput = {
  kind: "property",
  type: {
    kind: "union",
    name: undefined,
    generatedName: "TestOrientation",
    values: [
      {
        kind: "enum",
        name: "LR",
        isFixed: true,
        values: [
          {
            kind: "enumvalue",
            name: "left",
            value: "left",
          },
          {
            kind: "enumvalue",
            name: "right",
            value: "right",
          }
        ],
      },
      {
        kind: "enum",
        name: "UD",
        isFixed: true,
        values: [
          {
            kind: "enumvalue",
            name: "up",
            value: "up",
          },
          {
            kind: "enumvalue",
            name: "down",
            value: "down",
          }
        ],
      },
    ]
  }
}

Named union of anonymous unions without base type

union Orientation {
  lr: "left" | "right",
  up: "up" | "down",
}

model Test {
  orientation: Orientation;
}
const enums = getAllModels(sdkContext).find(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Orientation"])
const orientationTcgcOutput = {
  kind: "property",
  type: {
    kind: "enum",
    name: "Orientation",
    isFixed: true,
    values: [
      {
        kind: "enumvalue",
        name: "left",
        value: "left",
      },
      {
        kind: "enumvalue",
        name: "right",
        value: "right",
      },
      {
        kind: "enumvalue",
        name: "up",
        value: "up",
      },
      {
        kind: "enumvalue",
        name: "down",
        value: "down",
      },
    ],
  }
}
union Orientation {
  lr: "left" | "right",
  up: "up" | "down",
  string,
}

model Test {
  orientation: Orientation;
}
const enums = getAllModels(sdkContext).find(x => x.kind === "enum");
deepStrictEqual(enums.map(x => x.name), ["Orientation"])
const orientationTcgcOutput = {
  kind: "property",
  type: {
    kind: "enum",
    name: "Orientation",
    isFixed: false,
    values: [
      {
        kind: "enumvalue",
        name: "left",
        value: "left",
      },
      {
        kind: "enumvalue",
        name: "right",
        value: "right",
      },
      {
        kind: "enumvalue",
        name: "up",
        value: "up",
      },
      {
        kind: "enumvalue",
        name: "down",
        value: "down",
      },
    ],
  }
}
@tadelesh
Copy link

  1. I suggest to keep consistent for named union as enum and anonymous union as enum logic in TCGC. Python could add logic to convert anonymous enum into union type.
  2. We could add a flag to determine whether flatten the union as enum with hierarchy.

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