rust_verify_test coverage (fbbbbcf)

Coverage Report

Created: 2026-08-23 08:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
rust_verify/src/automatic_derive.rs
Line
Count
Source
1
use crate::context::Context;
2
use crate::verus_items::RustItem;
3
use rustc_hir::HirId;
4
use rustc_span::Span;
5
use std::sync::Arc;
6
use vir::ast::{
7
    BinaryOp, Expr, ExprX, FunctionX, Mode, Place, PlaceX, SpannedTyped, VirErr, VirErrAs,
8
};
9
use vir::messages::WarningAllow;
10
11
/// Traits with special handling
12
#[derive(Clone, Copy, Debug)]
13
pub enum SpecialTrait {
14
    Clone,
15
    //PartialEq,
16
}
17
18
/// What to do for a given automatically-derived trait impl
19
#[derive(Debug)]
20
pub enum AutomaticDeriveAction {
21
    Special(SpecialTrait),
22
    VerifyAsIs,
23
    /// Ignore, optionally providing a warning
24
    Ignore,
25
}
26
27
1.38k
pub fn get_action(rust_item: Option<RustItem>) -> AutomaticDeriveAction {
28
1.25k
    match rust_item {
29
487
        Some(RustItem::PartialEq | RustItem::Eq) => AutomaticDeriveAction::Ignore,
30
76
        Some(RustItem::Clone) => AutomaticDeriveAction::Special(SpecialTrait::Clone),
31
32
76
        Some(RustItem::Copy) => AutomaticDeriveAction::VerifyAsIs,
33
34
        Some(RustItem::Hash)
35
        | Some(RustItem::Default)
36
        | Some(RustItem::Debug)
37
        | Some(RustItem::Ord)
38
128
        | Some(RustItem::PartialOrd) => AutomaticDeriveAction::Ignore,
39
40
613
        Some(_) | None => AutomaticDeriveAction::VerifyAsIs,
41
    }
42
1.38k
}
43
44
434k
pub fn is_automatically_derived(attrs: &[rustc_hir::Attribute]) -> bool {
45
1.11M
    for attr in attrs.iter() {
46
294k
        match attr {
47
825k
            rustc_hir::Attribute::Unparsed(item) => match &item.path.segments[..] {
48
49.0k
                [segment] => {
49
49.0k
                    if segment.as_str() == "automatically_derived" {
50
0
                        return true;
51
49.0k
                    }
52
                }
53
776k
                _ => {}
54
            },
55
            rustc_hir::Attribute::Parsed(rustc_hir::attrs::AttributeKind::AutomaticallyDerived) => {
56
1.74k
                return true;
57
            }
58
292k
            _ => {}
59
        }
60
    }
61
433k
    false
62
434k
}
63
64
40
pub fn modify_derived_item<'tcx>(
65
40
    ctxt: &Context<'tcx>,
66
40
    id: rustc_span::def_id::DefId,
67
40
    inputs: &Vec<rustc_middle::ty::Ty>,
68
40
    span: Span,
69
40
    hir_id: HirId,
70
40
    action: &AutomaticDeriveAction,
71
40
    function: &mut FunctionX,
72
40
) -> Result<(), VirErr> {
73
40
    let AutomaticDeriveAction::Special(special) = action else {
74
2
        return Ok(());
75
    };
76
38
    match special {
77
        SpecialTrait::Clone => {
78
38
            if &*function.name.path.last_segment() == "clone" {
79
38
                return clone_add_post_condition(ctxt, id, inputs, span, hir_id, function);
80
0
            }
81
        }
82
    }
83
0
    Ok(())
84
40
}
85
86
38
fn clone_add_post_condition<'tcx>(
87
38
    ctxt: &Context<'tcx>,
88
38
    mut id: rustc_span::def_id::DefId,
89
38
    inputs: &Vec<rustc_middle::ty::Ty>,
90
38
    span: Span,
91
38
    hir_id: HirId,
92
38
    functionx: &mut FunctionX,
