blues-lsp 0.1.0

LSP language server for the Bluespec SystemVerilog language
Documentation
use std::{
    any,
    collections::{HashMap, HashSet},
    fmt::Debug,
    hash::Hash,
    sync::Arc,
};

use parking_lot::Mutex;
use tracing::warn;

use super::Project;

pub trait Queryable: Sized {
    type Input: Debug + Hash + Eq + Clone;
    type Backref: Debug + Hash + Eq + Clone;
    type Ctx: Default;

    fn compute(input: Self::Input, ctx: &Self::Ctx, proj: &Project) -> Arc<Self>;

    fn invalidate_backref(&self, backref: Self::Backref, proj: &Project);
    fn invalidate(&self, input: &Self::Input, ctx: &Self::Ctx, proj: &Project);
}

struct QuerryEntry<Q: Queryable> {
    data: Arc<Q>,
    backrefs: HashSet<Q::Backref>,
}
pub struct Querry<Q: Queryable> {
    cache: Mutex<HashMap<Q::Input, QuerryEntry<Q>>>,
    ctx: Q::Ctx,
}

impl<Q: Queryable> Querry<Q> {
    pub fn new() -> Self {
        Self {
            cache: Mutex::new(HashMap::new()),
            ctx: Default::default(),
        }
    }

    fn get_inner(&self, proj: &Project, i: Q::Input, backref: Option<Q::Backref>) -> Arc<Q> {
        if let Some(entry) = self.cache.lock().get_mut(&i) {
            backref.map(|b| entry.backrefs.insert(b));
            return entry.data.clone();
        }

        let computed = Q::compute(i.clone(), &self.ctx, proj);

        // Multiple threads may race to compute the same querry,
        // this is necessary to prevent deadlocks in situations where, eg.:
        // - Computing A requires querrying B
        // - Computing B requires querrying A
        // Here we let the first result win as otherwise we might
        // keep Arc's with duplicate data alive.
        let mut cache = self.cache.lock();
        let entry = cache.entry(i).or_insert_with(|| QuerryEntry {
            data: computed,
            backrefs: HashSet::new(),
        });

        backref.map(|b| entry.backrefs.insert(b));

        entry.data.clone()
    }

    pub fn get(&self, proj: &Project, i: Q::Input) -> Arc<Q> {
        self.get_inner(proj, i, None)
    }

    pub fn get_as(&self, proj: &Project, i: Q::Input, backref: Q::Backref) -> Arc<Q> {
        self.get_inner(proj, i, Some(backref))
    }

    pub fn invalidate(&self, proj: &Project, input: &Q::Input) {
        let cached = self.cache.lock().remove(input);
        if let Some(mut entry) = cached {
            entry
                .backrefs
                .into_iter()
                .for_each(|b| entry.data.invalidate_backref(b, proj));
            entry.data.invalidate(input, &self.ctx, proj);
            if Arc::get_mut(&mut entry.data).is_none() {
                warn!(
                    "Querry '{}' with input '{:?}' still has references after invalidation: {:?}",
                    any::type_name::<Q>(),
                    input,
                    (
                        Arc::strong_count(&entry.data) - 1,
                        Arc::weak_count(&entry.data)
                    )
                )
            }
        }
    }

    pub fn clear(&self, proj: &Project) {
        for i in self.cache.lock().keys() {
            self.invalidate(proj, i);
        }
    }
}

impl<Q: Queryable> Default for Querry<Q> {
    fn default() -> Self {
        Self::new()
    }
}