1use crate::call_graph::CallGraphQuery;
2use crate::client::SearchfoxClient;
3use anyhow::Result;
4use reqwest::Url;
5use std::collections::HashSet;
6
7pub struct GcInfo {
8 pub pretty: String,
9 pub mangled: String,
10 pub can_gc: bool,
11 pub gc_path: Option<String>,
12}
13
14impl SearchfoxClient {
15 pub async fn get_gc_info(&self, symbol: &str) -> Result<Vec<GcInfo>> {
16 let results = self.query_gc_from_call_graph(symbol).await?;
17 if !results.is_empty() {
18 return Ok(results);
19 }
20
21 let full_names = self.resolve_full_names(symbol).await?;
24 let mut all_results = Vec::new();
25 let mut seen_keys: HashSet<(String, bool, Option<String>)> = HashSet::new();
26
27 for full_name in full_names {
28 if full_name == symbol {
29 continue;
30 }
31 for info in self.query_gc_from_call_graph(&full_name).await? {
32 let key = (info.pretty.clone(), info.can_gc, info.gc_path.clone());
33 if seen_keys.insert(key) {
34 all_results.push(info);
35 }
36 }
37 }
38
39 Ok(all_results)
40 }
41
42 async fn query_gc_from_call_graph(&self, symbol: &str) -> Result<Vec<GcInfo>> {
43 let query = CallGraphQuery {
44 calls_from: Some(symbol.to_string()),
45 calls_to: None,
46 calls_between: None,
47 depth: 1,
48 };
49
50 let result = self.search_call_graph(&query).await?;
51
52 let mut results = Vec::new();
53 let mut seen: HashSet<(String, bool, Option<String>)> = HashSet::new();
54
55 if let Some(jumprefs) = result.get("jumprefs").and_then(|v| v.as_object()) {
56 for (mangled, info) in jumprefs {
57 let pretty = match info.get("pretty").and_then(|v| v.as_str()) {
58 Some(p) => p,
59 None => continue,
60 };
61
62 if !symbol_matches(pretty, symbol) {
63 continue;
64 }
65
66 let meta = match info.get("meta") {
67 Some(m) => m,
68 None => continue,
69 };
70
71 let can_gc = match meta.get("canGC").and_then(|v| v.as_bool()) {
72 Some(v) => v,
73 None => continue,
74 };
75
76 let gc_path = meta
77 .get("gcPath")
78 .and_then(|v| v.as_str())
79 .map(String::from);
80
81 let key = (pretty.to_string(), can_gc, gc_path.clone());
82 if seen.insert(key) {
83 results.push(GcInfo {
84 pretty: pretty.to_string(),
85 mangled: mangled.clone(),
86 can_gc,
87 gc_path,
88 });
89 }
90 }
91 }
92
93 Ok(results)
94 }
95
96 async fn resolve_full_names(&self, symbol: &str) -> Result<Vec<String>> {
97 let query = format!("id:{symbol}");
98 let mut url = Url::parse(&format!("https://searchfox.org/{}/search", self.repo))?;
99 url.query_pairs_mut().append_pair("q", &query);
100
101 let response = self.get(url).await?;
102 if !response.status().is_success() {
103 return Ok(vec![]);
104 }
105
106 let text = response.text().await?;
107 let json: serde_json::Value = match serde_json::from_str(&text) {
108 Ok(v) => v,
109 Err(_) => return Ok(vec![]),
110 };
111
112 let top = match json.as_object() {
113 Some(o) => o,
114 None => return Ok(vec![]),
115 };
116
117 let symbol_suffix = if let Some(pos) = symbol.rfind("::") {
118 &symbol[pos + 2..]
119 } else {
120 symbol
121 };
122
123 let mut names = Vec::new();
124
125 for (top_key, top_val) in top {
128 if top_key.starts_with('*') {
129 continue;
130 }
131 let categories = match top_val.as_object() {
132 Some(o) => o,
133 None => continue,
134 };
135 for cat_key in categories.keys() {
136 if (cat_key.contains("Definitions") || cat_key.contains("Declarations"))
137 && cat_key.ends_with(')')
138 {
139 if let Some(open) = cat_key.rfind('(') {
140 let name = &cat_key[open + 1..cat_key.len() - 1];
141 if name == symbol
142 || name.ends_with(&format!("::{symbol}"))
143 || name.ends_with(&format!("::{symbol_suffix}"))
144 {
145 names.push(name.to_string());
146 }
147 }
148 }
149 }
150 }
151
152 names.sort();
153 names.dedup();
154 Ok(names)
155 }
156}
157
158fn symbol_matches(pretty: &str, query: &str) -> bool {
159 pretty == query
160 || pretty.ends_with(&format!("::{query}"))
161 || pretty.starts_with(&format!("{query}("))
162 || pretty.starts_with(&format!("{query}<"))
163 || pretty.contains(&format!("::{query}("))
164 || pretty.contains(&format!("::{query}<"))
165}