///|
pub fn Parser::arm_type(
  self : Parser,
  depth : Int,
  names? : Map[String, Bool] = Map([]),
) -> Type raise XdrError {
  if self.peek() == "void" {
    self.expect("void")
    self.expect(";")
    TVoid
  } else {
    let field = self.declaration(depth)
    if names.contains(field.name) {
      raise Invalid(self.offset(), "duplicate union member")
    }
    names[field.name] = true
    self.expect(";")
    field.ty
  }
}

///|
pub fn Parser::compound(
  self : Parser,
  kind : String,
  depth : Int,
) -> Type raise XdrError {
  if depth > self.schema.limits.max_depth {
    raise Invalid(self.offset(), "schema nesting limit")
  }
  if kind == "enum" {
    self.expect("{")
    let values : Array[(String, Int)] = []
    let seen_values : Map[Int, Bool] = Map([])
    while self.peek() != "}" {
      let name = self.identifier()
      self.expect("=")
      let literal = self.value()
      let value = constant_i32(literal, self.offset())
      if seen_values.contains(value) {
        raise Invalid(self.offset(), "duplicate enum value")
      }
      seen_values[value] = true
      self.add_constant(name, literal, definition=false)
      values.push((name, value))
      if self.peek() != "," {
        break
      }
      self.expect(",")
      if self.peek() == "}" {
        raise Invalid(self.offset(), "trailing enum comma")
      }
    }
    self.expect("}")
    if values.is_empty() {
      raise Invalid(self.offset(), "empty enum")
    }
    return TEnum(values)
  }
  if kind == "struct" {
    self.expect("{")
    let fields : Array[Field] = []
    let seen_fields : Map[String, Bool] = Map([])
    let mut declarations = 0
    while self.peek() != "}" {
      let field = self.declaration(depth + 1)
      self.expect(";")
      declarations += 1
      // Anonymous void declarations contribute neither a field name nor bytes.
      if field.ty is TVoid {
        continue
      }
      if seen_fields.contains(field.name) {
        raise Invalid(self.offset(), "duplicate struct member")
      }
      seen_fields[field.name] = true
      fields.push(field)
    }
    self.expect("}")
    if declarations == 0 {
      raise Invalid(self.offset(), "empty struct")
    }
    return TStruct(fields)
  }
  if kind != "union" {
    raise Invalid(self.offset(), "unknown compound type")
  }
  self.expect("switch")
  self.expect("(")
  let discriminant = self.declaration(depth + 1)
  self.expect(")")
  self.expect("{")
  let names : Map[String, Bool] = Map([])
  names[discriminant.name] = true
  let arms : Array[Arm] = []
  let seen : Map[Int64, Bool] = Map([])
  let mut fallback : Type? = None
  while self.peek() != "}" {
    if self.peek() == "default" {
      if fallback is Some(_) {
        raise Invalid(self.offset(), "duplicate default arm")
      }
      self.expect("default")
      self.expect(":")
      fallback = Some(self.arm_type(depth + 1, names~))
    } else {
      let labels : Array[Int64] = []
      while self.peek() == "case" {
        self.expect("case")
        let label = constant_tag(self.value(), self.offset())
        self.expect(":")
        if seen.contains(label) {
          raise Invalid(self.offset(), "duplicate union case")
        }
        seen[label] = true
        labels.push(label)
      }
      if labels.is_empty() {
        raise Invalid(self.offset(), "expected union case or default")
      }
      arms.push({ labels, ty: self.arm_type(depth + 1, names~), })
    }
  }
  self.expect("}")
  if arms.is_empty() && fallback is None {
    raise Invalid(self.offset(), "empty union")
  }
  TUnion(discriminant.ty, arms, fallback)
}