1use std::collections::{HashMap, hash_map::Entry};
2
3use flux_common::dbg::{self, SpanTrace};
4use flux_syntax::surface::{self, DetachedItem, ExprPath, NodeId};
5use itertools::Itertools;
6use rustc_errors::ErrorGuaranteed;
7use rustc_hir::{
8 OwnerId,
9 def::{DefKind, Res},
10 def_id::LocalDefId,
11};
12use rustc_middle::ty::{AssocItem, AssocKind, Ty, TyCtxt};
13use rustc_span::{Symbol, def_id::DefId};
14
15use crate::collector::{FluxAttrs, SpecCollector, errors};
16type Result<T = ()> = std::result::Result<T, ErrorGuaranteed>;
17
18#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
19enum LookupRes {
20 DefId(DefId),
21 Name(Symbol),
22}
23
24impl LookupRes {
25 fn from_name<T: std::fmt::Debug>(thing: &T) -> Self {
26 let str = format!("{thing:?}");
27 LookupRes::Name(Symbol::intern(&str))
28 }
29
30 fn new(ty: &Ty) -> Self {
31 match ty.kind() {
32 rustc_middle::ty::TyKind::Adt(adt_def, _) => LookupRes::DefId(adt_def.did()),
33 _ => Self::from_name(ty),
34 }
35 }
36}
37
38#[derive(PartialEq, Eq, Debug, Hash)]
39struct TraitImplKey {
40 trait_: LookupRes,
41 self_ty: LookupRes,
42}
43
44fn path_to_symbol(path: &surface::ExprPath) -> Symbol {
45 let path_string = format!(
46 "{}",
47 path.segments
48 .iter()
49 .format_with("::", |s, f| f(&s.ident.name))
50 );
51 Symbol::intern(&path_string)
52}
53
54fn item_def_kind(kind: &surface::DetachedItemKind) -> Vec<DefKind> {
55 match kind {
56 surface::DetachedItemKind::FnSig(_) => vec![DefKind::Fn],
57 surface::DetachedItemKind::Mod(_) => vec![DefKind::Mod],
58 surface::DetachedItemKind::Struct(_) => vec![DefKind::Struct],
59 surface::DetachedItemKind::Enum(_) => vec![DefKind::Enum],
60 surface::DetachedItemKind::InherentImpl(_) | surface::DetachedItemKind::TraitImpl(_) => {
61 vec![DefKind::Struct, DefKind::Enum]
62 }
63 surface::DetachedItemKind::Trait(_) => vec![DefKind::Trait],
64 }
65}
66
67#[derive(Debug)]
68struct ScopeResolver {
69 items: HashMap<(Symbol, DefKind), LookupRes>,
70}
71
72impl ScopeResolver {
73 fn new(tcx: TyCtxt, def_id: LocalDefId, impl_resolver: &TraitImplResolver) -> Self {
74 let mut items = HashMap::default();
75 for child in tcx.module_children_local(def_id) {
76 let ident = child.ident;
77 if let Res::Def(exp_kind, def_id) = child.res {
78 items.insert((ident.name, exp_kind), LookupRes::DefId(def_id));
79 }
80 }
81 for pty in rustc_hir::PrimTy::ALL {
82 let name = pty.name();
83 items.insert((name, DefKind::Struct), LookupRes::Name(name)); }
85 for trait_impl_key in impl_resolver.items.keys() {
86 if let LookupRes::DefId(trait_id) = trait_impl_key.trait_ {
87 let name = Symbol::intern(&tcx.def_path_str(trait_id));
88 items.insert((name, DefKind::Trait), trait_impl_key.trait_);
89 }
90 }
91 Self { items }
92 }
93
94 fn lookup(&self, path: &ExprPath, item_kind: &surface::DetachedItemKind) -> Option<LookupRes> {
95 let symbol = path_to_symbol(path);
96 for kind in item_def_kind(item_kind) {
97 let key = (symbol, kind);
98 if let Some(res) = self.items.get(&key) {
99 return Some(*res);
100 }
101 }
102 None
103 }
104}
105
106#[derive(Debug)]
107struct TraitImplResolver {
108 items: HashMap<TraitImplKey, LocalDefId>,
109}
110
111impl TraitImplResolver {
112 fn new(tcx: TyCtxt) -> Self {
113 let mut items = HashMap::default();
114 for (trait_id, impl_ids) in tcx.all_local_trait_impls(()) {
115 let trait_ = LookupRes::DefId(*trait_id);
116 for impl_id in impl_ids {
117 if let Some(poly_trait_ref) = tcx.impl_trait_ref(*impl_id) {
118 let self_ty = poly_trait_ref.instantiate_identity().self_ty();
119 let self_ty = LookupRes::new(&self_ty);
120 let key = TraitImplKey { trait_, self_ty };
121 items.insert(key, *impl_id);
122 }
123 }
124 }
125 Self { items }
126 }
127
128 fn resolve(&self, trait_: LookupRes, self_ty: LookupRes) -> Option<LocalDefId> {
129 let key = TraitImplKey { trait_, self_ty };
130 self.items.get(&key).copied()
131 }
132}
133
134pub(super) struct DetachedSpecsCollector<'a, 'sess, 'tcx> {
135 inner: &'a mut SpecCollector<'sess, 'tcx>,
136 id_resolver: HashMap<NodeId, LookupRes>,
137 impl_resolver: TraitImplResolver,
138}
139
140impl<'a, 'sess, 'tcx> DetachedSpecsCollector<'a, 'sess, 'tcx> {
141 pub(super) fn collect(
142 inner: &'a mut SpecCollector<'sess, 'tcx>,
143 attrs: &mut FluxAttrs,
144 module_id: LocalDefId,
145 ) -> Result {
146 if let Some(detached_specs) = attrs.detached_specs() {
147 let trait_impl_resolver = TraitImplResolver::new(inner.tcx);
148 let mut collector =
149 Self { inner, id_resolver: HashMap::default(), impl_resolver: trait_impl_resolver };
150 collector.run(detached_specs, module_id)?;
151 };
152 Ok(())
153 }
154
155 fn run(&mut self, detached_specs: surface::DetachedSpecs, def_id: LocalDefId) -> Result {
156 self.resolve(&detached_specs, def_id)?;
157 for item in detached_specs.items {
158 self.attach(item)?;
159 }
160 Ok(())
161 }
162
163 fn resolve_path_kind(
164 &mut self,
165 resolver: &ScopeResolver,
166 path: &ExprPath,
167 kind: &surface::DetachedItemKind,
168 ) -> Result {
169 let Some(res) = resolver.lookup(path, kind) else {
170 return Err(self
171 .inner
172 .errors
173 .emit(errors::UnresolvedSpecification::new(path, "name")));
174 };
175 self.id_resolver.insert(path.node_id, res);
176 Ok(())
177 }
178
179 fn resolve(&mut self, detached_specs: &surface::DetachedSpecs, def_id: LocalDefId) -> Result {
180 let resolver = ScopeResolver::new(self.inner.tcx, def_id, &self.impl_resolver);
181 for item in &detached_specs.items {
182 self.resolve_path_kind(&resolver, &item.path, &item.kind)?;
183 if let surface::DetachedItemKind::TraitImpl(trait_impl) = &item.kind {
184 let kind = surface::DetachedItemKind::Trait(surface::DetachedTrait::default());
185 self.resolve_path_kind(&resolver, &trait_impl.trait_, &kind)?;
186 }
187 }
188 Ok(())
189 }
190
191 #[allow(
192 clippy::disallowed_methods,
193 reason = "this is pre-extern specs so it's fine: https://flux-rs.zulipchat.com/#narrow/channel/486369-verify-std/topic/detached-specs/near/529548357"
194 )]
195 fn unwrap_def_id(&self, def_id: &DefId) -> Result<Option<LocalDefId>> {
196 Ok(def_id.as_local())
197 }
198
199 fn lookup(&mut self, item: &surface::DetachedItem) -> Result<LocalDefId> {
200 let path_def_id = self.id_resolver.get(&item.path.node_id);
201
202 if let surface::DetachedItemKind::TraitImpl(trait_impl) = &item.kind
203 && let Some(trait_) = self.id_resolver.get(&trait_impl.trait_.node_id)
204 && let Some(self_ty) = path_def_id
205 && let Some(impl_id) = self.impl_resolver.resolve(*trait_, *self_ty)
206 {
207 return Ok(impl_id);
208 }
209 if let Some(LookupRes::DefId(def_id)) = self.id_resolver.get(&item.path.node_id)
210 && let Some(local_def_id) = self.unwrap_def_id(def_id)?
211 {
212 return Ok(local_def_id);
213 }
214 Err(self
215 .inner
216 .errors
217 .emit(errors::UnresolvedSpecification::new(&item.path, "item")))
218 }
219
220 fn attach(&mut self, item: surface::DetachedItem) -> Result {
221 let def_id = self.lookup(&item)?;
222 let owner_id = self.inner.tcx.local_def_id_to_hir_id(def_id).owner;
223 let span = item.span();
224 let dst_span = self.inner.tcx.def_span(def_id);
225 dbg::hyperlink!(self.inner.tcx, span, dst_span);
226 match item.kind {
227 surface::DetachedItemKind::FnSig(fn_sig) => {
228 self.inner.insert_item(
229 owner_id,
230 surface::Item {
231 attrs: item.attrs,
232 kind: surface::ItemKind::Fn(Some(fn_sig)),
233 node_id: item.node_id,
234 },
235 )?;
236 }
237 surface::DetachedItemKind::Struct(struct_def) => {
238 self.inner.insert_item(
239 owner_id,
240 surface::Item {
241 attrs: item.attrs,
242 kind: surface::ItemKind::Struct(struct_def),
243 node_id: item.node_id,
244 },
245 )?;
246 }
247 surface::DetachedItemKind::Enum(enum_def) => {
248 self.inner.insert_item(
249 owner_id,
250 surface::Item {
251 attrs: item.attrs,
252 kind: surface::ItemKind::Enum(enum_def),
253 node_id: item.node_id,
254 },
255 )?;
256 }
257 surface::DetachedItemKind::Mod(detached_specs) => {
258 self.run(detached_specs, owner_id.def_id)?;
259 }
260 surface::DetachedItemKind::Trait(trait_def) => {
261 self.collect_trait(owner_id, item.node_id, item.attrs, trait_def)?;
262 }
263 surface::DetachedItemKind::InherentImpl(inherent_impl) => {
264 let tcx = self.inner.tcx;
265 let assoc_items = tcx
266 .inherent_impls(def_id)
267 .iter()
268 .flat_map(|impl_id| tcx.associated_items(impl_id).in_definition_order());
269 self.collect_assoc_methods(
270 inherent_impl.items,
271 assoc_items,
272 |this, owner_id, item| {
273 this.inner.insert_impl_item(
274 owner_id,
275 surface::ImplItemFn {
276 attrs: item.attrs,
277 sig: Some(item.kind),
278 node_id: item.node_id,
279 },
280 )
281 },
282 )?;
283 }
284 surface::DetachedItemKind::TraitImpl(trait_impl) => {
285 self.collect_trait_impl(owner_id, item.node_id, item.attrs, trait_impl)?;
286 }
287 };
288 Ok(())
289 }
290
291 fn collect_trait(
292 &mut self,
293 owner_id: OwnerId,
294 node_id: NodeId,
295 attrs: Vec<surface::Attr>,
296 trait_def: surface::DetachedTrait,
297 ) -> Result {
298 self.inner.insert_item(
300 owner_id,
301 surface::Item {
302 attrs,
303 kind: surface::ItemKind::Trait(surface::Trait {
304 generics: None,
305 assoc_refinements: trait_def.refts,
306 }),
307 node_id,
308 },
309 )?;
310
311 let tcx = self.inner.tcx;
313 let assoc_items = tcx.associated_items(owner_id.def_id).in_definition_order();
314 self.collect_assoc_methods(trait_def.items, assoc_items, |this, owner_id, item| {
315 this.inner.insert_trait_item(
316 owner_id,
317 surface::TraitItemFn {
318 attrs: item.attrs,
319 sig: Some(item.kind),
320 node_id: item.node_id,
321 },
322 )
323 })
324 }
325
326 fn collect_trait_impl(
327 &mut self,
328 owner_id: OwnerId,
329 node_id: NodeId,
330 attrs: Vec<surface::Attr>,
331 trait_impl: surface::DetachedTraitImpl,
332 ) -> Result {
333 self.inner.insert_item(
335 owner_id,
336 surface::Item {
337 attrs,
338 kind: surface::ItemKind::Impl(surface::Impl {
339 generics: None,
340 assoc_refinements: trait_impl.refts,
341 }),
342 node_id,
343 },
344 )?;
345
346 let tcx = self.inner.tcx;
348 let assoc_items = tcx.associated_items(owner_id.def_id).in_definition_order();
349 self.collect_assoc_methods(trait_impl.items, assoc_items, |this, owner_id, item| {
350 this.inner.insert_impl_item(
351 owner_id,
352 surface::ImplItemFn {
353 attrs: item.attrs,
354 sig: Some(item.kind),
355 node_id: item.node_id,
356 },
357 )
358 })
359 }
360
361 fn collect_assoc_methods(
362 &mut self,
363 methods: Vec<DetachedItem<surface::FnSig>>,
364 assoc_items: impl Iterator<Item = &'tcx AssocItem>,
365 mut insert_item: impl FnMut(&mut Self, OwnerId, DetachedItem<surface::FnSig>) -> Result,
366 ) -> Result {
367 let mut table: HashMap<Symbol, DetachedItem<(surface::FnSig, Option<DefId>)>> =
368 HashMap::default();
369 for item in methods {
371 let name = path_to_symbol(&item.path);
372 let span = item.path.span;
373 if let Entry::Occupied(_) = table.entry(name) {
374 return Err(self
375 .inner
376 .errors
377 .emit(errors::MultipleSpecifications { name, span }));
378 } else {
379 table.insert(name, item.map_kind(|spec| (spec, None)));
380 }
381 }
382 for item in assoc_items {
384 if let AssocKind::Fn { name, .. } = item.kind
385 && let Some(val) = table.get_mut(&name)
386 && val.kind.1.is_none()
387 {
388 val.kind.1 = Some(item.def_id);
389 }
390 }
391 for (_name, item) in table {
393 let Some(def_id) = item.kind.1 else {
394 return Err(self
395 .inner
396 .errors
397 .emit(errors::UnresolvedSpecification::new(&item.path, "identifier")));
398 };
399 if let Some(def_id) = self.unwrap_def_id(&def_id)? {
400 dbg::hyperlink!(self.inner.tcx, item.path.span, self.inner.tcx.def_span(def_id));
401 let owner_id = self.inner.tcx.local_def_id_to_hir_id(def_id).owner;
402 insert_item(self, owner_id, item.map_kind(|k| k.0))?;
403 }
404 }
405 Ok(())
406 }
407}