wat_service 0.9.0

WebAssembly Text Format language service.
Documentation
use super::{Diagnostic, FastPlainInstr, RelatedInformation};
use crate::{
    binder::{Symbol, SymbolKey, SymbolTable},
    document::Document,
    types_analyzer::{self, CompositeType, DefTypes, FieldType, Fields, StorageType},
};

const DIAGNOSTIC_CODE: &str = "packing";

pub fn check(
    db: &dyn salsa::Database,
    document: Document,
    symbol_table: &SymbolTable,
    instr: &FastPlainInstr,
) -> Option<Diagnostic> {
    match instr.name {
        "struct.get" => {
            let def_types = types_analyzer::get_def_types(db, document);
            if let Some((_, symbol)) =
                find_struct_field(symbol_table, def_types, instr).filter(|(ty, _)| ty.is_packed())
            {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("field `{}` is packed", symbol.idx.render(db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr.name_range,
                        message: "use `struct.get_s` or `struct.get_u` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "struct.get_s" | "struct.get_u" => {
            let def_types = types_analyzer::get_def_types(db, document);
            if let Some((_, symbol)) =
                find_struct_field(symbol_table, def_types, instr).filter(|(ty, _)| !ty.is_packed())
            {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("field `{}` is unpacked", symbol.idx.render(db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr.name_range,
                        message: "use `struct.get` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "array.get" => {
            let def_types = types_analyzer::get_def_types(db, document);
            if let Some((_, symbol)) = find_array(symbol_table, def_types, instr).filter(|(ty, _)| ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("array `{}` is packed", symbol.idx.render(db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr.name_range,
                        message: "use `array.get_s` or `array.get_u` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        "array.get_s" | "array.get_u" => {
            let def_types = types_analyzer::get_def_types(db, document);
            if let Some((_, symbol)) = find_array(symbol_table, def_types, instr).filter(|(ty, _)| !ty.is_packed()) {
                Some(Diagnostic {
                    range: symbol.key.text_range(),
                    code: DIAGNOSTIC_CODE.into(),
                    message: format!("array `{}` is unpacked", symbol.idx.render(db)),
                    related_information: Some(vec![RelatedInformation {
                        range: instr.name_range,
                        message: "use `array.get` instead".into(),
                    }]),
                    ..Default::default()
                })
            } else {
                None
            }
        }
        _ => None,
    }
}

fn find_struct_field<'db>(
    symbol_table: &'db SymbolTable<'db>,
    def_types: &'db DefTypes<'db>,
    instr: &FastPlainInstr,
) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
    let struct_def_key = symbol_table.resolved.get(&instr.immediates.first().copied()?.into())?;
    let field_ref_symbol = symbol_table
        .symbols
        .get(&SymbolKey::from(instr.immediates.get(1).copied()?))?;
    if let Some(CompositeType::Struct(Fields(fields))) = 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>(
    symbol_table: &'db SymbolTable<'db>,
    def_types: &'db DefTypes<'db>,
    instr: &FastPlainInstr,
) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
    let ref_key = instr.immediates.first().copied()?.into();
    let ref_symbol = symbol_table.symbols.get(&ref_key)?;
    if let Some(CompositeType::Array(Some(FieldType { storage, .. }))) = def_types
        .get(symbol_table.resolved.get(&ref_key)?)
        .map(|def_type| &def_type.comp)
    {
        Some((storage, ref_symbol))
    } else {
        None
    }
}