wat_service 0.10.0

WebAssembly Text Format language service.
Documentation
use super::{Diagnostic, DiagnosticCtx, RelatedInformation};
use crate::{
    binder::{Symbol, SymbolKey},
    types_analyzer::{CompositeType, FieldType, Fields, StorageType},
};
use wat_syntax::{AmberNode, AmberToken, SyntaxKind};

const DIAGNOSTIC_CODE: &str = "packing";

pub fn check(ctx: &DiagnosticCtx, node: AmberNode, instr_name: AmberToken) -> Option<Diagnostic> {
    match instr_name.text() {
        "struct.get" => {
            if let Some((_, symbol)) = find_struct_field(ctx, node).filter(|(ty, _)| ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("field `{}` is packed", symbol.idx.render(ctx.db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr_name.text_range(),
                        message: "use `struct.get_s` or `struct.get_u` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "struct.get_s" | "struct.get_u" => {
            if let Some((_, symbol)) = find_struct_field(ctx, node).filter(|(ty, _)| !ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("field `{}` is unpacked", symbol.idx.render(ctx.db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr_name.text_range(),
                        message: "use `struct.get` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "array.get" => {
            if let Some((_, symbol)) = find_array(ctx, node).filter(|(ty, _)| ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("array `{}` is packed", symbol.idx.render(ctx.db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr_name.text_range(),
                        message: "use `array.get_s` or `array.get_u` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "array.get_s" | "array.get_u" => {
            if let Some((_, symbol)) = find_array(ctx, node).filter(|(ty, _)| !ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("array `{}` is unpacked", symbol.idx.render(ctx.db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr_name.text_range(),
                        message: "use `array.get` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        _ => None,
    }
}

fn find_struct_field<'db>(
    ctx: &'db DiagnosticCtx,
    instr: AmberNode,
) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
    let mut immediates = instr.children_by_kind(SyntaxKind::IMMEDIATE);
    let struct_def_key = ctx.symbol_table.resolved.get(&immediates.next()?.to_ptr().into())?;
    let field_ref_symbol = ctx
        .symbol_table
        .symbols
        .get(&SymbolKey::from(immediates.next()?.to_ptr()))?;
    if let Some(CompositeType::Struct(Fields(fields))) =
        ctx.def_types.get(struct_def_key).map(|def_type| &def_type.comp)
    {
        fields
            .iter()
            .find(|(_, idx)| field_ref_symbol.idx.is_defined_by(idx))
            .map(|(ty, _)| (&ty.storage, field_ref_symbol))
    } else {
        None
    }
}

fn find_array<'db>(ctx: &'db DiagnosticCtx, instr: AmberNode) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
    let ref_key = instr.children_by_kind(SyntaxKind::IMMEDIATE).next()?.to_ptr().into();
    let ref_symbol = ctx.symbol_table.symbols.get(&ref_key)?;
    if let Some(CompositeType::Array(Some(FieldType { storage, .. }))) = ctx
        .def_types
        .get(ctx.symbol_table.resolved.get(&ref_key)?)
        .map(|def_type| &def_type.comp)
    {
        Some((storage, ref_symbol))
    } else {
        None
    }
}