mir-analyzer 0.79.0

Analysis engine for the mir PHP static analyzer
Documentation
//! Demand-driven inferred return type lookups for body analysis.
//!
//! body-analysis callers use [`inferred_function_return_type_demand`] /
//! [`inferred_method_return_type_demand`] to resolve cross-file inferred
//! return types on demand via the salsa query graph.  No pre-committed
//! singleton is needed.

use std::cell::RefCell;
use std::collections::HashSet;
use std::sync::Arc;

use mir_types::Type;

use crate::db::{Fqcn, MirDatabase};

thread_local! {
    // Guards against re-entrant demand for a file currently being inferred on
    // this thread. When mutually-referential classes trigger a cycle that salsa
    // hasn't closed yet, the same file can be demanded again before the first
    // inference completes.  Returning None (→ mixed) breaks the recursion and
    // lets the fixpoint converge.
    static INFER_IN_PROGRESS: RefCell<HashSet<Arc<str>>> = RefCell::new(HashSet::new());
}

pub(crate) struct InferGuard(Arc<str>);

impl Drop for InferGuard {
    fn drop(&mut self) {
        INFER_IN_PROGRESS.with(|s| s.borrow_mut().remove(&self.0));
    }
}

pub(crate) fn try_mark_infer_in_progress(path: Arc<str>) -> Option<InferGuard> {
    let already_active = INFER_IN_PROGRESS.with(|s| s.borrow().contains(&path));
    if already_active {
        return None;
    }
    INFER_IN_PROGRESS.with(|s| s.borrow_mut().insert(path.clone()));
    Some(InferGuard(path))
}

/// Demand-driven inferred return type lookup for a function.
///
/// Locates the file that declares `fqn` (the symbol index, else on demand),
/// then calls `infer_file_return_types` on that file. Salsa
/// memoizes both queries, so repeated lookups for the same function are free.
/// Returns `None` when the function is unknown.
pub fn inferred_function_return_type_demand(db: &dyn MirDatabase, fqn: &str) -> Option<Arc<Type>> {
    let sf = crate::db::function_loc(db, Fqcn::from_str(db, fqn))?.file();
    let path = sf.path(db).clone();
    let _guard = try_mark_infer_in_progress(path)?;
    let inferred = crate::db::infer_file_return_types(db, sf);
    inferred.functions.get(fqn).cloned()
}

/// Demand-driven inferred return type lookup for a method.
///
/// Locates the file that declares the class (the symbol index, else on
/// demand), then calls `infer_file_return_types` on that file.
/// `method_name_lower` must already be ASCII-lowercased (PHP semantics).
/// Returns `None` when the class or method is unknown.
pub fn inferred_method_return_type_demand(
    db: &dyn MirDatabase,
    fqcn: &str,
    method_name_lower: &str,
) -> Option<Arc<Type>> {
    let sf = crate::db::class_like_loc(db, Fqcn::from_str(db, fqcn))?.file();
    let path = sf.path(db).clone();
    let _guard = try_mark_infer_in_progress(path)?;
    let inferred = crate::db::infer_file_return_types(db, sf);
    inferred
        .methods
        .get(&(Arc::<str>::from(fqcn), Arc::<str>::from(method_name_lower)))
        .cloned()
}

/// Demand-driven inferred property type lookup, for a property with no
/// native type hint and no `@var` docblock (see `PropertyDef::ty`).
///
/// Locates the file that declares `fqcn` (the symbol index, else on demand),
/// then calls `infer_file_return_types` on that file — the same
/// inference-only body-analysis pass also collects the union of types
/// directly assigned to the property in `fqcn`'s own constructor. `fqcn`
/// must be the property's DECLARING class (e.g. `find_property_in_chain`'s
/// owner), and `name` is case-sensitive (PHP property names are). Returns
/// `None` when the class is unknown or nothing was inferred for it.
pub fn inferred_property_type_demand(
    db: &dyn MirDatabase,
    fqcn: &str,
    name: &str,
) -> Option<Arc<Type>> {
    let sf = crate::db::class_like_loc(db, Fqcn::from_str(db, fqcn))?.file();
    let path = sf.path(db).clone();
    let _guard = try_mark_infer_in_progress(path)?;
    let inferred = crate::db::infer_file_return_types(db, sf);
    inferred
        .properties
        .get(&(Arc::<str>::from(fqcn), Arc::<str>::from(name)))
        .cloned()
}