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, Update};
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`.
29unsafe impl Update for MethodReturnsArc {
30    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
31        let old_ref = unsafe { &mut *old_pointer };
32        if Arc::ptr_eq(&old_ref.0, &new_value.0) {
33            false
34        } else {
35            *old_ref = new_value;
36            true
37        }
38    }
39}
40
41#[salsa::tracked(no_eq)]
42pub fn method_returns(db: &dyn Database, file: SourceFile) -> MethodReturnsArc {
43    let doc = parsed_doc(db, file);
44    MethodReturnsArc(Arc::new(build_method_returns(doc.get())))
45}
46
47#[cfg(test)]
48mod tests {
49    use std::sync::Arc;
50
51    use super::*;
52    use crate::db::analysis::AnalysisHost;
53    use crate::db::input::{FileId, SourceFile};
54    use salsa::Setter;
55
56    #[test]
57    fn method_returns_captures_factory_return() {
58        let host = AnalysisHost::new();
59        let file = SourceFile::new(
60            host.db(),
61            FileId(0),
62            Arc::<str>::from("file:///t.php"),
63            Arc::<str>::from(
64                "<?php\nclass Foo {\n    public function make(): Bar { return new Bar(); }\n}\nclass Bar {}",
65            ),
66            None,
67        );
68        let m = method_returns(host.db(), file);
69        let foo = m.get().get("Foo").expect("class Foo in map");
70        assert_eq!(foo.get("make").map(String::as_str), Some("Bar"));
71    }
72
73    #[test]
74    fn method_returns_reruns_after_edit() {
75        let mut host = AnalysisHost::new();
76        let file = SourceFile::new(
77            host.db(),
78            FileId(1),
79            Arc::<str>::from("file:///t.php"),
80            Arc::<str>::from(
81                "<?php\nclass F {\n    public function m(): A { return new A(); }\n}\nclass A {}\nclass B {}",
82            ),
83            None,
84        );
85        let a1 = method_returns(host.db(), file);
86        let first = Arc::as_ptr(&a1.0);
87
88        file.set_text(host.db_mut()).to(Arc::<str>::from(
89            "<?php\nclass F {\n    public function m(): B { return new B(); }\n}\nclass A {}\nclass B {}",
90        ));
91        let a2 = method_returns(host.db(), file);
92        assert_ne!(
93            first,
94            Arc::as_ptr(&a2.0),
95            "editing the source should produce a new map"
96        );
97        assert_eq!(
98            a2.get()
99                .get("F")
100                .and_then(|m| m.get("m"))
101                .map(String::as_str),
102            Some("B")
103        );
104    }
105}