use super::{CodegenCtx, function_emitter::FunctionEmitter};
use crate::compiler_error::CompilerFailure;
use wasm_encoder::{BlockType, HeapType, Instruction, RefType, ValType};
pub(super) const PATH_SLOT: i32 = super::cast_check::RECURSIVE_VALIDATOR_CAPACITY * 2;
pub(super) const FAILURE_SLOT: i32 = PATH_SLOT + 1;
const VALUE_SLOT: i32 = PATH_SLOT + 2;
const EXPECTED_SLOT: i32 = PATH_SLOT + 3;
const TAG_MISS_SLOT: i32 = PATH_SLOT + 4;
const IN_MEMBER_SLOT: i32 = PATH_SLOT + 5;
pub(super) const SLOTS: i32 = 6;
const AT_UNION: i32 = 0;
const TAG_MISS: i32 = 1;
const IN_MEMBER: i32 = 2;
const TIER_SHIFT: i32 = 29;
const MAX_EXPECTED_LEN: usize = 48;
const PATH_UNITS_PER_STEP: usize = 64;
#[derive(Clone)]
pub(super) struct Locals {
current: u32,
failure: Failure,
key: Option<u32>,
path: Vec<Segment>,
unions: Vec<UnionLocals>,
open_unions: Vec<usize>,
}
#[derive(Clone, Copy)]
pub(super) enum Tested<'a> {
Value { ty: &'a crate::Type, local: u32 },
Absent { ty: &'a crate::Type },
}
#[derive(Clone, Copy)]
struct Failure {
path: u32,
value: u32,
expected: u32,
tier: u32,
}
#[derive(Clone, Copy)]
pub(super) struct UnionLocals {
result: u32,
best: Failure,
outer: Failure,
}
#[derive(Clone)]
enum Segment {
Field(String),
Index(u32),
}
pub(super) fn has_nested_paths(ty: &crate::Type) -> bool {
use crate::Type;
match ty.peel() {
Type::Union(members) => members.iter().any(has_nested_paths),
Type::Number
| Type::NumberLiteral(_)
| Type::NumberEnum { .. }
| Type::String
| Type::StringLiteral(_)
| Type::StringEnum { .. }
| Type::Boolean
| Type::BooleanLiteral(_)
| Type::BigInt
| Type::BigIntLiteral(_)
| Type::Uint8Array
| Type::Null
| Type::Undefined
| Type::Void
| Type::Unknown
| Type::Never
| Type::Error
| Type::Function { .. } => false,
_ => true,
}
}
pub(super) fn begin(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) -> Option<Locals> {
let previous = emitter.cast_diagnostic.take();
let current = ctx.latch(emitter.add_anonymous_local(ctx.latch(string_slot(ctx))?))?;
let failure = ctx.latch(add_failure(emitter, ctx))?;
super::cast_check::emit_inline_string(emitter, ctx, "$");
emitter.instruction(Instruction::LocalSet(current));
emitter.instruction(Instruction::RefNull(HeapType::Concrete(
ctx.latch(string_index(ctx))?,
)));
emitter.instruction(Instruction::LocalSet(failure.path));
emitter.cast_diagnostic = Some(Locals {
current,
failure,
key: None,
path: Vec::new(),
unions: Vec::new(),
open_unions: Vec::new(),
});
previous
}
fn add_failure(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
) -> Result<Failure, CompilerFailure> {
Ok(Failure {
path: emitter.add_anonymous_local(string_slot(ctx)?)?,
value: emitter.add_anonymous_local(object_slot(ctx)?)?,
expected: emitter.add_anonymous_local(string_slot(ctx)?)?,
tier: emitter.add_anonymous_local(ValType::I32)?,
})
}
fn copy_failure(emitter: &mut FunctionEmitter, from: Failure, to: Failure) {
for (from, to) in [
(from.path, to.path),
(from.value, to.value),
(from.expected, to.expected),
(from.tier, to.tier),
] {
emitter.instruction(Instruction::LocalGet(from));
emitter.instruction(Instruction::LocalSet(to));
}
}
pub(super) fn session_key(emitter: &mut FunctionEmitter, key: Option<u32>) {
if let Some(state) = &mut emitter.cast_diagnostic {
state.key = key;
}
}
pub(super) fn checkpoint(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) -> Option<u32> {
let state = emitter.cast_diagnostic.clone()?;
let saved = ctx.latch(emitter.add_anonymous_local(ctx.latch(string_slot(ctx))?))?;
emitter.instruction(Instruction::LocalGet(state.failure.path));
emitter.instruction(Instruction::LocalSet(saved));
Some(saved)
}
pub(super) fn finish(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
saved: Option<u32>,
tested: Tested<'_>,
) -> Result<(), CompilerFailure> {
let (Some(state), Some(saved)) = (emitter.cast_diagnostic.clone(), saved) else {
return Ok(());
};
let result = emitter.add_anonymous_local(ValType::I32)?;
emitter.instruction(Instruction::LocalTee(result));
emitter.emit_if(BlockType::Empty);
emitter.instruction(Instruction::LocalGet(saved));
emitter.instruction(Instruction::LocalSet(state.failure.path));
emitter.emit_else();
emitter.instruction(Instruction::LocalGet(state.failure.path));
emitter.instruction(Instruction::RefIsNull);
emitter.emit_if(BlockType::Empty);
emit_path(emitter, ctx, &state)?;
emitter.instruction(Instruction::LocalSet(state.failure.path));
record_tested(emitter, ctx, &state, tested)?;
emitter.emit_end();
emitter.emit_end();
emitter.instruction(Instruction::LocalGet(result));
Ok(())
}
fn record_tested(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
state: &Locals,
tested: Tested<'_>,
) -> Result<(), CompilerFailure> {
let ty = match tested {
Tested::Value { ty, local } => {
emitter.instruction(Instruction::LocalGet(local));
ty
}
Tested::Absent { ty } => {
crate::codegen::function_emitter::expr::emit_undefined(emitter, ctx)?;
ty
}
};
emitter.instruction(Instruction::LocalSet(state.failure.value));
super::cast_check::emit_inline_string(emitter, ctx, &expected_name(ty));
emitter.instruction(Instruction::LocalSet(state.failure.expected));
emitter.instruction(Instruction::I32Const(tier(state, ty)));
emitter.instruction(Instruction::LocalSet(state.failure.tier));
Ok(())
}
fn tier(state: &Locals, ty: &crate::Type) -> i32 {
match state.path.len().checked_sub(innermost_union_path(state)) {
None | Some(0) => AT_UNION,
Some(1) if is_discriminant(ty) => TAG_MISS,
Some(_) => IN_MEMBER,
}
}
fn innermost_union_path(state: &Locals) -> usize {
state.open_unions.last().copied().unwrap_or(0)
}
fn inside_member(state: &Locals) -> bool {
state.path.len() > innermost_union_path(state)
}
pub(super) fn discriminants_first(
fields: &std::collections::BTreeMap<String, crate::ObjectField>,
) -> impl Iterator<Item = (&String, &crate::ObjectField)> {
let (tags, rest): (Vec<_>, Vec<_>) = fields
.iter()
.partition(|(_, field)| is_discriminant(&field.ty));
tags.into_iter().chain(rest)
}
fn is_discriminant(ty: &crate::Type) -> bool {
use crate::Type;
match ty.peel() {
Type::Union(members) => members.iter().all(is_discriminant),
Type::StringLiteral(_)
| Type::NumberLiteral(_)
| Type::BooleanLiteral(_)
| Type::BigIntLiteral(_) => true,
Type::StringEnum { member, .. } => member.is_some(),
Type::NumberEnum { member, .. } => member.is_some(),
_ => false,
}
}
fn expected_name(ty: &crate::Type) -> String {
use crate::Type;
let written = ty.to_string();
if written.chars().count() <= MAX_EXPECTED_LEN {
return written;
}
if is_discriminant(ty) {
return "a literal".to_string();
}
match ty.peel() {
Type::Array(_) | Type::Tuple(_) => "an array",
Type::Union(_) => "a union",
Type::Function { .. } => "a function",
_ => "an object",
}
.to_string()
}
pub(super) fn union_start(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) -> Option<UnionLocals> {
let state = emitter.cast_diagnostic.clone()?;
let union = if let Some(union) = state.unions.get(state.open_unions.len()) {
*union
} else {
let union = UnionLocals {
result: ctx.latch(emitter.add_anonymous_local(ValType::I32))?,
best: ctx.latch(add_failure(emitter, ctx))?,
outer: ctx.latch(add_failure(emitter, ctx))?,
};
emitter.cast_diagnostic.as_mut()?.unions.push(union);
union
};
emitter
.cast_diagnostic
.as_mut()?
.open_unions
.push(state.path.len());
emitter.instruction(Instruction::RefNull(HeapType::Concrete(
ctx.latch(string_index(ctx))?,
)));
emitter.instruction(Instruction::LocalSet(union.best.path));
copy_failure(emitter, state.failure, union.outer);
Some(union)
}
pub(super) fn union_next(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
ctx.latch(union_next_checked(emitter, ctx));
}
fn union_next_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let Some(state) = emitter.cast_diagnostic.clone() else {
return Ok(());
};
emitter.instruction(Instruction::RefNull(HeapType::Concrete(string_index(ctx)?)));
emitter.instruction(Instruction::LocalSet(state.failure.path));
Ok(())
}
pub(super) fn union_keep(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
union: Option<UnionLocals>,
) {
let (Some(state), Some(union)) = (emitter.cast_diagnostic.clone(), union) else {
return;
};
failure_rank(emitter, ctx, state.failure);
failure_rank(emitter, ctx, union.best);
emitter.instruction(Instruction::I32GtU);
emitter.emit_if(BlockType::Empty);
copy_failure(emitter, state.failure, union.best);
emitter.emit_end();
}
fn failure_rank(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, failure: Failure) {
path_length(emitter, ctx, failure.path);
emitter.instruction(Instruction::I32Const(0));
emitter.instruction(Instruction::LocalGet(failure.tier));
emitter.instruction(Instruction::I32Const(TIER_SHIFT));
emitter.instruction(Instruction::I32Shl);
emitter.instruction(Instruction::LocalGet(failure.path));
emitter.instruction(Instruction::RefIsNull);
emitter.instruction(Instruction::Select);
emitter.instruction(Instruction::I32Or);
}
pub(super) fn union_end(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
union: Option<UnionLocals>,
) -> Result<(), CompilerFailure> {
let (Some(state), Some(union)) = (emitter.cast_diagnostic.as_mut(), union) else {
return Ok(());
};
state
.open_unions
.pop()
.ok_or_else(|| super::internal_failure("a union check ended without starting"))?;
let state = state.clone();
emitter.instruction(Instruction::LocalSet(union.result));
emitter.instruction(Instruction::LocalGet(union.outer.path));
emitter.instruction(Instruction::RefIsNull);
emitter.emit_if(BlockType::Empty);
copy_failure(emitter, union.best, state.failure);
emitter.instruction(Instruction::LocalGet(union.result));
emitter.instruction(Instruction::I32Eqz);
emitter.instruction(Instruction::LocalGet(state.failure.tier));
emitter.instruction(Instruction::I32Const(AT_UNION));
emitter.instruction(Instruction::I32Eq);
emitter.instruction(Instruction::I32And);
emitter.emit_if(BlockType::Empty);
emitter.instruction(Instruction::RefNull(HeapType::Concrete(string_index(ctx)?)));
emitter.instruction(Instruction::LocalSet(state.failure.path));
if inside_member(&state) {
emitter.emit_else();
emitter.instruction(Instruction::I32Const(IN_MEMBER));
emitter.instruction(Instruction::LocalSet(state.failure.tier));
}
emitter.emit_end();
emitter.emit_else();
copy_failure(emitter, union.outer, state.failure);
emitter.emit_end();
emitter.instruction(Instruction::LocalGet(union.result));
Ok(())
}
fn path_length(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, local: u32) {
ctx.latch(path_length_checked(emitter, ctx, local));
}
fn path_length_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
local: u32,
) -> Result<(), crate::compiler_error::CompilerFailure> {
emitter.instruction(Instruction::LocalGet(local));
emitter.instruction(Instruction::RefIsNull);
emitter.emit_if(BlockType::Result(ValType::I32));
emitter.instruction(Instruction::I32Const(0));
emitter.emit_else();
emitter.instruction(Instruction::LocalGet(local));
emitter.instruction(Instruction::RefAsNonNull);
emitter.instruction(Instruction::StructGet {
struct_type_index: string_index(ctx)?,
field_index: 1,
});
emitter.instruction(Instruction::ArrayLen);
emitter.emit_end();
Ok(())
}
pub(super) fn field(emitter: &mut FunctionEmitter, _ctx: &CodegenCtx, name: &str) -> Option<usize> {
let state = emitter.cast_diagnostic.as_mut()?;
let mark = state.path.len();
state.path.push(Segment::Field(name.into()));
Some(mark)
}
pub(super) fn index(emitter: &mut FunctionEmitter, _ctx: &CodegenCtx, index: u32) -> Option<usize> {
let state = emitter.cast_diagnostic.as_mut()?;
let mark = state.path.len();
state.path.push(Segment::Index(index));
Some(mark)
}
pub(super) fn pop(emitter: &mut FunctionEmitter, mark: Option<usize>) {
if let (Some(state), Some(mark)) = (emitter.cast_diagnostic.as_mut(), mark) {
state.path.truncate(mark);
}
}
fn emit_path(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
state: &Locals,
) -> Result<(), CompilerFailure> {
emitter.instruction(Instruction::LocalGet(state.current));
for segment in &state.path {
match segment {
Segment::Field(name) => {
let units = name.encode_utf16().count() / PATH_UNITS_PER_STEP;
let name_steps = u64::try_from(units).unwrap_or(u64::MAX);
ctx.charge_validator_steps(emitter, name_steps.saturating_add(1))?;
let quoted = serde_json::to_string(name).map_err(|error| {
super::internal_failure(format!("cannot quote a field name: {error}"))
})?;
super::cast_check::emit_inline_string(emitter, ctx, &format!("[{quoted}]"));
concat(emitter, ctx);
}
Segment::Index(index) => {
ctx.charge_validator_step(emitter)?;
super::cast_check::emit_inline_string(emitter, ctx, "[");
concat(emitter, ctx);
emitter.instruction(Instruction::LocalGet(*index));
emitter.instruction(Instruction::F64ConvertI32U);
emitter.instruction(Instruction::F64Const(10.0_f64.into()));
let number_to_string =
ctx.symbols
.prelude_func_idx("Number#toString")
.ok_or_else(|| {
super::internal_failure(
"Number#toString is not imported from the prelude",
)
})?;
emitter.instruction(Instruction::Call(number_to_string));
concat(emitter, ctx);
super::cast_check::emit_inline_string(emitter, ctx, "]");
concat(emitter, ctx);
}
}
}
Ok(())
}
pub(super) fn append_failure(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
ctx.latch(append_failure_checked(emitter, ctx));
}
fn append_failure_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let Some(state) = emitter.cast_diagnostic.clone() else {
return Ok(());
};
let Some(message) = ctx.latch(emitter.add_anonymous_local(string_slot(ctx)?)) else {
return Ok(());
};
emitter.instruction(Instruction::LocalSet(message));
path_length(emitter, ctx, state.failure.path);
emitter.instruction(Instruction::I32Const(1));
emitter.instruction(Instruction::I32GtU);
emitter.emit_if(BlockType::Result(string_slot(ctx)?));
emitter.instruction(Instruction::LocalGet(message));
super::cast_check::emit_inline_string(emitter, ctx, " at ");
concat(emitter, ctx);
emitter.instruction(Instruction::LocalGet(state.failure.path));
concat(emitter, ctx);
super::cast_check::emit_inline_string(emitter, ctx, " (expected ");
concat(emitter, ctx);
emitter.instruction(Instruction::LocalGet(state.failure.expected));
concat(emitter, ctx);
super::cast_check::emit_inline_string(emitter, ctx, ", got ");
concat(emitter, ctx);
super::cast_check::emit_runtime_type_string(emitter, ctx, state.failure.value);
concat(emitter, ctx);
super::cast_check::emit_inline_string(emitter, ctx, ")");
concat(emitter, ctx);
emitter.emit_else();
emitter.instruction(Instruction::LocalGet(message));
emitter.emit_end();
emitter.instruction(Instruction::RefAsNonNull);
if let Some(key) = state.key {
super::cast_check::emit_inline_string(emitter, ctx, " for session key ");
concat(emitter, ctx);
emitter.instruction(Instruction::LocalGet(key));
concat(emitter, ctx);
}
Ok(())
}
pub(super) fn save_recursive(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
visited: u32,
) -> Result<(), CompilerFailure> {
let Some(state) = emitter.cast_diagnostic.clone() else {
return Ok(());
};
let array = ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| super::internal_failure("intrinsic types are not declared"))?
.raw_array;
for (index, local) in [
(PATH_SLOT, state.current),
(FAILURE_SLOT, state.failure.path),
(VALUE_SLOT, state.failure.value),
(EXPECTED_SLOT, state.failure.expected),
] {
emitter.instruction(Instruction::LocalGet(visited));
emitter.instruction(Instruction::I32Const(index));
if index == PATH_SLOT {
emit_path(emitter, ctx, &state)?;
} else {
emitter.instruction(Instruction::LocalGet(local));
}
emitter.instruction(Instruction::ArraySet(array));
}
for (index, tier) in [(TAG_MISS_SLOT, TAG_MISS), (IN_MEMBER_SLOT, IN_MEMBER)] {
emitter.instruction(Instruction::LocalGet(visited));
emitter.instruction(Instruction::I32Const(index));
emitter.instruction(Instruction::LocalGet(state.failure.path));
emitter.instruction(Instruction::RefNull(HeapType::Concrete(string_index(ctx)?)));
emitter.instruction(Instruction::LocalGet(state.failure.tier));
emitter.instruction(Instruction::I32Const(tier));
emitter.instruction(Instruction::I32Eq);
emitter.instruction(Instruction::TypedSelect(string_slot(ctx)?));
emitter.instruction(Instruction::ArraySet(array));
}
Ok(())
}
pub(super) fn load_recursive(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, visited: u32) {
ctx.latch(load_recursive_checked(emitter, ctx, visited));
}
fn load_recursive_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
visited: u32,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let Some(state) = emitter.cast_diagnostic.clone() else {
return Ok(());
};
let array = ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?
.raw_array;
emitter.instruction(Instruction::LocalGet(visited));
emitter.instruction(Instruction::I32Const(FAILURE_SLOT));
emitter.instruction(Instruction::ArrayGet(array));
emitter.instruction(Instruction::LocalGet(state.failure.path));
emitter.instruction(Instruction::RefEq);
emitter.instruction(Instruction::I32Eqz);
emitter.emit_if(BlockType::Empty);
if inside_member(&state) {
emitter.instruction(Instruction::I32Const(IN_MEMBER));
} else {
emitter.instruction(Instruction::I32Const(AT_UNION));
for (index, tier) in [(TAG_MISS_SLOT, TAG_MISS), (IN_MEMBER_SLOT, IN_MEMBER)] {
emitter.instruction(Instruction::I32Const(tier));
emitter.instruction(Instruction::LocalGet(visited));
emitter.instruction(Instruction::I32Const(index));
emitter.instruction(Instruction::ArrayGet(array));
emitter.instruction(Instruction::RefIsNull);
emitter.instruction(Instruction::Select);
}
}
emitter.instruction(Instruction::LocalSet(state.failure.tier));
emitter.emit_end();
for (index, local, heap) in [
(FAILURE_SLOT, state.failure.path, string_index(ctx)?),
(VALUE_SLOT, state.failure.value, object_index(ctx)?),
(EXPECTED_SLOT, state.failure.expected, string_index(ctx)?),
] {
emitter.instruction(Instruction::LocalGet(visited));
emitter.instruction(Instruction::I32Const(index));
emitter.instruction(Instruction::ArrayGet(array));
emitter.instruction(Instruction::RefCastNullable(HeapType::Concrete(heap)));
emitter.instruction(Instruction::LocalSet(local));
}
Ok(())
}
pub(super) fn enter_recursive(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
ctx.latch(enter_recursive_checked(emitter, ctx));
}
fn enter_recursive_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
) -> Result<(), crate::compiler_error::CompilerFailure> {
begin(emitter, ctx);
let state = emitter
.cast_diagnostic
.clone()
.ok_or_else(|| crate::codegen::internal_failure("validator diagnostic"))?;
let array = ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?
.raw_array;
emitter.instruction(Instruction::LocalGet(1));
emitter.instruction(Instruction::I32Const(PATH_SLOT));
emitter.instruction(Instruction::ArrayGet(array));
emitter.instruction(Instruction::RefCastNullable(HeapType::Concrete(
string_index(ctx)?,
)));
let Some(incoming) = ctx.latch(emitter.add_anonymous_local(string_slot(ctx)?)) else {
return Ok(());
};
emitter.instruction(Instruction::LocalTee(incoming));
emitter.instruction(Instruction::RefIsNull);
emitter.instruction(Instruction::I32Eqz);
emitter.emit_if(BlockType::Empty);
emitter.instruction(Instruction::LocalGet(incoming));
emitter.instruction(Instruction::LocalSet(state.current));
emitter.emit_end();
load_recursive(emitter, ctx, 1);
Ok(())
}
pub(super) fn concat(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
ctx.latch(concat_checked(emitter, ctx));
}
fn concat_checked(
emitter: &mut FunctionEmitter,
ctx: &CodegenCtx,
) -> Result<(), crate::compiler_error::CompilerFailure> {
let Some(right) = ctx.latch(emitter.add_anonymous_local(string_slot(ctx)?)) else {
return Ok(());
};
emitter.instruction(Instruction::LocalSet(right));
emitter.instruction(Instruction::RefAsNonNull);
emitter.instruction(Instruction::LocalGet(right));
emitter.instruction(Instruction::RefAsNonNull);
emitter.instruction(Instruction::Call(
ctx.symbols
.prelude_func_idx("string_concat")
.ok_or_else(|| crate::codegen::internal_failure("string concat"))?,
));
Ok(())
}
fn string_index(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
ctx.symbols
.string_type_idx()
.ok_or_else(|| crate::codegen::internal_failure("string"))
}
fn object_index(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
Ok(ctx
.symbols
.intrinsic_type_indices()
.ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?
.object)
}
fn object_slot(ctx: &CodegenCtx) -> Result<ValType, crate::compiler_error::CompilerFailure> {
Ok(ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(object_index(ctx)?),
}))
}
fn string_slot(ctx: &CodegenCtx) -> Result<ValType, crate::compiler_error::CompilerFailure> {
Ok(ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(string_index(ctx)?),
}))
}