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
}
}