// Looking up what a struct or array access operates on.
//
// Ported from wax/src/lib-wax/typing.ml.
//
// A type NAME resolves to a definition of some kind, and an aggregate operation
// needs one of a particular kind. Asking for the wrong kind is a mistake worth
// naming precisely -- "expected struct type" rather than a mismatch several
// steps later -- which is why each kind gets its own lookup rather than one
// that returns the definition and leaves the caller to match on it.
//
// An unbound name is reported by the lookup itself. Returning `None` silently
// would let a literal naming a type that does not exist be accepted here and
// lowered to `unreachable`, which is exactly what checking exists to pre-empt.
///|
/// The function type a name refers to.
pub fn lookup_func_type(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
location? : @basic.Location? = None,
) -> @ast.FuncType? {
guard find(ctx.types, diagnostics, name) is Some((_, def)) else {
return None
}
match def.typ {
Func(ft) => Some(ft)
_ => {
expected_func_type(diagnostics, location.unwrap_or(name.loc))
None
}
}
}
///|
/// The fields a struct type declares.
pub fn lookup_struct_type(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
location? : @basic.Location? = None,
) -> Array[
@basic.Annotated[
(@ast.Ident, @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]]),
@basic.Location,
],
]? {
guard find(ctx.types, diagnostics, name) is Some((_, def)) else {
return None
}
match def.typ {
Struct(fields) => Some(fields)
_ => {
expected_struct_type(diagnostics, location.unwrap_or(name.loc))
None
}
}
}
///|
/// The element type an array type declares.
pub fn lookup_array_type(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
name : @ast.Ident,
location? : @basic.Location? = None,
) -> @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]]? {
guard find(ctx.types, diagnostics, name) is Some((_, def)) else {
return None
}
match def.typ {
Array(field) => Some(field)
_ => {
expected_array_type(diagnostics, location.unwrap_or(name.loc))
None
}
}
}
///|
/// The unique struct type with exactly these field names, if there is one.
///
/// This is what lets `{ x: 1, y: 2 }` be written without naming its type. The
/// index is built once, after every type is registered, so the answer is
/// complete; `None` means either no struct has that field set or several do,
/// and in both cases the literal has to name what it means.
pub fn infer_struct_by_fields(
ctx : @typing_env.ModuleContext,
fields : Array[@ast.Ident],
) -> @ast.Ident? {
let key = field_set_key(fields.map(f => f.name))
match ctx.structs_by_fields.get(key) {
Some(Some(name)) => Some(name)
_ => None
}
}
///|
/// The type a field READ produces.
///
/// A packed field is not read at its stored width: `i8` and `i16` come off as
/// i32, and which sign extension applies is the reader's choice. `Int8` and
/// `Int16` carry that -- they are i32 values that remember how narrow they
/// were, so the lowering can pick `struct.get_s` against `struct.get_u`.
pub fn field_read_type(
ctx : @typing_env.TypeContext,
diagnostics : @diagnostic.Context,
f : @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]],
) -> @infer.Cell[@infer.InferredType]? {
match f.typ {
Value(typ) => internalize(ctx, diagnostics, typ)
Packed(I8) => Some(@infer.Cell::make(Int8))
Packed(I16) => Some(@infer.Cell::make(Int16))
}
}
///|
/// The value type a field is WRITTEN at.
///
/// The other half of the packed story: a write takes a plain i32 and the
/// narrowing is implicit, so unlike a read there is nothing to remember.
pub fn unpack_type(
f : @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]],
) -> @wasm_types.ValType[@ast.Ident] {
match f.typ {
Value(v) => v
Packed(_) => I32
}
}
///|
/// Whether a field has a default value, so a `..default` construction can leave
/// it out.
///
/// Everything numeric does -- zero. A reference does only if it is nullable:
/// there is no null to default a non-null reference to, so it must be given.
pub fn field_has_default(
ty : @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]],
) -> Bool {
match ty.typ {
Packed(_) => true
Value(I32 | I64 | F32 | F64 | V128) => true
Value(Ref(r)) => r.nullable
}
}
///|
/// Find a field by name in a struct's declared fields, with its index.
///
/// The index is what the lowering emits; the name is what the reader wrote.
/// Returning both is why this exists rather than a plain search.
fn find_field(
fields : Array[
@basic.Annotated[
(@ast.Ident, @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]]),
@basic.Location,
],
],
name : String,
) -> (Int, @wasm_types.MutType[@wasm_types.StorageType[@ast.Ident]])? {
for i, f in fields {
if f.desc.0.name == name {
return Some((i, f.desc.1))
}
}
None
}
///|
/// The type a concrete allocator produces: a non-null reference to what it
/// allocated.
///
/// EXACT only when custom-descriptors is enabled. At the wasm level
/// `struct.new` really does yield an exact reference -- it allocates that type
/// and no subtype -- but exact reference types are part of that proposal, so
/// without it the plain inexact reference is what can be written down.
fn construction_result(
ctx : @typing_env.ModuleContext,
name : @ast.Ident,
) -> @infer.Cell[@infer.InferredType]? {
let exact = ctx.type_context.features.is_enabled(CustomDescriptors)
internalize(
ctx.type_context,
ctx.diagnostics,
Ref({ nullable: false, typ: if exact { Exact(name) } else { Type(name) } }),
inline=inline_comptype(ctx, name),
)
}
///|
/// Whether a byte string decodes as UTF-8.
///
/// Needed because an `i16` array holds code UNITS rather than bytes, so what
/// goes into it has to be text and not an arbitrary blob.
fn is_valid_utf8(b : Bytes) -> Bool {
let n = b.length()
let mut i = 0
while i < n {
let c = b[i].to_int()
let extra = if c < 0x80 {
0
} else if c >= 0xC2 && c <= 0xDF {
1
} else if c >= 0xE0 && c <= 0xEF {
2
} else if c >= 0xF0 && c <= 0xF4 {
3
} else {
return false
}
if i + extra >= n {
return false
}
for k in 1..<=extra {
let cc = b[i + k].to_int()
if cc < 0x80 || cc > 0xBF {
return false
}
}
// The ranges above admit a few sequences the standard excludes -- an
// overlong three-byte form, a surrogate, a code point past U+10FFFF -- so
// the first continuation byte is narrowed per lead byte.
if extra == 2 {
let cc = b[i + 1].to_int()
if c == 0xE0 && cc < 0xA0 {
return false
}
if c == 0xED && cc > 0x9F {
return false
}
}
if extra == 3 {
let cc = b[i + 1].to_int()
if c == 0xF0 && cc < 0x90 {
return false
}
if c == 0xF4 && cc > 0x8F {
return false
}
}
i = i + extra + 1
}
true
}
///|
/// Whether an element segment's type fits the array being built from it.
///
/// The segment supplies the values, so its element type must be a SUBTYPE of
/// what the array holds -- the usual direction for anything being stored.
fn check_elem_subtype(
ctx : @typing_env.ModuleContext,
location : @basic.Location,
src : @wasm_types.RefType[@ast.Ident],
dst : @wasm_types.RefType[@ast.Ident],
) -> Unit {
let s = internalize_valtype(ctx.type_context, ctx.diagnostics, Ref(src))
let d = internalize_valtype(ctx.type_context, ctx.diagnostics, Ref(dst))
if s is Some(s) && d is Some(d) {
if !@type_store.val_subtype(
ctx.type_context.subtyping_info(),
s.internal,
d.internal,
) {
incompatible_element_type(
ctx.diagnostics,
location,
@infer.valtype_cell(s),
@infer.valtype_cell(d),
)
}
}
}
///|
/// Recover the target reference type of a descriptor cast, branch or
/// allocation from its DESCRIPTOR operand.
///
/// These instructions write only the descriptor -- `x as desc d`, not
/// `x as &$X desc d` -- so the type being reached for is not written down. It
/// is recovered here: the operand has type `&$Y`, `$Y` describes `$X`, and `$X`
/// is what was meant.
///
/// Exactness carries through. An exact descriptor identifies exactly one
/// described type, so the result is exact too.
fn descriptor_reftype(
ctx : @typing_env.ModuleContext,
location : @basic.Location,
nullable~ : Bool,
descriptor : @infer.Cell[@infer.InferredType],
) -> @wasm_types.RefType[@ast.Ident]? {
let target = match descriptor.get() {
Valtype({ typ: Ref({ typ: Type(y) | Exact(y) as yt, .. }), .. }) =>
match ctx.types.find_no_mark(y.name) {
Some((_, { describes: Some(x), .. })) => {
let exact = yt is Exact(_)
Some(
(
{ nullable, typ: if exact { Exact(x) } else { Type(x) } } :
@wasm_types.RefType[@ast.Ident]),
)
}
_ => None
}
_ => None
}
if target is None {
type_without_descriptor(ctx.diagnostics, location)
}
target
}