// Structural equality uses compiler-generated continuations. Keep each field
// here in step with types.mbt, including derived float_value fields that the
// canonical JSON intentionally omits.

///|
async fn[A] equal_array_view(
  left : ArrayView[A],
  right : ArrayView[A],
  equal_value : async (A, A) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  guard left.length() == right.length() else { return false }
  for i in 0.. Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  match (left, right) {
    (None, None) => true
    (Some(a), Some(b)) => equal_value(a, b)
    _ => false
  }
}

///|
async fn equal_arg(
  runner : @work.Runner,
  left : Arg,
  right : Arg,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.names == right.names &&
  left.location == right.location &&
  equal_option(left.annotation, right.annotation, (left, right) => {
    equal_type_ast(runner, left, right)
  })
}

///|
async fn equal_type_ast(
  runner : @work.Runner,
  left : TypeAst,
  right : TypeAst,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (Constructor(a0), Constructor(b0)) =>
      equal_type_ast_constructor(runner, a0, b0)
    (Fn(a0), Fn(b0)) => equal_type_ast_fn(runner, a0, b0)
    (Var(a0), Var(b0)) => a0 == b0
    (Tuple(a0), Tuple(b0)) => equal_type_ast_tuple(runner, a0, b0)
    (Hole(a0), Hole(b0)) => a0 == b0
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for TypeAst with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_type_ast(runner, left, right))
}

///|
async fn equal_type_ast_constructor(
  runner : @work.Runner,
  left : TypeAstConstructor,
  right : TypeAstConstructor,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  left.name == right.name &&
  equal_array_view(left.arguments, right.arguments, (left, right) => {
    equal_type_ast(runner, left, right)
  }) &&
  left.start_parentheses == right.start_parentheses
}

///|
async fn equal_type_ast_fn(
  runner : @work.Runner,
  left : TypeAstFn,
  right : TypeAstFn,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_array_view(left.arguments, right.arguments, (left, right) => {
    equal_type_ast(runner, left, right)
  }) &&
  equal_type_ast(runner, left.return_, right.return_)
}

///|
async fn equal_type_ast_tuple(
  runner : @work.Runner,
  left : TypeAstTuple,
  right : TypeAstTuple,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_array_view(left.elements, right.elements, (left, right) => {
    equal_type_ast(runner, left, right)
  })
}

///|
async fn equal_statement(
  runner : @work.Runner,
  left : Statement,
  right : Statement,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (Expression(a0), Expression(b0)) => equal_expr(runner, a0, b0)
    (Assignment(a0), Assignment(b0)) => equal_assignment(runner, a0, b0)
    (Use(a0), Use(b0)) => equal_use(runner, a0, b0)
    (Assert(a0), Assert(b0)) => equal_assert(runner, a0, b0)
    _ => false
  }
}

///|
async fn equal_assignment(
  runner : @work.Runner,
  left : Assignment,
  right : Assignment,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_expr(runner, left.value, right.value) &&
  equal_pattern(runner, left.pattern, right.pattern) &&
  equal_assignment_kind(runner, left.kind, right.kind) &&
  equal_option(left.annotation, right.annotation, (left, right) => {
    equal_type_ast(runner, left, right)
  })
}

///|
async fn equal_assignment_kind(
  runner : @work.Runner,
  left : AssignmentKind,
  right : AssignmentKind,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (Let, Let) => true
    (Generated, Generated) => true
    (
      Assert(location=a0, assert_keyword_start=a1, message=a2),
      Assert(location=b0, assert_keyword_start=b1, message=b2),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      equal_option(a2, b2, (left, right) => equal_expr(runner, left, right))
    _ => false
  }
}

///|
async fn equal_use(
  runner : @work.Runner,
  left : Use,
  right : Use,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  equal_expr(runner, left.call, right.call) &&
  left.location == right.location &&
  left.right_hand_side_location == right.right_hand_side_location &&
  left.assignments_location == right.assignments_location &&
  equal_array_view(left.assignments, right.assignments, (left, right) => {
    equal_use_assignment(runner, left, right)
  })
}

///|
async fn equal_use_assignment(
  runner : @work.Runner,
  left : UseAssignment,
  right : UseAssignment,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_pattern(runner, left.pattern, right.pattern) &&
  equal_option(left.annotation, right.annotation, (left, right) => {
    equal_type_ast(runner, left, right)
  })
}

