// Turning Wax's named types into the store's numbered ones.
//
// Ported from the type-resolution section of wax/src/lib-wax/typing.ml.
//
// THIS IS THE SEAM AGAIN, from the checker's side. A Wax type names what it
// refers to (`&$node`); the store numbers it. Both are instances of the one
// `wasm_types` family, so resolution is a fold that replaces the index type and
// changes nothing else -- which is why each of these functions is a
// constructor-for-constructor walk with exactly two interesting arms.
//
// Everything returns an option rather than raising. A type that fails to
// resolve has already been reported, and returning `None` lets the caller drop
// that one declaration and keep checking the rest, instead of the first bad
// type ending the run.
///|
/// Look a name up in a table, REPORTING it if nothing binds it.
///
/// The reporting is the point. Resolving silently let an unbound type name
/// through, so a construction literal naming a type that does not exist was
/// accepted here and only noticed by the lowering -- which is the thing
/// checking exists to happen before.
///
/// Suggestions come from every name the table knows, not only those visible
/// under the current assumption: a name declared in another `#[if]` branch is
/// still a plausible thing the author meant.
fn[A] find(
tbl : @typing_env.Tbl[A],
diagnostics : @diagnostic.Context,
name : @ast.Ident,
) -> A? {
match tbl.resolve(name.name, name.loc) {
Some(v) => Some(v)
None => {
unbound_name(
diagnostics,
name.loc,
tbl.kind,
name.name,
suggestions=@spell.suggest(tbl.names().iter(), name.name),
)
None
}
}
}
///|
/// The definition a name refers to, if it resolves.
///
/// One of the two things the code generator needs from the checker; the other
/// is the annotation itself.
pub fn get_type_definition(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
) -> @ast.SubType? {
find(ctx.types, diagnostics, name).map(r => r.1)
}
///|
/// How a source reference appears inside a rec group being registered: a `Def`
/// for a type already in the store, a `Rec` for one this very group defines.
fn resolve_type_ref(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
) -> @type_store.RefIndex? {
find(ctx.types, diagnostics, name).map(r => r.0)
}
///|
/// The canonical index of an already-defined referenced type.
///
/// A `Rec` here would mean a group still under construction, which the callers
/// of this -- as opposed to `resolve_type_ref` -- never look up.
pub fn resolve_type_name(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
) -> @type_store.Id? {
match resolve_type_ref(ctx, diagnostics, name) {
Some(Def(id)) => Some(id)
_ => None
}
}
///|
/// Note that a proposal is used, and complain if it is not enabled.
///
/// Checking continues either way, so one disabled feature does not swallow
/// every error after it.
fn require_feature(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
location : @basic.Location,
feature : @feature.Feature,
) -> Unit {
ctx.features.mark_used(feature)
if !ctx.features.is_enabled(feature) {
feature_disabled(diagnostics, location, feature)
}
}
// ============================================================
// Into the resolved form, whose references are canonical indices
// ============================================================
///|
pub fn heaptype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
h : @wasm_types.HeapType[@ast.Ident],
) -> @wasm_types.HeapType[@type_store.Id]? {
match h {
Func => Some(Func)
NoFunc => Some(NoFunc)
Exn => Some(Exn)
NoExn => Some(NoExn)
Cont => Some(Cont)
NoCont => Some(NoCont)
Extern => Some(Extern)
NoExtern => Some(NoExtern)
Any => Some(Any)
Eq => Some(Eq)
I31 => Some(I31)
Struct => Some(Struct)
Array => Some(Array)
None_ => Some(None_)
Type(idx) =>
resolve_type_name(ctx, diagnostics, idx).map(t => {
@wasm_types.HeapType::Type(t)
})
Exact(idx) => {
// `exact` is the custom-descriptors proposal, and is gated even when the
// name resolves.
require_feature(ctx, diagnostics, idx.loc, CustomDescriptors)
resolve_type_name(ctx, diagnostics, idx).map(t => {
@wasm_types.HeapType::Exact(t)
})
}
}
}
///|
fn reftype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
r : @wasm_types.RefType[@ast.Ident],
) -> @wasm_types.RefType[@type_store.Id]? {
heaptype(ctx, diagnostics, r.typ).map(typ => {
({ nullable: r.nullable, typ } : @wasm_types.RefType[@type_store.Id])
})
}
///|
pub fn valtype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ty : @wasm_types.ValType[@ast.Ident],
) -> @wasm_types.ValType[@type_store.Id]? {
match ty {
I32 => Some(I32)
I64 => Some(I64)
F32 => Some(F32)
F64 => Some(F64)
V128 => Some(V128)
Ref(r) => reftype(ctx, diagnostics, r).map(t => @wasm_types.ValType::Ref(t))
}
}
// ============================================================
// Into the normalized form, whose references may name the group being built
// ============================================================
///|
/// As `heaptype`, but a reference may also be to a member of the rec group
/// currently being registered -- which is what lets a group of mutually
/// recursive types be built at all, and what lets it dedup wherever it lands.
pub fn n_heaptype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
h : @wasm_types.HeapType[@ast.Ident],
) -> @wasm_types.HeapType[@type_store.RefIndex]? {
match h {
Func => Some(Func)
NoFunc => Some(NoFunc)
Exn => Some(Exn)
NoExn => Some(NoExn)
Cont => Some(Cont)
NoCont => Some(NoCont)
Extern => Some(Extern)
NoExtern => Some(NoExtern)
Any => Some(Any)
Eq => Some(Eq)
I31 => Some(I31)
Struct => Some(Struct)
Array => Some(Array)
None_ => Some(None_)
Type(idx) =>
resolve_type_ref(ctx, diagnostics, idx).map(r => {
@wasm_types.HeapType::Type(r)
})
Exact(idx) => {
require_feature(ctx, diagnostics, idx.loc, CustomDescriptors)
resolve_type_ref(ctx, diagnostics, idx).map(r => {
@wasm_types.HeapType::Exact(r)
})
}
}
}
///|
fn n_reftype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
r : @wasm_types.RefType[@ast.Ident],
) -> @wasm_types.RefType[@type_store.RefIndex]? {
n_heaptype(ctx, diagnostics, r.typ).map(typ => {
({ nullable: r.nullable, typ } : @wasm_types.RefType[@type_store.RefIndex])
})
}
///|
fn n_valtype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ty : @wasm_types.ValType[@ast.Ident],
) -> @wasm_types.ValType[@type_store.RefIndex]? {
match ty {
I32 => Some(I32)
I64 => Some(I64)
F32 => Some(F32)
F64 => Some(F64)
V128 => Some(V128)
Ref(r) =>
n_reftype(ctx, diagnostics, r).map(t => @wasm_types.ValType::Ref(t))
}
}
///|
fn n_storagetype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ty : @wasm_types.StorageType[@ast.Ident],
) -> @wasm_types.StorageType[@type_store.RefIndex]? {
match ty {
Value(v) =>
n_valtype(ctx, diagnostics, v).map(t => @wasm_types.StorageType::Value(t))
Packed(p) => Some(Packed(p))
}
}
///|
fn n_fieldtype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ty : @wasm_types.FieldType[@ast.Ident],
) -> @wasm_types.FieldType[@type_store.RefIndex]? {
n_storagetype(ctx, diagnostics, ty.typ).map(typ => {
({ mut_: ty.mut_, typ } : @wasm_types.FieldType[@type_store.RefIndex])
})
}
///|
/// Map every element, giving up as soon as one fails.
///
/// A single unresolvable component makes the whole type unresolvable, and the
/// component has already reported why.
fn[A, B] map_all(xs : Array[A], f : (A) -> B?) -> Array[B]? {
let out : Array[B] = []
for x in xs {
match f(x) {
None => return None
Some(y) => out.push(y)
}
}
Some(out)
}
///|
/// A function type, with the duplicate-parameter check.
///
/// The check runs even when a parameter's type fails to resolve, because a
/// duplicate name is worth reporting whether or not the types are sound.
pub fn n_functype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ft : @ast.FuncType,
) -> @type_store.FuncType[@type_store.RefIndex]? {
check_unique_param_names(diagnostics, ft.params)
guard map_all(ft.params, p => n_valtype(ctx, diagnostics, p.desc.1))
is Some(params) else {
return None
}
guard map_all(ft.results, r => n_valtype(ctx, diagnostics, r))
is Some(results) else {
return None
}
Some({ params, results })
}
///|
/// Report any parameter name used twice, at its second occurrence.
fn check_unique_param_names(
diagnostics : @diagnostic.Context,
params : Array[
@basic.Annotated[
(@ast.Ident?, @wasm_types.ValType[@ast.Ident]),
@basic.Location,
],
],
) -> Unit {
let seen : Map[String, @basic.Location] = Map([])
for p in params {
if p.desc.0 is Some(name) {
match seen.get(name.name) {
Some(prev) =>
duplicated_parameter(diagnostics, name.loc, prev, name.name)
None => seen[name.name] = name.loc
}
}
}
}
///|
/// What a defined type defines.
pub fn comptype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
ty : @ast.CompType,
) -> @type_store.CompType[@type_store.RefIndex]? {
match ty {
Func(ft) =>
n_functype(ctx, diagnostics, ft).map(f => @type_store.CompType::Func(f))
Struct(fields) => {
check_unique_field_names(diagnostics, fields)
map_all(fields, f => n_fieldtype(ctx, diagnostics, f.desc.1)).map(fs => {
@type_store.CompType::Struct(fs)
})
}
Array(field) =>
n_fieldtype(ctx, diagnostics, field).map(f => {
@type_store.CompType::Array(f)
})
// A continuation is a continuation OF a function type -- `cont ct` where
// `ct` is itself a continuation names nothing to run -- but that is not
// asked here: within a rec group the wrapped type may be declared after
// this one, so the answer only settles once the group is interned.
// `check_type_definitions` asks it, at this same span.
Cont(idx) =>
resolve_type_ref(ctx, diagnostics, idx).map(r => {
@type_store.CompType::Cont(r)
})
}
}
///|
/// Report any field name used twice, at its second occurrence.
fn check_unique_field_names(
diagnostics : @diagnostic.Context,
fields : Array[
@basic.Annotated[
(@ast.Ident, @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]]),
@basic.Location,
],
],
) -> Unit {
let seen : Map[String, @basic.Location] = Map([])
for f in fields {
let name = f.desc.0
match seen.get(name.name) {
Some(prev) => duplicated_field(diagnostics, name.loc, prev, name.name)
None => seen[name.name] = name.loc
}
}
}
///|
/// A source value type together with its resolved form.
///
/// The pair is what the checker works in: `typ` keeps the name, so a diagnostic
/// can print what the author wrote, and `internal` carries the store index, so
/// subtyping can be decided. Resolution can fail, and then there is no pair.
pub fn internalize_valtype(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
typ : @wasm_types.ValType[@ast.Ident],
) -> @infer.InferredValType? {
valtype(ctx, diagnostics, typ).map(internal => {
({ typ, internal, anon_comptype: None } : @infer.InferredValType)
})
}
///|
/// As `internalize_valtype`, but as a cell ready to go on the stack.
///
/// `inline` carries the composite type of a synthesized reference -- a string's
/// byte array, an inline function type -- so a diagnostic renders the structure
/// rather than a generated name that means nothing to the reader.
pub fn internalize(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
typ : @wasm_types.ValType[@ast.Ident],
inline? : @ast.CompType? = None,
) -> @infer.Cell[@infer.InferredType]? {
valtype(ctx, diagnostics, typ).map(internal => {
@infer.valtype_cell({ typ, internal, anon_comptype: inline })
})
}