1use std::collections::{HashMap, HashSet};
24use std::sync::{OnceLock, RwLock};
25
26use pest::iterators::Pair;
27use pest::Parser;
28use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
29use safety_parser::syn::visit_mut::{self, VisitMut};
30use safety_parser::syn::Expr;
31
32use super::pest_grammar::{ContractParser, Rule};
33use super::types::{Property, PropertyKind};
34
35#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum DefArg {
39 Param(usize),
40 Lit(String),
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum DefBody {
46 And(Vec<DefBody>),
47 Or(Vec<DefBody>),
48 Call { tag: String, args: Vec<DefArg> },
49}
50
51#[derive(Debug, Clone)]
53pub struct DefSpec {
54 pub name: String,
55 pub params: Vec<String>,
56 pub param_tys: Vec<String>,
57 pub body: DefBody,
58 pub doc: Vec<String>,
59}
60
61pub fn parse_defs(source: &str) -> Vec<DefSpec> {
76 let mut defs = Vec::new();
77 let mut doc: Vec<String> = Vec::new();
78
79 let mut s = source.trim_start();
80 loop {
81 loop {
83 if let Some(r) = s.strip_prefix("///") {
84 let end = r.find('\n').unwrap_or(r.len());
85 doc.push(r[..end].trim().to_string());
86 s = r[end..].trim_start();
87 } else if let Some(r) = s.strip_prefix("//") {
88 let end = r.find('\n').unwrap_or(r.len());
89 s = r[end..].trim_start();
90 } else {
91 break;
92 }
93 }
94 if s.is_empty() {
95 break;
96 }
97
98 let (Some(mut def), consumed) = parse_one_def_block(s) else {
99 break;
100 };
101 def.doc = std::mem::take(&mut doc);
102 defs.push(def);
103 s = s[consumed..].trim_start();
104 }
105
106 defs
107}
108
109fn parse_one_def_block(s: &str) -> (Option<DefSpec>, usize) {
119 let Some(open) = s.find('(') else {
120 return (None, 0);
121 };
122 let name = s[..open].trim().to_string();
123 if name.is_empty() {
124 return (None, 0);
125 }
126
127 let Some(rel_close) = s[open + 1..].find(')') else {
130 return (None, 0);
131 };
132 let close = open + 1 + rel_close;
133 let params_str = &s[open + 1..close];
134
135 let after = s[close + 1..].trim_start();
137 if !after.starts_with('{') {
138 return (None, 0);
139 }
140 let brace_open = s.len() - after.len();
141 let body_start = brace_open + 1;
142
143 let bytes = s.as_bytes();
145 let mut depth = 1usize;
146 let mut j = body_start;
147 while j < bytes.len() {
148 match bytes[j] {
149 b'{' => depth += 1,
150 b'}' => {
151 depth -= 1;
152 if depth == 0 {
153 break;
154 }
155 }
156 _ => {}
157 }
158 j += 1;
159 }
160 if depth != 0 {
161 return (None, 0);
162 }
163 let body = s[body_start..j].trim();
164
165 let (params, param_tys) = parse_equation_params(params_str);
166 let Some(body_ast) = parse_body(body, ¶ms) else {
167 return (None, 0);
168 };
169
170 let def = DefSpec {
171 name,
172 params,
173 param_tys,
174 body: body_ast,
175 doc: Vec::new(),
176 };
177 (Some(def), j + 1)
178}
179
180fn parse_equation_params(params_str: &str) -> (Vec<String>, Vec<String>) {
181 let mut params = Vec::new();
182 let mut param_tys = Vec::new();
183 for seg in params_str.split(',') {
184 let seg = seg.trim();
185 if seg.is_empty() {
186 continue;
187 }
188 match seg.split_once(':') {
189 Some((p, ty)) => {
190 params.push(p.trim().to_string());
191 param_tys.push(ty.trim().to_string());
192 }
193 None => {
194 params.push(seg.to_string());
195 param_tys.push(String::new());
196 }
197 }
198 }
199 (params, param_tys)
200}
201
202fn render_expr_src(e: &Expr) -> String {
206 quote::ToTokens::to_token_stream(e)
207 .to_string()
208 .replace(" . ", ".")
209 .replace(" ,", ",")
210 .replace(" (", "(")
211 .replace(" :: ", "::")
212}
213
214fn resolve_arg_string<'tcx>(
218 tcx: rustc_middle::ty::TyCtxt<'tcx>,
219 def_id: rustc_hir::def_id::DefId,
220 param_ty: &str,
221 expr: &Expr,
222) -> String {
223 match param_ty {
224 "Ptr" => super::resolve::parse_target_arg(tcx, def_id, expr)
225 .display_for_report(tcx, None, Some(def_id)),
226 "Ty" => super::resolve::parse_type(tcx, def_id, expr, "def")
227 .map(|ty| ty.to_string())
228 .unwrap_or_else(|| render_expr_src(expr)),
229 "Expr" => {
230 let ce = super::resolve::expr_to_pest(tcx, def_id, expr);
231 super::render::display_expr_user_friendly(&ce, tcx, None, Some(def_id))
232 }
233 _ => render_expr_src(expr),
234 }
235}
236
237fn parse_body(body: &str, params: &[String]) -> Option<DefBody> {
239 let mut pairs = ContractParser::parse(Rule::def_body, body).ok()?;
240 let def_body = pairs.next()?;
241 let or_expr = def_body.into_inner().next()?;
242 Some(conv_def_or(or_expr, params))
243}
244
245fn conv_def_or(pair: Pair<Rule>, params: &[String]) -> DefBody {
246 let parts: Vec<DefBody> = pair.into_inner().map(|p| conv_def_and(p, params)).collect();
247 if parts.len() == 1 {
248 parts.into_iter().next().unwrap()
249 } else {
250 DefBody::Or(parts)
251 }
252}
253
254fn conv_def_and(pair: Pair<Rule>, params: &[String]) -> DefBody {
255 let parts: Vec<DefBody> = pair.into_inner().map(|p| conv_def_leaf(p, params)).collect();
256 if parts.len() == 1 {
257 parts.into_iter().next().unwrap()
258 } else {
259 DefBody::And(parts)
260 }
261}
262
263fn conv_def_leaf(pair: Pair<Rule>, params: &[String]) -> DefBody {
264 match pair.into_inner().next() {
265 Some(inner) => match inner.as_rule() {
266 Rule::tag_call => conv_def_call(inner, params),
267 Rule::or_expr => conv_def_or(inner, params),
268 _ => DefBody::Call {
269 tag: String::new(),
270 args: Vec::new(),
271 },
272 },
273 None => DefBody::Call {
274 tag: String::new(),
275 args: Vec::new(),
276 },
277 }
278}
279
280fn conv_def_call(pair: Pair<Rule>, params: &[String]) -> DefBody {
281 let mut inner = pair.into_inner();
282 let Some(tag) = inner.next() else {
283 return DefBody::Call {
284 tag: String::new(),
285 args: Vec::new(),
286 };
287 };
288 let tag = tag.as_str().to_string();
289 let args = match inner.next() {
290 Some(arg_list) => arg_list
291 .into_inner()
292 .map(|arg| {
293 let text = arg.as_str().trim().to_string();
294 match params.iter().position(|n| n == &text) {
295 Some(i) => DefArg::Param(i),
296 None => DefArg::Lit(text),
297 }
298 })
299 .collect(),
300 None => Vec::new(),
301 };
302 DefBody::Call { tag, args }
303}
304
305struct Subst<'a> {
313 params: &'a [String],
314 args: &'a [Expr],
315}
316
317impl VisitMut for Subst<'_> {
318 fn visit_expr_mut(&mut self, node: &mut Expr) {
319 if let Expr::Path(path) = node {
320 if path.qself.is_none()
321 && path.path.leading_colon.is_none()
322 && path.path.segments.len() == 1
323 {
324 let ident = path.path.segments[0].ident.to_string();
325 if let Some(i) = self.params.iter().position(|n| *n == ident) {
326 if let Some(arg) = self.args.get(i) {
327 *node = arg.clone();
330 return;
331 }
332 }
333 }
334 }
335 visit_mut::visit_expr_mut(self, node);
336 }
337}
338
339fn def_ty_matches_arg_kind(def_ty: &str, kind: super::spec::ArgKind) -> bool {
341 use super::spec::ArgKind;
342 match (def_ty, kind) {
343 ("Ptr", ArgKind::Target) => true,
344 ("Ty", ArgKind::Ty) => true,
345 ("Expr", ArgKind::Expr) => true,
346 ("Ident", ArgKind::Ident) => true,
347 _ => false,
348 }
349}
350
351fn expand_body<'tcx>(
357 tcx: rustc_middle::ty::TyCtxt<'tcx>,
358 def_id: rustc_hir::def_id::DefId,
359 body: &DefBody,
360 exprs: &[Expr],
361 params: &[String],
362 param_tys: &[String],
363) -> Vec<Property<'tcx>> {
364 match body {
365 DefBody::And(parts) => parts
366 .iter()
367 .flat_map(|p| expand_body(tcx, def_id, p, exprs, params, param_tys))
368 .collect(),
369 DefBody::Or(parts) => {
370 let mut groups: Vec<Vec<Box<Property<'tcx>>>> = Vec::new();
371 for part in parts {
372 let group: Vec<Box<Property<'tcx>>> =
373 expand_body(tcx, def_id, part, exprs, params, param_tys)
374 .into_iter()
375 .map(Box::new)
376 .collect();
377 if !group.is_empty() {
378 groups.push(group);
379 }
380 }
381 vec![Property::new_or(groups)]
382 }
383 DefBody::Call { tag, args } => {
384 if let Some(spec) = super::spec::find_spec(tag) {
387 match spec.build {
388 super::spec::BuildKind::Targets => {
390 for (pos, a) in args.iter().enumerate() {
391 if let DefArg::Param(i) = a
392 && let Some(def_ty) = param_tys.get(*i)
393 && !def_ty_matches_arg_kind(
394 def_ty,
395 super::spec::ArgKind::Target,
396 )
397 {
398 let pname = params.get(*i).map(String::as_str).unwrap_or("?");
399 rap_warn!(
400 "contract def type mismatch: `{tag}` arg {pos} expects \
401 {:?}, but param `{pname}` is annotated `{def_ty}`",
402 super::spec::ArgKind::Target
403 );
404 }
405 }
406 }
407 super::spec::BuildKind::TobeSpecified => {}
409 _ => {
412 if let Some(form) = spec.forms.iter().find(|f| f.len() == args.len()) {
413 for (pos, a) in args.iter().enumerate() {
414 if let DefArg::Param(i) = a
415 && let (Some(def_ty), Some(&arg_kind)) =
416 (param_tys.get(*i), form.get(pos))
417 && !def_ty_matches_arg_kind(def_ty, arg_kind)
418 {
419 let pname = params.get(*i).map(String::as_str).unwrap_or("?");
420 rap_warn!(
421 "contract def type mismatch: `{tag}` arg {pos} expects \
422 {:?}, but param `{pname}` is annotated `{def_ty}`",
423 arg_kind
424 );
425 }
426 }
427 }
428 }
429 }
430 }
431
432 let mut resolved: Vec<Expr> = Vec::with_capacity(args.len());
433 for a in args {
434 match a {
435 DefArg::Param(i) => {
436 let Some(e) = exprs.get(*i) else {
437 return vec![unknown_property()];
438 };
439 resolved.push(e.clone());
440 }
441 DefArg::Lit(s) => {
442 let Ok(mut e) = syn::parse_str::<Expr>(s) else {
443 return vec![unknown_property()];
444 };
445 Subst { params, args: exprs }.visit_expr_mut(&mut e);
446 resolved.push(e);
447 }
448 }
449 }
450 Property::parse_list(tcx, def_id, tag, &resolved)
453 }
454 }
455}
456
457fn unknown_property<'tcx>() -> Property<'tcx> {
458 Property::new_leaf(PropertyKind::Unknown, Vec::new())
459}
460
461fn builtin_defs_map() -> &'static HashMap<String, DefSpec> {
467 static BUILTIN: OnceLock<HashMap<String, DefSpec>> = OnceLock::new();
468 BUILTIN.get_or_init(builtin_defs)
469}
470
471fn user_defs_map() -> &'static RwLock<HashMap<CrateNum, HashMap<String, DefSpec>>> {
477 static USER: OnceLock<RwLock<HashMap<CrateNum, HashMap<String, DefSpec>>>> = OnceLock::new();
478 USER.get_or_init(|| RwLock::new(HashMap::new()))
479}
480
481fn builtin_defs() -> HashMap<String, DefSpec> {
484 let mut map = HashMap::new();
485 for def in parse_defs(include_str!("assets/std-contracts.rs")) {
486 map.insert(def.name.clone(), def);
487 }
488 for def in parse_defs(include_str!("assets/user-contracts.rs")) {
489 map.insert(def.name.clone(), def);
490 }
491 map
492}
493
494pub fn find_def(krate: CrateNum, name: &str) -> Option<DefSpec> {
497 if let Some(d) = user_defs_map()
498 .read()
499 .ok()
500 .and_then(|t| t.get(&krate).and_then(|m| m.get(name).cloned()))
501 {
502 return Some(d);
503 }
504 builtin_defs_map().get(name).cloned()
505}
506
507pub fn expand_def<'tcx>(
509 tcx: rustc_middle::ty::TyCtxt<'tcx>,
510 def_id: rustc_hir::def_id::DefId,
511 name: &str,
512 exprs: &[Expr],
513) -> Option<Vec<Property<'tcx>>> {
514 let def = find_def(def_id.krate, name)?;
515 if def.params.len() != exprs.len() {
516 return None;
517 }
518 if let Some(cycle) = find_def_cycle(def_id.krate, name) {
522 rap_error!("contract def cycle detected: {}", cycle.join(" -> "));
523 return None;
524 }
525 let mut props = expand_body(tcx, def_id, &def.body, exprs, &def.params, &def.param_tys);
526 let arg_strings: Vec<String> = exprs
530 .iter()
531 .enumerate()
532 .map(|(i, e)| {
533 let param_ty = def.param_tys.get(i).map(|s| s.as_str()).unwrap_or("");
534 resolve_arg_string(tcx, def_id, param_ty, e)
535 })
536 .collect();
537 let meaning = if def.doc.is_empty() {
538 None
539 } else {
540 Some(def.doc.join(" "))
541 };
542 for p in &mut props {
543 p.set_origin(name.to_string(), arg_strings.clone(), meaning.clone());
544 }
545 Some(props)
546}
547
548pub fn find_def_cycle(krate: CrateNum, start: &str) -> Option<Vec<String>> {
555 let mut combined = builtin_defs_map().clone();
556 if let Ok(user) = user_defs_map().read()
557 && let Some(crate_defs) = user.get(&krate)
558 {
559 for (name, def) in crate_defs {
560 combined.insert(name.clone(), def.clone());
561 }
562 }
563 find_cycle_in(start, &combined)
564}
565
566fn find_cycle_in(start: &str, table: &HashMap<String, DefSpec>) -> Option<Vec<String>> {
567 fn dfs(
568 name: &str,
569 table: &HashMap<String, DefSpec>,
570 path: &mut Vec<String>,
571 done: &mut HashSet<String>,
572 ) -> Option<Vec<String>> {
573 if let Some(pos) = path.iter().position(|n| n == name) {
574 let mut cycle: Vec<String> = path[pos..].to_vec();
575 cycle.push(name.to_string());
576 return Some(cycle);
577 }
578 if done.contains(name) {
579 return None;
580 }
581 let Some(def) = table.get(name) else {
582 return None;
583 };
584 path.push(name.to_string());
585 for tag in def_refs(&def.body) {
586 if let Some(cycle) = dfs(&tag, table, path, done) {
587 return Some(cycle);
588 }
589 }
590 path.pop();
591 done.insert(name.to_string());
592 None
593 }
594
595 let mut path = Vec::new();
596 let mut done = HashSet::new();
597 dfs(start, table, &mut path, &mut done)
598}
599
600fn def_refs(body: &DefBody) -> Vec<String> {
602 let mut out = Vec::new();
603 collect_def_refs(body, &mut out);
604 out
605}
606
607fn collect_def_refs(body: &DefBody, out: &mut Vec<String>) {
608 match body {
609 DefBody::And(parts) | DefBody::Or(parts) => {
610 for part in parts {
611 collect_def_refs(part, out);
612 }
613 }
614 DefBody::Call { tag, .. } => out.push(tag.clone()),
615 }
616}
617
618pub fn register_defs_from_source(krate: CrateNum, source: &str) -> usize {
623 let defs = parse_defs(source);
624 let n = defs.len();
625 if n == 0 {
626 return 0;
627 }
628 let mut table = user_defs_map().write().expect("def table poisoned");
629 let entry = table.entry(krate).or_default();
630 for def in defs {
631 entry.insert(def.name.clone(), def);
632 }
633 n
634}
635
636pub fn register_contract_defs(tcx: rustc_middle::ty::TyCtxt<'_>) -> usize {
642 struct Visitor<'tcx> {
643 tcx: rustc_middle::ty::TyCtxt<'tcx>,
644 count: usize,
645 }
646
647 impl<'tcx> rustc_hir::intravisit::Visitor<'tcx> for Visitor<'tcx> {
648 fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
649 let attrs = self.tcx.hir_attrs(item.hir_id());
650 for attr in attrs {
651 if !is_contract_def_attr(attr) {
652 continue;
653 }
654 let attr_str = crate::compat::attribute_to_string(self.tcx, attr);
655 if let Some(def_str) = extract_contract_def_string(&attr_str) {
656 let n = register_defs_from_source(LOCAL_CRATE, &def_str);
657 if n > 0 {
658 rap_info!("rapx: registered {n} contract def(s) from #[rapx::def_contract]");
659 }
660 self.count += n;
661 }
662 }
663 rustc_hir::intravisit::walk_item(self, item);
664 }
665 }
666
667 let mut v = Visitor { tcx, count: 0 };
668 tcx.hir_visit_all_item_likes_in_crate(&mut v);
669 v.count
670}
671
672fn is_contract_def_attr(attr: &rustc_hir::Attribute) -> bool {
675 let path = attr.path();
676 if path.len() >= 2
677 && path[path.len() - 2].as_str() == "rapx"
678 && path[path.len() - 1].as_str() == "def_contract"
679 {
680 return true;
681 }
682 path.len() == 1 && path[0].as_str() == "def_contract"
683}
684
685fn extract_contract_def_string(attr_str: &str) -> Option<String> {
688 struct OneAttr {
689 attr: syn::Attribute,
690 }
691 impl syn::parse::Parse for OneAttr {
692 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
693 let attrs = syn::Attribute::parse_outer(input)?;
694 let attr = attrs
695 .into_iter()
696 .next()
697 .ok_or_else(|| input.error("expected one attribute"))?;
698 Ok(OneAttr { attr })
699 }
700 }
701
702 let one: OneAttr = syn::parse_str(attr_str).ok()?;
703 let syn::Meta::List(list) = one.attr.meta else {
704 return None;
705 };
706 let lit: syn::LitStr = syn::parse2(list.tokens).ok()?;
707 Some(lit.value())
708}
709