// Resolving a branch to the control frame it targets.
//
// Ported from wax/src/lib-wax/typing.ml.
///|
/// The types a branch to this label delivers.
///
/// Searches the enclosing frames innermost first, so a shadowing inner label of
/// the same name wins -- which is what makes a label a lexical binding rather
/// than a module-wide name.
///
/// An unresolved label reports and yields no types. It also SETS a flag on the
/// context, and that flag is what stops the failure becoming two: a block whose
/// only value delivery was the unresolved branch legitimately computes no
/// value, and complaining about that as well would anchor a derived error away
/// from the unbound label the reader actually has to fix.
pub fn branch_target(
ctx : @typing_env.ModuleContext,
label : @ast.Ident,
) -> Array[@infer.Cell[@infer.InferredType]] {
for frame in ctx.control_types {
if frame.label is Some(l) && l.name == label.name {
// Keyed by the LABEL DECLARATION's offset rather than its name, so a
// shadowing inner label does not mask an unused outer one. The wasm
// validator likewise tracks usage per control frame.
ctx.used_labels.push(l.loc.start.cnum)
@typing_env.record_reference(ctx.resolve_links, label.loc, [l.loc])
return frame.results
}
}
unbound_name(
ctx.diagnostics,
label.loc,
"label",
label.name,
suggestions=label_suggestions(ctx, label.name),
)
ctx.unresolved_label.val = true
[]
}
///|
/// Does this label resolve to a frame in scope?
///
/// Reports nothing and records no use. It exists to tell an UNBOUND label --
/// already diagnosed -- from a legitimately void target, which otherwise look
/// the same, both delivering no types.
pub fn label_in_scope(
ctx : @typing_env.ModuleContext,
label : @ast.Ident,
) -> Bool {
for frame in ctx.control_types {
if frame.label is Some(l) && l.name == label.name {
return true
}
}
false
}
///|
/// Labels close enough to be worth suggesting.
///
/// Drawn from the frames in scope, because a label is lexical: one from a block
/// that has already closed is not a thing the author could have meant here.
fn label_suggestions(
ctx : @typing_env.ModuleContext,
name : String,
) -> Array[String] {
let candidates : Array[String] = []
for frame in ctx.control_types {
if frame.label is Some(l) {
candidates.push(l.name)
}
}
@spell.suggest(candidates.iter(), name)
}
///|
/// Deliver the values below a `br_if` / `br_on_null` operand to the branch
/// target, and answer what they are typed as on the fall-through.
///
/// A pass-through branch is the odd one out among branches: the value is
/// delivered to the target when the branch is taken AND stays on the stack when
/// it is not. So it is typed as the target's result either way -- which makes
/// the requirement stricter than for an ordinary `br`. An ordinary delivery
/// need only be a SUBTYPE of the result. A pass-through value has to be
/// EXACTLY it, because the fall-through path will go on using it at that type.
///
/// That is what `exacts` records, and the snapshot has to be taken HERE, before
/// the delivery below pins the value: after pinning, its natural type is gone
/// and every value would look like it matched.
pub fn deliver_to_branch_target(
info : @type_store.SubtypingInfo,
diagnostics : @diagnostic.Context,
location : @basic.Location,
types : Array[@infer.Cell[@infer.InferredType]],
params : Array[@infer.Cell[@infer.InferredType]],
) -> Array[@infer.Cell[@infer.InferredType]] {
let same_arity = types.length() == params.length()
if same_arity {
for i in 0.. is_inferring(p))
if any_inferring && same_arity {
let out : Array[@infer.Cell[@infer.InferredType]] = []
for i in 0.. Unit {
guard @typing_env.standalone_valtype(result) is Some(t) else { return }
for e in exacts {
let (loc, ty) = e
if ty.get() is Valtype(v) && v.internal != t.internal {
br_if_result_mismatch(
diagnostics,
location,
loc.unwrap_or(location),
result,
ty,
)
}
}
}