Skip to main content

php_lsp/db/
method_returns.rs

1//! `method_returns` salsa query — derives the per-file map of
2//! `class_name -> method_name -> return_class_name`. Depends on `parsed_doc`.
3//!
4//! This is the sole cache for method-return inference since Phase E3 removed
5//! the `OnceLock<MethodReturnsMap>` from `ParsedDoc`. Production call sites
6//! (inlay_hints, type_definition, hover, completion) fetch the memoized
7//! `Arc<MethodReturnsMap>` via `DocumentStore::get_method_returns_salsa` /
8//! `other_docs_with_returns` and pass it into the `TypeMap` constructors.
9
10use std::sync::Arc;
11
12use salsa::Database;
13
14use crate::ast::MethodReturnsMap;
15use crate::db::input::SourceFile;
16use crate::db::parse::parsed_doc;
17use crate::type_map::build_method_returns;
18
19#[derive(Clone)]
20pub struct MethodReturnsArc(pub Arc<MethodReturnsMap>);
21
22impl MethodReturnsArc {
23    pub fn get(&self) -> &MethodReturnsMap {
24        &self.0
25    }
26}
27
28// SAFETY: identical contract to `ParsedArc::maybe_update`.
29crate::impl_arc_update!(MethodReturnsArc);
30
31#[salsa::tracked(no_eq)]
32pub fn method_returns(db: &dyn Database, file: SourceFile) -> MethodReturnsArc {
33    let doc = parsed_doc(db, file);
34    MethodReturnsArc(Arc::new(build_method_returns(doc.get())))
35}
36
37#[cfg(test)]
38mod tests {
39    use std::sync::Arc;
40
41    use super::*;
42    use crate::db::analysis::AnalysisHost;
43    use crate::db::input::{FileId, SourceFile};
44    use salsa::Setter;
45
46    #[test]
47    fn method_returns_captures_factory_return() {
48        let host = AnalysisHost::new();
49        let file = SourceFile::new(
50            host.db(),
51            FileId(0),
52            Arc::<str>::from("file:///t.php"),
53            Arc::<str>::from(
54                "<?php\nclass Foo {\n    public function make(): Bar { return new Bar(); }\n}\nclass Bar {}",
55            ),
56            None,
57        );
58        let m = method_returns(host.db(), file);
59        let foo = m.get().get("Foo").expect("class Foo in map");
60        assert_eq!(foo.get("make").map(String::as_str), Some("Bar"));
61    }
62
63    #[test]
64    fn method_returns_reruns_after_edit() {
65        let mut host = AnalysisHost::new();
66        let file = SourceFile::new(
67            host.db(),
68            FileId(1),
69            Arc::<str>::from("file:///t.php"),
70            Arc::<str>::from(
71                "<?php\nclass F {\n    public function m(): A { return new A(); }\n}\nclass A {}\nclass B {}",
72            ),
73            None,
74        );
75        let a1 = method_returns(host.db(), file);
76        let first = Arc::as_ptr(&a1.0);
77
78        file.set_text(host.db_mut()).to(Arc::<str>::from(
79            "<?php\nclass F {\n    public function m(): B { return new B(); }\n}\nclass A {}\nclass B {}",
80        ));
81        let a2 = method_returns(host.db(), file);
82        assert_ne!(
83            first,
84            Arc::as_ptr(&a2.0),
85            "editing the source should produce a new map"
86        );
87        assert_eq!(
88            a2.get()
89                .get("F")
90                .and_then(|m| m.get("m"))
91                .map(String::as_str),
92            Some("B")
93        );
94    }
95}