93
38
) -> Result<(), VirErr> {
94
38
    if inputs.len() >= 1 {
95
        use rustc_middle::ty::{AdtDef, TyKind};
96
38
        if let TyKind::Ref(_, t, _) = inputs[0].kind() {
97
38
            if let TyKind::Adt(AdtDef(adt_def_data), _) = t.kind() {
98
38
                // It's more convenient to put verifier::allow on the datatype than on the function
99
38
                id = adt_def_data.did;
100
38
            }
101
0
        }
102
0
    }
103
38
    let warn = |msg: &str| {
104
1
        crate::attributes::warning_maybe(
105
1
            ctxt.tcx,
106
1
            id,
107
1
            span,
108
1
            &WarningAllow::AutoderiveCloneWithoutSpec,
109
            || msg,
110
1
            |msg| ctxt.diagnostics.borrow_mut().push(VirErrAs::Warning(msg)),
111
        );
112
1
    };
113
38
    let warn_unexpected = || {
114
0
        warn(
115
0
            "autoderive Clone impl does not take the form Verus expects; continuing, but without adding a specification for the derived Clone impl",
116
0
        )
117
0
    };
118
38
    let warn_unsupported = || {
119
1
        warn(
120
1
            "Verus does not (yet) support autoderive Clone impl when the clone is not a copy; continuing, but without adding a specification for the derived Clone impl",
121
1
        )
122
1
    };
123
124
38
    let Some(body) = &functionx.body else {
125
0
        return Ok(());
126
    };
127
128
    let uses_copy;
129
    let self_var;
130
131
38
    match &body.x {
132
38
        ExprX::Block(_stmts, Some(last_expr)) => match &last_expr.x {
133
37
            ExprX::ReadPlace(pl, _) => match &pl.x {
134
37
                PlaceX::Local(id) if &*id.0 == "self" => {
135
37
                    uses_copy = true;
136
37
                    self_var = Some(last_expr.clone());
137
37
                }
138
                _ => {
139
0
                    warn_unexpected();
140
0
                    return Ok(());
141
                }
142
            },
143
1
            ExprX::Ctor { .. } => {
144
1
                uses_copy = false;
145
1
                self_var = None;
146
1
            }
147
            _ => {
148
0
                warn_unexpected();
149
0
                return Ok(());
150
            }
151
        },
152
        _ => {
153
0
            warn_unexpected();
154
0
            return Ok(());
155
        }
156
    }
157
158
38
    if functionx.ensure.0.len() != 0 {
159
0
        warn_unexpected();
160
0
        return Ok(());
161
38
    }
162
163
38
    if uses_copy {
164
37
        // Add `ensures ret == self`
165
37
        let self_var = self_var.unwrap();
166
37
        let ret_var = SpannedTyped::new(
167
37
            &self_var.span,
168
37
            &self_var.typ,
169
37
            ExprX::Var(functionx.ret.x.name.clone()),
170
37
        );
171
37
        let eq_expr = SpannedTyped::new(
172
37
            &self_var.span,
173
37
            &vir::ast_util::bool_typ(),
174
37
            ExprX::Binary(BinaryOp::Eq(Mode::Spec), ret_var.clone(), self_var.clone()),
175
37
        );
176
37
177
37
        let eq_expr = cleanup_span_ids(ctxt, span, hir_id, &eq_expr);
178
37
        functionx.ensure.0 = Arc::new(vec![eq_expr]);
179
37
    } else {
180
1
        warn_unsupported();
181
1
    }
182
183
38
    Ok(())
184
38
}
185
186
// TODO better place for this
187
37
fn cleanup_span_ids<'tcx>(ctxt: &Context<'tcx>, span: Span, hir_id: HirId, expr: &Expr) -> Expr {
188
37
    vir::ast_visitor::map_expr_place_visitor(
189
37
        expr,
190
111
        &|e: &Expr| {
191
111
            let e = ctxt.spans.spanned_typed_new(span, &e.typ, e.x.clone());
192
111
            let mut erasure_info = ctxt.erasure_info.borrow_mut();
193
111
            erasure_info.hir_vir_ids.push((hir_id, e.span.id));
194
111
            Ok(e)
195
111
        },
196
37
        &|p: &Place| {
197
37
            let p = ctxt.spans.spanned_typed_new(span, &p.typ, p.x.clone());
198
37
            let mut erasure_info = ctxt.erasure_info.borrow_mut();
199
37
            erasure_info.hir_vir_ids.push((hir_id, p.span.id));
200
37
            Ok(p)
201
37
        },
202
    )
203
37
    .unwrap()
204
37
}