///|
async fn equal_assert(
  runner : @work.Runner,
  left : Assert,
  right : Assert,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_expr(runner, left.value, right.value) &&
  equal_option(left.message, right.message, (left, right) => {
    equal_expr(runner, left, right)
  })
}

///|
async fn equal_expr(
  runner : @work.Runner,
  left : Expr,
  right : Expr,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (
      Int(location=a0, value=a1, int_value=a2),
      Int(location=b0, value=b1, int_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (
      Float(location=a0, value=a1, float_value=a2),
      Float(location=b0, value=b1, float_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (String(location=a0, value=a1), String(location=b0, value=b1)) =>
      a0 == b0 && a1 == b1
    (Block(location=a0, statements=a1), Block(location=b0, statements=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_statement(runner, left, right)
      })
    (Var(location=a0, name=a1), Var(location=b0, name=b1)) =>
      a0 == b0 && a1 == b1
    (
      Fn(
        location=a0,
        kind=a1,
        end_of_head_byte_index=a2,
        arguments=a3,
        body=a4,
        return_annotation=a5
      ),
      Fn(
        location=b0,
        kind=b1,
        end_of_head_byte_index=b2,
        arguments=b3,
        body=b4,
        return_annotation=b5
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      equal_array_view(a3, b3, (left, right) => equal_arg(runner, left, right)) &&
      equal_array_view(a4, b4, (left, right) => {
        equal_statement(runner, left, right)
      }) &&
      equal_option(a5, b5, (left, right) => equal_type_ast(runner, left, right))
    (
      List(location=a0, elements=a1, tail=a2),
      List(location=b0, elements=b1, tail=b2),
    ) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => equal_expr(runner, left, right)) &&
      equal_option(a2, b2, (left, right) => equal_expr(runner, left, right))
    (
      Call(location=a0, fun=a1, arguments=a2, open_parenthesis=a3),
      Call(location=b0, fun=b1, arguments=b2, open_parenthesis=b3),
    ) =>
      a0 == b0 &&
      equal_expr(runner, a1, b1) &&
      equal_array_view(a2, b2, (left, right) => {
        equal_call_arg(runner, left, right, (left, right) => {
          equal_expr(runner, left, right)
        })
      }) &&
      a3 == b3
    (
      BinOp(location=a0, operator=a1, operator_start=a2, left=a3, right=a4),
      BinOp(location=b0, operator=b1, operator_start=b2, left=b3, right=b4),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      equal_expr(runner, a3, b3) &&
      equal_expr(runner, a4, b4)
    (Pipeline(expressions=a0), Pipeline(expressions=b0)) =>
      equal_array_view(a0, b0, (left, right) => equal_expr(runner, left, right))
    (
      Case(location=a0, subjects=a1, clauses=a2),
      Case(location=b0, subjects=b1, clauses=b2),
    ) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => equal_expr(runner, left, right)) &&
      equal_option(a2, b2, (left, right) => {
        equal_array_view(left, right, (left, right) => {
          equal_clause(runner, left, right)
        })
      })
    (
      FieldAccess(location=a0, label_location=a1, label=a2, container=a3),
      FieldAccess(location=b0, label_location=b1, label=b2, container=b3),
    ) => a0 == b0 && a1 == b1 && a2 == b2 && equal_expr(runner, a3, b3)
    (Tuple(location=a0, elements=a1), Tuple(location=b0, elements=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => equal_expr(runner, left, right))
    (
      TupleIndex(location=a0, index=a1, tuple=a2),
      TupleIndex(location=b0, index=b1, tuple=b2),
    ) => a0 == b0 && a1 == b1 && equal_expr(runner, a2, b2)
    (
      Todo(kind=a0, location=a1, message=a2),
      Todo(kind=b0, location=b1, message=b2),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      equal_option(a2, b2, (left, right) => equal_expr(runner, left, right))
    (Panic(location=a0, message=a1), Panic(location=b0, message=b1)) =>
      a0 == b0 &&
      equal_option(a1, b1, (left, right) => equal_expr(runner, left, right))
    (
      Echo(location=a0, keyword_end=a1, expression=a2, message=a3),
      Echo(location=b0, keyword_end=b1, expression=b2, message=b3),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      equal_option(a2, b2, (left, right) => equal_expr(runner, left, right)) &&
      equal_option(a3, b3, (left, right) => equal_expr(runner, left, right))
    (BitArray(location=a0, segments=a1), BitArray(location=b0, segments=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_bit_array_segment(runner, left, right, (left, right) => {
          equal_expr(runner, left, right)
        })
      })
    (
      RecordUpdate(
        location=a0,
        spread_start=a1,
        constructor_=a2,
        record=a3,
        arguments=a4
      ),
      RecordUpdate(
        location=b0,
        spread_start=b1,
        constructor_=b2,
        record=b3,
        arguments=b4
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      equal_expr(runner, a2, b2) &&
      equal_record_being_updated(runner, a3, b3, (left, right) => {
        equal_expr(runner, left, right)
      }) &&
      equal_array_view(a4, b4, (left, right) => {
        equal_record_update_arg(runner, left, right, (left, right) => {
          equal_expr(runner, left, right)
        })
      })
    (NegateBool(location=a0, value=a1), NegateBool(location=b0, value=b1)) =>
      a0 == b0 && equal_expr(runner, a1, b1)
    (NegateInt(location=a0, value=a1), NegateInt(location=b0, value=b1)) =>
      a0 == b0 && equal_expr(runner, a1, b1)
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for Expr with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_expr(runner, left, right))
}

///|
async fn[A] equal_call_arg(
  runner : @work.Runner,
  left : CallArg[A],
  right : CallArg[A],
  equal_value : async (A, A) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.label == right.label &&
  left.location == right.location &&
  equal_value(left.value, right.value) &&
  left.implicit == right.implicit
}

///|
async fn equal_clause(
  runner : @work.Runner,
  left : Clause,
  right : Clause,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_array_view(left.pattern, right.pattern, (left, right) => {
    equal_pattern(runner, left, right)
  }) &&
  equal_array_view(left.alternative_patterns, right.alternative_patterns, (
    left,
    right,
  ) => {
    equal_array_view(left, right, (left, right) => {
      equal_pattern(runner, left, right)
    })
  }) &&
  equal_option(left.guard_, right.guard_, (left, right) => {
    equal_clause_guard(runner, left, right)
  }) &&
  equal_expr(runner, left.then, right.then)
}

///|
async fn equal_clause_guard(
  runner : @work.Runner,
  left : ClauseGuard,
  right : ClauseGuard,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (Block(location=a0, value=a1), Block(location=b0, value=b1)) =>
      a0 == b0 && equal_clause_guard(runner, a1, b1)
    (
      BinaryOperator(
        location=a0,
        operator=a1,
        operator_start=a2,
        left=a3,
        right=a4
      ),
      BinaryOperator(
        location=b0,
        operator=b1,
        operator_start=b2,
        left=b3,
        right=b4
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      equal_clause_guard(runner, a3, b3) &&
      equal_clause_guard(runner, a4, b4)
    (Not(location=a0, expression=a1), Not(location=b0, expression=b1)) =>
      a0 == b0 && equal_clause_guard(runner, a1, b1)
    (Var(location=a0, name=a1), Var(location=b0, name=b1)) =>
      a0 == b0 && a1 == b1
    (
      TupleIndex(location=a0, index=a1, tuple=a2),
      TupleIndex(location=b0, index=b1, tuple=b2),
    ) => a0 == b0 && a1 == b1 && equal_clause_guard(runner, a2, b2)
    (
      FieldAccess(label_location=a0, index=a1, label=a2, container=a3),
      FieldAccess(label_location=b0, index=b1, label=b2, container=b3),
    ) => a0 == b0 && a1 == b1 && a2 == b2 && equal_clause_guard(runner, a3, b3)
    (Constant(a0), Constant(b0)) => equal_constant(runner, a0, b0)
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for ClauseGuard with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_clause_guard(runner, left, right))
}

///|
async fn equal_pattern(
  runner : @work.Runner,
  left : Pattern,
  right : Pattern,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (
      Int(location=a0, value=a1, int_value=a2),
      Int(location=b0, value=b1, int_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (
      Float(location=a0, value=a1, float_value=a2),
      Float(location=b0, value=b1, float_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (String(location=a0, value=a1), String(location=b0, value=b1)) =>
      a0 == b0 && a1 == b1
    (Variable(location=a0, name=a1), Variable(location=b0, name=b1)) =>
      a0 == b0 && a1 == b1
    (BitArraySize(a0), BitArraySize(b0)) => equal_bit_array_size(runner, a0, b0)
    (
      Assign(name=a0, location=a1, pattern=a2),
      Assign(name=b0, location=b1, pattern=b2),
    ) => a0 == b0 && a1 == b1 && equal_pattern(runner, a2, b2)
    (Discard(name=a0, location=a1), Discard(name=b0, location=b1)) =>
      a0 == b0 && a1 == b1
    (
      List(location=a0, elements=a1, tail=a2),
      List(location=b0, elements=b1, tail=b2),
    ) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_pattern(runner, left, right)
      }) &&
      equal_option(a2, b2, (left, right) => {
        equal_tail_pattern(runner, left, right)
      })
    (
      Constructor(
        location=a0,
        name_location=a1,
        name=a2,
        arguments=a3,
        module_=a4,
        spread=a5
      ),
      Constructor(
        location=b0,
        name_location=b1,
        name=b2,
        arguments=b3,
        module_=b4,
        spread=b5
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      equal_array_view(a3, b3, (left, right) => {
        equal_call_arg(runner, left, right, (left, right) => {
          equal_pattern(runner, left, right)
        })
      }) &&
      a4 == b4 &&
      a5 == b5
    (Tuple(location=a0, elements=a1), Tuple(location=b0, elements=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_pattern(runner, left, right)
      })
    (BitArray(location=a0, segments=a1), BitArray(location=b0, segments=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_bit_array_segment(runner, left, right, (left, right) => {
          equal_pattern(runner, left, right)
        })
      })
    (
      StringPrefix(
        location=a0,
        left_location=a1,
        left_side_assignment=a2,
        right_location=a3,
        left_side_string=a4,
        right_side_assignment=a5
      ),
      StringPrefix(
        location=b0,
        left_location=b1,
        left_side_assignment=b2,
        right_location=b3,
        left_side_string=b4,
        right_side_assignment=b5
      ),
    ) => a0 == b0 && a1 == b1 && a2 == b2 && a3 == b3 && a4 == b4 && a5 == b5
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for Pattern with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_pattern(runner, left, right))
}

///|
async fn equal_tail_pattern(
  runner : @work.Runner,
  left : TailPattern,
  right : TailPattern,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_pattern(runner, left.pattern, right.pattern)
}

///|
async fn equal_bit_array_size(
  runner : @work.Runner,
  left : BitArraySize,
  right : BitArraySize,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (
      Int(location=a0, value=a1, int_value=a2),
      Int(location=b0, value=b1, int_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (Variable(location=a0, name=a1), Variable(location=b0, name=b1)) =>
      a0 == b0 && a1 == b1
    (
      BinaryOperator(location=a0, operator=a1, left=a2, right=a3),
      BinaryOperator(location=b0, operator=b1, left=b2, right=b3),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      equal_bit_array_size(runner, a2, b2) &&
      equal_bit_array_size(runner, a3, b3)
    (Block(location=a0, inner=a1), Block(location=b0, inner=b1)) =>
      a0 == b0 && equal_bit_array_size(runner, a1, b1)
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for BitArraySize with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_bit_array_size(runner, left, right))
}

///|
async fn[V] equal_bit_array_segment(
  runner : @work.Runner,
  left : BitArraySegment[V],
  right : BitArraySegment[V],
  equal_value : async (V, V) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.location == right.location &&
  equal_value(left.value, right.value) &&
  equal_array_view(left.options, right.options, (left, right) => {
    equal_bit_array_option(runner, left, right, (left, right) => {
      equal_value(left, right)
    })
  })
}

///|
async fn[V] equal_bit_array_option(
  runner : @work.Runner,
  left : BitArrayOption[V],
  right : BitArrayOption[V],
  equal_value : async (V, V) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (Bytes(location=a0), Bytes(location=b0)) => a0 == b0
    (Int(location=a0), Int(location=b0)) => a0 == b0
    (Float(location=a0), Float(location=b0)) => a0 == b0
    (Bits(location=a0), Bits(location=b0)) => a0 == b0
    (Utf8(location=a0), Utf8(location=b0)) => a0 == b0
    (Utf16(location=a0), Utf16(location=b0)) => a0 == b0
    (Utf32(location=a0), Utf32(location=b0)) => a0 == b0
    (Utf8Codepoint(location=a0), Utf8Codepoint(location=b0)) => a0 == b0
    (Utf16Codepoint(location=a0), Utf16Codepoint(location=b0)) => a0 == b0
    (Utf32Codepoint(location=a0), Utf32Codepoint(location=b0)) => a0 == b0
    (Signed(location=a0), Signed(location=b0)) => a0 == b0
    (Unsigned(location=a0), Unsigned(location=b0)) => a0 == b0
    (Big(location=a0), Big(location=b0)) => a0 == b0
    (Little(location=a0), Little(location=b0)) => a0 == b0
    (Native(location=a0), Native(location=b0)) => a0 == b0
    (
      Size(location=a0, value=a1, short_form=a2),
      Size(location=b0, value=b1, short_form=b2),
    ) => a0 == b0 && equal_value(a1, b1) && a2 == b2
    (Unit(location=a0, value=a1), Unit(location=b0, value=b1)) =>
      a0 == b0 && a1 == b1
    _ => false
  }
}

///|
async fn[A] equal_record_being_updated(
  runner : @work.Runner,
  left : RecordBeingUpdated[A],
  right : RecordBeingUpdated[A],
  equal_value : async (A, A) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  equal_value(left.base, right.base) && left.location == right.location
}

///|
async fn[A] equal_record_update_arg(
  runner : @work.Runner,
  left : RecordUpdateArg[A],
  right : RecordUpdateArg[A],
  equal_value : async (A, A) -> Bool noraise + nocancel,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  left.label == right.label &&
  left.location == right.location &&
  equal_value(left.value, right.value)
}

///|
async fn equal_constant(
  runner : @work.Runner,
  left : Constant,
  right : Constant,
) -> Bool noraise + nocancel {
  runner.pause()
  defer runner.pause()
  match (left, right) {
    (
      Int(location=a0, value=a1, int_value=a2),
      Int(location=b0, value=b1, int_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (
      Float(location=a0, value=a1, float_value=a2),
      Float(location=b0, value=b1, float_value=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (String(location=a0, value=a1), String(location=b0, value=b1)) =>
      a0 == b0 && a1 == b1
    (Tuple(location=a0, elements=a1), Tuple(location=b0, elements=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_constant(runner, left, right)
      })
    (
      List(location=a0, elements=a1, tail=a2),
      List(location=b0, elements=b1, tail=b2),
    ) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_constant(runner, left, right)
      }) &&
      equal_option(a2, b2, (left, right) => equal_constant(runner, left, right))
    (
      Record(
        location=a0,
        arguments_start_position=a1,
        module_=a2,
        name=a3,
        arguments=a4
      ),
      Record(
        location=b0,
        arguments_start_position=b1,
        module_=b2,
        name=b3,
        arguments=b4
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      a3 == b3 &&
      equal_option(a4, b4, (left, right) => {
        equal_array_view(left, right, (left, right) => {
          equal_call_arg(runner, left, right, (left, right) => {
            equal_constant(runner, left, right)
          })
        })
      })
    (
      RecordUpdate(
        location=a0,
        constructor_location=a1,
        module_=a2,
        name=a3,
        record=a4,
        arguments=a5
      ),
      RecordUpdate(
        location=b0,
        constructor_location=b1,
        module_=b2,
        name=b3,
        record=b4,
        arguments=b5
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      a3 == b3 &&
      equal_record_being_updated(runner, a4, b4, (left, right) => {
        equal_constant(runner, left, right)
      }) &&
      equal_array_view(a5, b5, (left, right) => {
        equal_record_update_arg(runner, left, right, (left, right) => {
          equal_constant(runner, left, right)
        })
      })
    (BitArray(location=a0, segments=a1), BitArray(location=b0, segments=b1)) =>
      a0 == b0 &&
      equal_array_view(a1, b1, (left, right) => {
        equal_bit_array_segment(runner, left, right, (left, right) => {
          equal_constant(runner, left, right)
        })
      })
    (
      Var(location=a0, module_=a1, name=a2),
      Var(location=b0, module_=b1, name=b2),
    ) => a0 == b0 && a1 == b1 && a2 == b2
    (
      BinaryOperator(
        location=a0,
        operator_start=a1,
        operator=a2,
        left=a3,
        right=a4
      ),
      BinaryOperator(
        location=b0,
        operator_start=b1,
        operator=b2,
        left=b3,
        right=b4
      ),
    ) =>
      a0 == b0 &&
      a1 == b1 &&
      a2 == b2 &&
      equal_constant(runner, a3, b3) &&
      equal_constant(runner, a4, b4)
    (Todo(location=a0, message=a1), Todo(location=b0, message=b1)) =>
      a0 == b0 &&
      equal_option(a1, b1, (left, right) => equal_constant(runner, left, right))
    _ => false
  }
}

///|
/// Compare every AST field without recursive machine-stack growth.
pub impl Eq for Constant with fn equal(left, right) {
  let runner = @work.Runner()
  runner.run_total(() => equal_constant(runner, left, right))
}