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 surface::DetachedItemKind::Static(_) => {
65 vec![DefKind::Static {
66 mutability: rustc_ast::Mutability::Not,
67 nested: false,
68 safety: rustc_hir::Safety::Safe,
69 }]
70 }
71 }
72}
73
74#[derive(Debug)]
75struct ScopeResolver {
76 items: HashMap<(Symbol, DefKind), LookupRes>,
77}
78
79impl ScopeResolver {
80 fn new(tcx: TyCtxt, def_id: LocalDefId, impl_resolver: &TraitImplResolver) -> Self {
81 let mut items = HashMap::default();
82 for child in tcx.module_children_local(def_id) {
83 let ident = child.ident;
84 if let Res::Def(exp_kind, def_id) = child.res {
85 items.insert((ident.name, exp_kind), LookupRes::DefId(def_id));
86 }
87 }
88 for pty in rustc_hir::PrimTy::ALL {
89 let name = pty.name();
90 items.insert((name, DefKind::Struct), LookupRes::Name(name)); }
92 for trait_impl_key in impl_resolver.items.keys() {
93 if let LookupRes::DefId(trait_id) = trait_impl_key.trait_ {
94 let name = Symbol::intern(&tcx.def_path_str(trait_id));
95 items.insert((name, DefKind::Trait), trait_impl_key.trait_);
96 }
97 }
98 Self { items }
99 }
100
101 fn lookup(&self, path: &ExprPath, item_kind: &surface::DetachedItemKind) -> Option<LookupRes> {
102 let symbol = path_to_symbol(path);
103 for kind in item_def_kind(item_kind) {
104 let key = (symbol, kind);
105 if let Some(res) = self.items.get(&key) {
106 return Some(*res);
107 }
108 }
109 None
110 }
111}
112
113#[derive(Debug)]
114struct TraitImplResolver {
115 items: HashMap<TraitImplKey, LocalDefId>,
116}
117
118impl TraitImplResolver {
119 fn new(tcx: TyCtxt) -> Self {
120 let mut items = HashMap::default();
121 for (trait_id, impl_ids) in tcx.all_local_trait_impls(()) {
122 let trait_ = LookupRes::DefId(*trait_id);
123 for impl_id in impl_ids {
124 let poly_trait_ref = tcx.impl_trait_ref(*impl_id);
125 let self_ty = poly_trait_ref
126 .instantiate_identity()
127 .skip_norm_wip()
128 .self_ty();
129 let self_ty = LookupRes::new(&self_ty);
130 let key = TraitImplKey { trait_, self_ty };
131 items.insert(key, *impl_id);
132 }
133 }
134 Self { items }
135 }
136
137 fn resolve(&self, trait_: LookupRes, self_ty: LookupRes) -> Option<LocalDefId> {
138 let key = TraitImplKey { trait_, self_ty };
139 self.items.get(&key).copied()
140 }
141}
142
143pub(super) struct DetachedSpecsCollector<'a, 'sess, 'tcx> {
144 inner: &'a mut SpecCollector<'sess, 'tcx>,
145 id_resolver: HashMap<NodeId, LookupRes>,
146 impl_resolver: TraitImplResolver,
147}
148
149impl<'a, 'sess, 'tcx> DetachedSpecsCollector<'a, 'sess, 'tcx> {
150 pub(super) fn collect(
151 inner: &'a mut SpecCollector<'sess, 'tcx>,
152 attrs: &mut FluxAttrs,
153 module_id: LocalDefId,
154 ) -> Result {
155 if let Some(detached_specs) = attrs.detached_specs() {
156 let trait_impl_resolver = TraitImplResolver::new(inner.tcx);
157 let mut collector =
158 Self { inner, id_resolver: HashMap::default(), impl_resolver: trait_impl_resolver };
159 collector.run(detached_specs, module_id)?;
160 };
161 Ok(())
162 }
163
164 fn run(&mut self, detached_specs: surface::DetachedSpecs, def_id: LocalDefId) -> Result {
165 self.resolve(&detached_specs, def_id)?;
166 for item in detached_specs.items {
167 self.attach(item)?;
168 }
169 Ok(())
170 }
171
172 fn resolve_path_kind(
173 &mut self,
174 resolver: &ScopeResolver,
175 path: &ExprPath,
176 kind: &surface::DetachedItemKind,
177 ) -> Result {
178 let Some(res) = resolver.lookup(path, kind) else {
179 return Err(self
180 .inner
181 .errors
182 .emit(errors::UnresolvedSpecification::new(path, "name")));
183 };
184 self.id_resolver.insert(path.node_id, res);
185 Ok(())
186 }
187
188 fn resolve(&mut self, detached_specs: &surface::DetachedSpecs, def_id: LocalDefId) -> Result {
189 let resolver = ScopeResolver::new(self.inner.tcx, def_id, &self.impl_resolver);
190 for item in &detached_specs.items {
191 self.resolve_path_kind(&resolver, &item.path, &item.kind)?;
192 if let surface::DetachedItemKind::TraitImpl(trait_impl) = &item.kind {
193 let kind = surface::DetachedItemKind::Trait(surface::DetachedTrait::default());
194 self.resolve_path_kind(&resolver, &trait_impl.trait_, &kind)?;
195 }
196 }
197 Ok(())
198 }
199
200 #[allow(
201 clippy::disallowed_methods,
202 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"
203 )]
204 fn unwrap_def_id(&self, def_id: &DefId) -> Result<Option<LocalDefId>> {
205 Ok(def_id.as_local())
206 }
207
208 fn lookup(&mut self, item: &surface::DetachedItem) -> Result<LocalDefId> {
209 let path_def_id = self.id_resolver.get(&item.path.node_id);
210
211 if let surface::DetachedItemKind::TraitImpl(trait_impl) = &item.kind
212 && let Some(trait_) = self.id_resolver.get(&trait_impl.trait_.node_id)
213 && let Some(self_ty) = path_def_id
214 && let Some(impl_id) = self.impl_resolver.resolve(*trait_, *self_ty)
215 {
216 return Ok(impl_id);
217 }
218 if let Some(LookupRes::DefId(def_id)) = self.id_resolver.get(&item.path.node_id)
219 && let Some(local_def_id) = self.unwrap_def_id(def_id)?
220 {
221 return Ok(local_def_id);
222 }
223 Err(self
224 .inner
225 .errors
226 .emit(errors::UnresolvedSpecification::new(&item.path, "item")))
227 }
228
229 fn attach(&mut self, item: surface::DetachedItem) -> Result {
230 let def_id = self.lookup(&item)?;
231 let owner_id = self.inner.tcx.local_def_id_to_hir_id(def_id).owner;
232 let span = item.span();
233 let dst_span = self.inner.tcx.def_span(def_id);
234 dbg::hyperlink!(self.inner.tcx, span, dst_span);
235 match item.kind {
236 surface::DetachedItemKind::FnSig(fn_sig) => {
237 self.inner.insert_item(
238 owner_id,
239 surface::Item {
240 attrs: item.attrs,
241 kind: surface::ItemKind::Fn(Some(fn_sig)),
242 node_id: item.node_id,
243 },
244 )?;
245 }
246 surface::DetachedItemKind::Struct(struct_def) => {
247 self.inner.insert_item(
248 owner_id,
249 surface::Item {
250 attrs: item.attrs,
251 kind: surface::ItemKind::Struct(struct_def),
252 node_id: item.node_id,
253 },
254 )?;
255 }
256 surface::DetachedItemKind::Enum(enum_def) => {
257 self.inner.insert_item(
258 owner_id,
259 surface::Item {
260 attrs: item.attrs,
261 kind: surface::ItemKind::Enum(enum_def),
262 node_id: item.node_id,
263 },
264 )?;
265 }
266 surface::DetachedItemKind::Mod(detached_specs) => {
267 self.run(detached_specs, owner_id.def_id)?;
268 }
269 surface::DetachedItemKind::Trait(trait_def) => {
270 self.collect_trait(owner_id, item.node_id, item.attrs, trait_def)?;
271 }
272 surface::DetachedItemKind::InherentImpl(inherent_impl) => {
273 let tcx = self.inner.tcx;
274 let assoc_items = tcx
275 .inherent_impls(def_id)
276 .iter()
277 .flat_map(|impl_id| tcx.associated_items(*impl_id).in_definition_order());
278 self.collect_assoc_methods(
279 inherent_impl.items,
280 assoc_items,
281 |this, owner_id, item| {
282 this.inner.insert_impl_item(
283 owner_id,
284 surface::ImplItemFn {
285 attrs: item.attrs,
286 sig: Some(item.kind),
287 node_id: item.node_id,
288 },
289 )
290 },
291 )?;
292 }
293 surface::DetachedItemKind::TraitImpl(trait_impl) => {
294 self.collect_trait_impl(owner_id, item.node_id, item.attrs, trait_impl)?;
295 }
296 surface::DetachedItemKind::Static(static_info) => {
297 self.inner.insert_item(
298 owner_id,
299 surface::Item {
300 attrs: item.attrs,
301 kind: surface::ItemKind::Static(static_info),
302 node_id: item.node_id,
303 },
304 )?;
305 }
306 };
307 Ok(())
308 }
309
310 fn collect_trait(
311 &mut self,
312 owner_id: OwnerId,
313 node_id: NodeId,
314 attrs: Vec<surface::Attr>,
315 trait_def: surface::DetachedTrait,
316 ) -> Result {
317 self.inner.insert_item(
319 owner_id,
320 surface::Item {
321 attrs,
322 kind: surface::ItemKind::Trait(surface::Trait {
323 generics: None,
324 assoc_refinements: trait_def.refts,
325 }),
326 node_id,
327 },
328 )?;
329
330 let tcx = self.inner.tcx;
332 let assoc_items = tcx.associated_items(owner_id.def_id).in_definition_order();
333 self.collect_assoc_methods(trait_def.items, assoc_items, |this, owner_id, item| {
334 this.inner.insert_trait_item(
335 owner_id,
336 surface::TraitItemFn {
337 attrs: item.attrs,
338 sig: Some(item.kind),
339 node_id: item.node_id,
340 },
341 )
342 })
343 }
344
345 fn collect_trait_impl(
346 &mut self,
347 owner_id: OwnerId,
348 node_id: NodeId,
349 attrs: Vec<surface::Attr>,
350 trait_impl: surface::DetachedTraitImpl,
351 ) -> Result {
352 self.inner.insert_item(
354 owner_id,
355 surface::Item {
356 attrs,
357 kind: surface::ItemKind::Impl(surface::Impl {
358 generics: None,
359 assoc_refinements: trait_impl.refts,
360 }),
361 node_id,
362 },
363 )?;
364
365 let tcx = self.inner.tcx;
367 let assoc_items = tcx.associated_items(owner_id.def_id).in_definition_order();
368 self.collect_assoc_methods(trait_impl.items, assoc_items, |this, owner_id, item| {
369 this.inner.insert_impl_item(
370 owner_id,
371 surface::ImplItemFn {
372 attrs: item.attrs,
373 sig: Some(item.kind),
374 node_id: item.node_id,
375 },
376 )
377 })
378 }
379
380 fn collect_assoc_methods(
381 &mut self,
382 methods: Vec<DetachedItem<surface::FnSig>>,
383 assoc_items: impl Iterator<Item = &'tcx AssocItem>,
384 mut insert_item: impl FnMut(&mut Self, OwnerId, DetachedItem<surface::FnSig>) -> Result,
385 ) -> Result {
386 let mut table: HashMap<Symbol, DetachedItem<(surface::FnSig, Option<DefId>)>> =
387 HashMap::default();
388 for item in methods {
390 let name = path_to_symbol(&item.path);
391 let span = item.path.span;
392 if let Entry::Occupied(_) = table.entry(name) {
393 return Err(self
394 .inner
395 .errors
396 .emit(errors::MultipleSpecifications { name, span }));
397 } else {
398 table.insert(name, item.map_kind(|spec| (spec, None)));
399 }
400 }
401 for item in assoc_items {
403 if let AssocKind::Fn { name, .. } = item.kind
404 && let Some(val) = table.get_mut(&name)
405 && val.kind.1.is_none()
406 {
407 val.kind.1 = Some(item.def_id);
408 }
409 }
410 for (_name, item) in table {
412 let Some(def_id) = item.kind.1 else {
413 return Err(self
414 .inner
415 .errors
416 .emit(errors::UnresolvedSpecification::new(&item.path, "identifier")));
417 };
418 if let Some(def_id) = self.unwrap_def_id(&def_id)? {
419 dbg::hyperlink!(self.inner.tcx, item.path.span, self.inner.tcx.def_span(def_id));
420 let owner_id = self.inner.tcx.local_def_id_to_hir_id(def_id).owner;
421 insert_item(self, owner_id, item.map_kind(|k| k.0))?;
422 }
423 }
424 Ok(())
425 }
426}