wat_service 0.8.0

WebAssembly Text Format language service.
Documentation
use crate::{
    binder::{SymbolKey, SymbolKind, SymbolTable},
    document::Document,
};
use indexmap::IndexMap;
use rowan::{
    TextRange,
    ast::{AstNode, support},
};
use rustc_hash::FxBuildHasher;
use wat_syntax::{
    SyntaxKind,
    ast::{CompType, ExternTypeGlobal, FieldType, ModuleFieldGlobal, PlainInstr, TypeDef},
};

#[salsa::tracked(returns(ref))]
pub(crate) fn get_mutabilities<'db>(
    db: &'db dyn salsa::Database,
    document: Document,
) -> IndexMap<SymbolKey, Mutability, FxBuildHasher> {
    let root = document.root_tree(db);
    let symbol_table = SymbolTable::of(db, document);
    symbol_table
        .symbols
        .values()
        .filter_map(|symbol| match symbol.kind {
            SymbolKind::GlobalDef => {
                let node = symbol.key.to_node(&root);
                match node.kind() {
                    SyntaxKind::MODULE_FIELD_GLOBAL => {
                        let global = ModuleFieldGlobal::cast(node)?;
                        let range = global
                            .global_type()
                            .and_then(|global_type| global_type.mut_keyword())
                            .map(|token| token.text_range());
                        Some((
                            symbol.key,
                            Mutability {
                                mut_keyword: range,
                                cross_module: global.exports().count() > 0,
                            },
                        ))
                    }
                    SyntaxKind::EXTERN_TYPE_GLOBAL => {
                        let global = ExternTypeGlobal::cast(node)?;
                        let range = global
                            .global_type()
                            .and_then(|global_type| global_type.mut_keyword())
                            .map(|token| token.text_range());
                        Some((
                            symbol.key,
                            Mutability {
                                mut_keyword: range,
                                cross_module: true,
                            },
                        ))
                    }
                    _ => None,
                }
            }
            SymbolKind::Type => TypeDef::cast(symbol.key.to_node(&root))
                .and_then(|type_def| type_def.sub_type())
                .and_then(|sub_type| sub_type.comp_type())
                .and_then(|comp_type| {
                    if let CompType::Array(array_type) = comp_type {
                        array_type.field_type()
                    } else {
                        None
                    }
                })
                .map(|field_type| {
                    let range = field_type.mut_keyword().map(|token| token.text_range());
                    (
                        symbol.key,
                        Mutability {
                            mut_keyword: range,
                            cross_module: false,
                        },
                    )
                }),
            SymbolKind::FieldDef => {
                let node = symbol.key.to_node(&root);
                let range = if node.kind() == SyntaxKind::FIELD {
                    support::child::<FieldType>(&node)
                } else {
                    FieldType::cast(node)
                }
                .and_then(|field_type| field_type.mut_keyword())
                .map(|token| token.text_range());
                Some((
                    symbol.key,
                    Mutability {
                        mut_keyword: range,
                        cross_module: false,
                    },
                ))
            }
            _ => None,
        })
        .collect()
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Mutability {
    pub(crate) mut_keyword: Option<TextRange>,
    pub(crate) cross_module: bool,
}

#[salsa::tracked(returns(ref))]
pub(crate) fn get_mutation_actions<'db>(
    db: &'db dyn salsa::Database,
    document: Document,
) -> IndexMap<SymbolKey, MutationAction, FxBuildHasher> {
    let root = document.root_tree(db);
    let symbol_table = SymbolTable::of(db, document);
    symbol_table
        .symbols
        .values()
        .filter_map(|symbol| match symbol.kind {
            SymbolKind::GlobalRef => {
                let parent = symbol.key.to_node(&root).parent()?;
                let kind = match parent.kind() {
                    SyntaxKind::PLAIN_INSTR => {
                        match PlainInstr::cast(parent)?.instr_name()?.text() {
                            "global.get" => MutationActionKind::Get,
                            "global.set" => MutationActionKind::Set,
                            _ => return None,
                        }
                    }
                    SyntaxKind::EXTERN_IDX_GLOBAL => MutationActionKind::Export,
                    _ => return None,
                };
                let target = symbol_table.resolved.get(&symbol.key).copied();
                Some((symbol.key, MutationAction { target, kind }))
            }
            SymbolKind::FieldRef => {
                let parent = symbol.key.to_node(&root).parent()?;
                let kind = match PlainInstr::cast(parent)?.instr_name()?.text() {
                    "struct.get" | "struct.get_s" | "struct.get_u" => MutationActionKind::Get,
                    "struct.set" => MutationActionKind::Set,
                    _ => return None,
                };
                let target = symbol_table.resolved.get(&symbol.key).copied();
                Some((symbol.key, MutationAction { target, kind }))
            }
            SymbolKind::TypeUse => {
                let current_node = symbol.key.to_node(&root);
                let parent = current_node.parent()?;
                let kind = match PlainInstr::cast(parent.clone())?.instr_name()?.text() {
                    "array.get" | "array.get_s" | "array.get_u" => MutationActionKind::Get,
                    "array.set" | "array.fill" | "array.init_data" | "array.init_elem" => {
                        MutationActionKind::Set
                    }
                    "array.copy" => {
                        if parent.children().next() == Some(current_node) {
                            MutationActionKind::Set
                        } else {
                            MutationActionKind::Get
                        }
                    }
                    _ => return None,
                };
                let target = symbol_table.resolved.get(&symbol.key).copied();
                Some((symbol.key, MutationAction { target, kind }))
            }
            _ => None,
        })
        .collect()
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct MutationAction {
    pub(crate) target: Option<SymbolKey>,
    pub(crate) kind: MutationActionKind,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum MutationActionKind {
    Get,
    Set,
    Export,
}