use crate::{
LanguageService,
binder::{Symbol, SymbolKey, SymbolTable},
document::Document,
helpers,
types_analyzer::{self, CompositeType, DefTypes, FieldType, Fields, StorageType},
uri::InternUri,
};
use line_index::LineIndex;
use lspt::{Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Location, Union2};
use rowan::ast::support;
use wat_syntax::{SyntaxKind, SyntaxNode};
const DIAGNOSTIC_CODE: &str = "packing";
pub fn check(
service: &LanguageService,
uri: InternUri,
document: Document,
line_index: &LineIndex,
symbol_table: &SymbolTable,
node: &SyntaxNode,
) -> Option<Diagnostic> {
let instr_name = support::token(node, SyntaxKind::INSTR_NAME)?;
match instr_name.text() {
"struct.get" => {
let def_types = types_analyzer::get_def_types(service, document);
if let Some((_, symbol)) =
find_struct_field(symbol_table, def_types, node).filter(|(ty, _)| ty.is_packed())
{
Some(Diagnostic {
range: helpers::rowan_range_to_lsp_range(line_index, symbol.key.text_range()),
severity: Some(DiagnosticSeverity::Error),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: format!("field `{}` is packed", symbol.idx.render(service)),
related_information: Some(vec![DiagnosticRelatedInformation {
location: Location {
uri: uri.raw(service),
range: helpers::rowan_range_to_lsp_range(
line_index,
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" => {
let def_types = types_analyzer::get_def_types(service, document);
if let Some((_, symbol)) =
find_struct_field(symbol_table, def_types, node).filter(|(ty, _)| !ty.is_packed())
{
Some(Diagnostic {
range: helpers::rowan_range_to_lsp_range(line_index, symbol.key.text_range()),
severity: Some(DiagnosticSeverity::Error),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: format!("field `{}` is unpacked", symbol.idx.render(service)),
related_information: Some(vec![DiagnosticRelatedInformation {
location: Location {
uri: uri.raw(service),
range: helpers::rowan_range_to_lsp_range(
line_index,
instr_name.text_range(),
),
},
message: "use `struct.get` instead".into(),
}]),
..Default::default()
})
} else {
None
}
}
"array.get" => {
let def_types = types_analyzer::get_def_types(service, document);
if let Some((_, symbol)) =
find_array(symbol_table, def_types, node).filter(|(ty, _)| ty.is_packed())
{
Some(Diagnostic {
range: helpers::rowan_range_to_lsp_range(line_index, symbol.key.text_range()),
severity: Some(DiagnosticSeverity::Error),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: format!("array `{}` is packed", symbol.idx.render(service)),
related_information: Some(vec![DiagnosticRelatedInformation {
location: Location {
uri: uri.raw(service),
range: helpers::rowan_range_to_lsp_range(
line_index,
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" => {
let def_types = types_analyzer::get_def_types(service, document);
if let Some((_, symbol)) =
find_array(symbol_table, def_types, node).filter(|(ty, _)| !ty.is_packed())
{
Some(Diagnostic {
range: helpers::rowan_range_to_lsp_range(line_index, symbol.key.text_range()),
severity: Some(DiagnosticSeverity::Error),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: format!("array `{}` is unpacked", symbol.idx.render(service)),
related_information: Some(vec![DiagnosticRelatedInformation {
location: Location {
uri: uri.raw(service),
range: helpers::rowan_range_to_lsp_range(
line_index,
instr_name.text_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>,
node: &SyntaxNode,
) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
let mut immediates = node
.children()
.filter(|child| child.kind() == SyntaxKind::IMMEDIATE);
let struct_def_key = symbol_table
.resolved
.get(&SymbolKey::new(&immediates.next()?))?;
let field_ref_symbol = symbol_table
.symbols
.get(&SymbolKey::new(&immediates.next()?))?;
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>,
node: &SyntaxNode,
) -> Option<(&'db StorageType<'db>, &'db Symbol<'db>)> {
let ref_key = SymbolKey::new(&node.first_child_by_kind(&|kind| kind == SyntaxKind::IMMEDIATE)?);
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
}
}