// What a checked module can be turned into.

///|
/// What can stop a checked module from becoming output.
///
/// Three packages sit on this path and each raises its own error: the lowering
/// to the wasm module model, the binary encoder, and the text printer. A caller
/// handed the open `Error` type can only print what it catches; naming the
/// three lets it match on them, and says up front that there is no fourth.
///
/// None of these are a rejected module. A module the checker rejected is
/// reported through `Session::reports` and `rejected` before anything is
/// lowered -- these are the lowering's own gaps, and reaching one on a module
/// that checked clean is a bug in this port rather than in the input. The one
/// exception is `LowerError::AmbiguousBinding`, which only a generated tree can
/// reach and which names what the generator has to fix.
pub suberror CompileError {
  /// The Wax module would not lower: a construct not handled yet, a name the
  /// checker accepted that did not survive, or one the binary format cannot
  /// hold at all.
  Lower(@to_wasm.LowerError)
  /// The lowered module would not encode.
  Encode(@wasm_bin.EncodeError)
  /// The lowered module would not print as WebAssembly text.
  Wat(@to_wat.WatError)
}

///|
/// Delegated to the wrapped error, which already renders with its own span:
/// the wrapper says which stage, and the stage's own message is the answer.
pub impl Show for CompileError with fn output(self, logger) {
  match self {
    Lower(e) => Show::output(e, logger)
    Encode(e) => Show::output(e, logger)
    Wat(e) => Show::output(e, logger)
  }
}

///|
/// The error as a located diagnostic, when it is the module's rather than this
/// port's: a construct the binary format has no way to hold, which the
/// reference refuses with the same diagnostic. `None` for a gap in the port,
/// which is reported as one instead.
pub fn CompileError::diagnostic(self : CompileError) -> @diagnostic.Diagnostic? {
  match self {
    Lower(Unemittable("conditional annotations", loc)) =>
      Some({
        loc,
        severity: Error,
        warning: None,
        message: @message.text(
          "Conditional annotations cannot be emitted to the WebAssembly binary format.",
        ),
        hint: Some(
          @message.text(
            "Resolve the conditionals with -D/--define, or convert to a text format (wat or wax).",
          ),
        ),
        edit: None,
        related: [],
        universal: false,
      })
    _ => None
  }
}

///|
/// A module that has been through the type checker.
///
/// It holds the four things the lowering needs and that a caller would
/// otherwise have to keep together by hand: the session (for its type store),
/// the module context the checker built, the source AST, and the per-node
/// inferred types the checker left alongside it. The lowering reads the types
/// off `typed`, which is the entire interface between the two stages.
///
/// Holding a `Checked` is NOT a claim that the module is valid -- see
/// `Session::check`. Lowering one the checker rejected raises, or produces a
/// module derived from something known to be wrong.
pub struct Checked {
  /// The session it was checked in; its `store` is what numbers the types.
  session : Session
  /// Names and signatures, as the checker resolved them.
  context : @typing_env.ModuleContext
  /// The module as written.
  source_module : @ast.LocModule
  /// The same module with an inferred type on every node.
  typed : Array[
    @basic.Annotated[
      @ast.ModuleField[
        (Array[@infer.Cell[@infer.InferredType]], @basic.Location),
      ],
      @basic.Location,
    ],
  ]
}

///|
/// Lower to the wasm module model, shared by both output forms.
///
/// `keep_conditionals` retains the conditional sections rather than resolving
/// them, which only the text form wants: a module with an unresolved
/// conditional has no single binary form, and that is what a conditional is
/// for.
///
/// `try_table` rewrites the legacy `try`/`catch` into the standard
/// `try_table`, for engines and tools that only accept the current
/// exception-handling proposal.
pub fn Checked::to_module(
  self : Checked,
  keep_conditionals? : Bool,
  try_table? : Bool,
) -> @wasm_bin.Module raise @to_wasm.LowerError {
  @to_wasm.lower_module(
    self.context,
    self.session.store,
    self.source_module,
    self.typed,
    keep_conditionals?,
    try_table?,
  )
}

///|
/// The module with each legacy `try_legacy` rewritten into the structured
/// `try`, which lowers to the standard `try_table` (`--try-table` on a
/// Wax-to-Wax conversion).
///
/// The rewrite needs the types the checker inferred -- an unannotated try
/// takes its result type from its context -- which is why it is a step of a
/// CHECKED module. A legacy try that takes parameters has no structured
/// equivalent and is reported to the session.
pub fn Checked::rewrite_legacy_try(self : Checked) -> @ast.LocModule {
  @typing.rewrite_legacy_try(
    self.session.diagnostics,
    self.typed,
    self.source_module,
  )
}

///|
/// Encode to a `.wasm` binary.
///
/// `label_names` false leaves the label names out of the `name` section:
/// Binaryen's `wasm-opt` does not know that subsection and warns about it.
pub fn Checked::to_bytes(
  self : Checked,
  label_names~ : Bool = true,
  try_table? : Bool,
) -> Bytes raise CompileError {
  let m = self.to_module(try_table?) catch { e => raise Lower(e) }
  @wasm_bin.encode(m, label_names~) catch {
    e => raise Encode(e)
  }
}

///|
/// Print in the WebAssembly text format.
///
/// The same lowered module the binary form comes from, printed rather than
/// encoded, so the two cannot drift. `trivia` carries the source comments; they
/// were written against Wax source and come out as WAT comments, with the same
/// text and different delimiters.
pub fn Checked::to_wat(
  self : Checked,
  trivia? : @trivia.Context,
  try_table? : Bool,
) -> String raise CompileError {
  let m = self.to_module(keep_conditionals=true, try_table?) catch {
    e => raise Lower(e)
  }
  @to_wat.print_module(m, trivia~) catch {
    e => raise Wat(e)
  }
}