1use std::collections::HashMap;
4
5use hax_lib_macros_types::{AssociationRole, AttrPayload, ItemUid};
6
7use crate::ast::diagnostics::{Context, DiagnosticInfo, DiagnosticInfoKind};
8
9use super::ast::*;
10use visitors::AstVisitorMut;
11
12#[derive(Clone)]
14pub struct LinkedItemGraph {
15 items: HashMap<ItemUid, Item>,
16 context: Context,
17}
18
19impl Default for LinkedItemGraph {
20 fn default() -> Self {
21 Self {
22 items: Default::default(),
23 context: Context::Unknown,
24 }
25 }
26}
27
28pub fn hax_attributes(attrs: &Attributes) -> impl Iterator<Item = &AttrPayload> {
30 attrs.iter().flat_map(|attr| match &attr.kind {
31 AttributeKind::Hax(attr_payload) => Some(attr_payload),
32 _ => None,
33 })
34}
35
36fn uuid(context: Context, item: &Item) -> Option<ItemUid> {
37 let mut uuids = hax_attributes(&item.meta.attributes).flat_map(|attr| match attr {
38 AttrPayload::Uid(item_uid) => Some(item_uid),
39 _ => None,
40 });
41 let uuid = uuids.next()?;
42 if let Some(other) = uuids.next() {
43 emit_assertion_failure(
44 context,
45 item.span(),
46 format!(
47 "Found more than one UUID hax attribute on this item. The two first UUIDs are {uuid} and {other}."
48 ),
49 );
50 None
51 } else {
52 Some(uuid.clone())
53 }
54}
55
56fn emit_assertion_failure(context: Context, span: span::Span, message: impl Into<String>) {
57 DiagnosticInfo {
58 context,
59 span,
60 kind: DiagnosticInfoKind::AssertionFailure {
61 details: message.into(),
62 },
63 }
64 .emit();
65}
66
67impl std::fmt::Debug for LinkedItemGraph {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("LinkedItemGraph")
70 .field(
71 "items",
72 &self
73 .items
74 .iter()
75 .map(|(id, item)| (id.to_string(), item.ident.to_debug_string()))
76 .collect::<Vec<_>>(),
77 )
78 .field("context", &self.context)
79 .finish()
80 }
81}
82
83impl LinkedItemGraph {
84 pub fn new(items: &[Item], context: Context) -> Self {
87 Self {
88 items: HashMap::from_iter(
89 items
90 .iter()
91 .filter_map(|item| Some((uuid(context.clone(), item)?, item.clone()))),
92 ),
93 context,
94 }
95 }
96
97 fn emit_assertion_failure(&self, span: span::Span, message: impl Into<String>) {
98 emit_assertion_failure(self.context.clone(), span, message)
99 }
100
101 fn emit_unimplemented(&self, span: span::Span, issue_id: u32, message: impl Into<String>) {
102 DiagnosticInfo {
103 context: self.context.clone(),
104 span,
105 kind: DiagnosticInfoKind::Unimplemented {
106 issue_id: Some(issue_id),
107 details: Some(message.into()),
108 },
109 }
110 .emit();
111 }
112
113 pub fn linked_items_iter(
115 &self,
116 item: &impl HasMetadata,
117 ) -> impl Iterator<Item = (AssociationRole, Result<&Item, DiagnosticInfo>)> {
118 let item_attributes = &item.metadata().attributes;
119 hax_attributes(item_attributes).flat_map(move |attr| match attr {
120 AttrPayload::AssociatedItem { role, item: target } => {
121 let target = self.items.get(target).map(Ok).unwrap_or_else(|| {
122 Err(DiagnosticInfo {
123 context: self.context.clone(),
124 span: item.span(),
125 kind: DiagnosticInfoKind::AssertionFailure {
126 details: format!("An item linked via hax attributes could not be found. The UUID is {target:?}. The graph is {:#?}.", self),
127 },
128 })
129 });
130 Some((*role, target))
131 }
132 _ => None,
133 })
134 }
135
136 pub fn linked_items(
138 &self,
139 item: &impl HasMetadata,
140 ) -> HashMap<AssociationRole, Vec<Result<&Item, DiagnosticInfo>>> {
141 let mut map: HashMap<AssociationRole, Vec<_>> = HashMap::new();
142 for (role, item) in self.linked_items_iter(item) {
143 map.entry(role).or_default().push(item);
144 }
145 map
146 }
147
148 pub fn fn_like_linked_expressions(
151 &self,
152 item: &impl HasMetadata,
153 self_id: Option<identifiers::LocalId>,
154 ) -> FnLikeAssocatedExpressions {
155 let assoc_items = self.linked_items(item);
156 let get = |role| {
157 assoc_items
158 .get(&role)
159 .iter()
160 .flat_map(|vec| vec.iter())
161 .flat_map(|item| match item {
162 Ok(item) => Some(item),
163 Err(err) => {
164 err.emit();
165 None
166 }
167 })
168 .map(|item| extract_expr(&self.context, item, self_id.clone()))
169 .collect::<Vec<_>>()
170 };
171 let precondition = {
172 let mut preconditions = get(AssociationRole::Requires).into_iter();
173 preconditions.next().map(|(e, _)| {
174 for extra in preconditions {
175 self.emit_unimplemented(extra.0.span(), 1270, "multiple pre-conditions");
176 }
177 e
178 })
179 };
180 let decreases = {
181 let mut decreases = get(AssociationRole::Decreases).into_iter();
182 decreases.next().map(|(e, _)| {
183 for extra in decreases {
184 self.emit_unimplemented(extra.0.span(), 1270, "multiple decreases");
185 }
186 e
187 })
188 };
189 let postcondition = {
190 let mut postconditions = get(AssociationRole::Ensures).into_iter();
191 postconditions.next().and_then(|(e, params)| {
192 for extra in postconditions {
193 self.emit_unimplemented(extra.0.span(), 1270, "multiple post-conditions");
194 }
195 if let Some(last_param) = params.last() {
196 Some(Postcondition {
197 result_binder: last_param.pat.clone(),
198 body: e.clone(),
199 })
200 } else {
201 self.emit_assertion_failure(
202 e.span(),
203 "hax ensures attribute: could not find output binder",
204 );
205 None
206 }
207 })
208 };
209 FnLikeAssocatedExpressions {
210 decreases,
211 precondition,
212 postcondition,
213 }
214 }
215}
216
217fn extract_expr<'a>(
218 context: &Context,
219 item: &'a Item,
220 self_id: Option<identifiers::LocalId>,
221) -> (Expr, Vec<&'a Param>) {
222 let ItemKind::Fn { body, params, .. } = item.kind() else {
223 return (
224 ExprKind::Error(ErrorNode::assertion_failure(
225 item.clone(),
226 context.clone(),
227 "Expected an function",
228 ))
229 .into_expr(item.span(), Ty::prop(), vec![]),
230 vec![],
231 );
232 };
233 let mut body = body.clone();
234 if let Some(self_id) = self_id
235 && let [maybe_self, ..] = params.as_slice()
236 && let PatKind::Binding {
237 var, sub_pat: None, ..
238 } = &*maybe_self.pat.kind
239 {
240 utils::mappers::SubstLocalIds::one(var.clone(), self_id.clone()).visit(&mut body)
242 }
243 (body, params.iter().collect())
244}
245
246pub struct Postcondition {
257 pub result_binder: Pat,
259 pub body: Expr,
261}
262
263pub struct FnLikeAssocatedExpressions {
265 pub decreases: Option<Expr>,
267 pub precondition: Option<Expr>,
269 pub postcondition: Option<Postcondition>,
271}