rust_verify_test coverage (7c1d655)

Coverage Report

Created: 2026-07-20 10:58

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.26k
pub fn get_action(rust_item: Option<RustItem>) -> AutomaticDeriveAction {
28
1.17k
    match rust_item {
29
483
        Some(RustItem::PartialEq | RustItem::Eq) => AutomaticDeriveAction::Ignore,
30
40
        Some(RustItem::Clone) => AutomaticDeriveAction::Special(SpecialTrait::Clone),
31
32
40
        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
573
        Some(_) | None => AutomaticDeriveAction::VerifyAsIs,
41
    }
42
1.26k
}
43
44
399k
pub fn is_automatically_derived(attrs: &[rustc_hir::Attribute]) -> bool {
45
1.03M
    for attr in attrs.iter() {
46
284k
        match attr {
47
748k
            rustc_hir::Attribute::Unparsed(item) => match &item.path.segments[..] {
48
42.9k
                [segment] => {
49
42.9k
                    if segment.as_str() == "automatically_derived" {
50
0
                        return true;
51
42.9k
                    }
52
                }
53
705k
                _ => {}
54
            },
55
            rustc_hir::Attribute::Parsed(
56
                rustc_hir::attrs::AttributeKind::AutomaticallyDerived(_),
57
            ) => {
58
1.63k
                return true;
59
            }
60
283k
            _ => {}
61
        }
62
    }
63
397k
    false
64
399k
}
65
66
22
pub fn modify_derived_item<'tcx>(
67
22
    ctxt: &Context<'tcx>,
68
22
    id: rustc_span::def_id::DefId,
69
22
    inputs: &Vec<rustc_middle::ty::Ty>,
70
22
    span: Span,
71
22
    hir_id: HirId,
72
22
    action: &AutomaticDeriveAction,
73
22
    function: &mut FunctionX,
74
22
) -> Result<(), VirErr> {
75
22
    let AutomaticDeriveAction::Special(special) = action else {
76
2
        return Ok(());
77
    };
78
20
    match special {
79
        SpecialTrait::Clone => {
80
20
            if &*function.name.path.last_segment() == "clone" {
81
20
                return clone_add_post_condition(ctxt, id, inputs, span, hir_id, function);
82
0
            }
83
        }
84
    }
85
0
    Ok(())
86
22
}
87
88
20
fn clone_add_post_condition<'tcx>(
89
20
    ctxt: &Context<'tcx>,
90
20
    mut id: rustc_span::def_id::DefId,
91
20
    inputs: &Vec<rustc_middle::ty::Ty>,
92
20
    span: Span,
93
20
    hir_id: HirId,
94
20
    functionx: &mut FunctionX,
95
20
) -> Result<(), VirErr> {
96
20
    if inputs.len() >= 1 {
97
        use rustc_middle::ty::{AdtDef, TyKind};
98
20
        if let TyKind::Ref(_, t, _) = inputs[0].kind() {
99
20
            if let TyKind::Adt(AdtDef(adt_def_data), _) = t.kind() {
100
20
                // It's more convenient to put verifier::allow on the datatype than on the function
101
20
                id = adt_def_data.did;
102
20
            }
103
0
        }
104
0
    }
105
20
    let warn = |msg: &str| {
106
1
        crate::attributes::warning_maybe(
107
1
            ctxt.tcx,
108
1
            id,
109
1
            span,
110
1
            &WarningAllow::AutoderiveCloneWithoutSpec,
111
            || msg,
112
1
            |msg| ctxt.diagnostics.borrow_mut().push(VirErrAs::Warning(msg)),
113
        );
114
1
    };
115
20
    let warn_unexpected = || {
116
0
        warn(
117
0
            "autoderive Clone impl does not take the form Verus expects; continuing, but without adding a specification for the derived Clone impl",
118
0
        )
119
0
    };
120
20
    let warn_unsupported = || {
121
1
        warn(
122
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",
123
1
        )
124
1
    };
125
126
20
    let Some(body) = &functionx.body else {
127
0
        return Ok(());
128
    };
129
130
    let uses_copy;
131
    let self_var;
132
133
20
    match &body.x {
134
20
        ExprX::Block(_stmts, Some(last_expr)) => match &last_expr.x {
135
19
            ExprX::ReadPlace(pl, _) => match &pl.x {
136
19
                PlaceX::Local(id) if &*id.0 == "self" => {
137
19
                    uses_copy = true;
138
19
                    self_var = Some(last_expr.clone());
139
19
                }
140
                _ => {
141
0
                    warn_unexpected();
142
0
                    return Ok(());
143
                }
144
            },
145
1
            ExprX::Ctor { .. } => {
146
1
                uses_copy = false;
147
1
                self_var = None;
148
1
            }
149
            _ => {
150
0
                warn_unexpected();
151
0
                return Ok(());
152
            }
153
        },
154
        _ => {
155
0
            warn_unexpected();
156
0
            return Ok(());
157
        }
158
    }
159
160
20
    if functionx.ensure.0.len() != 0 {
161
0
        warn_unexpected();
162
0
        return Ok(());
163
20
    }
164
165
20
    if uses_copy {
166
19
        // Add `ensures ret == self`
167
19
        let self_var = self_var.unwrap();
168
19
        let ret_var = SpannedTyped::new(
169
19
            &self_var.span,
170
19
            &self_var.typ,
171
19
            ExprX::Var(functionx.ret.x.name.clone()),
172
19
        );
173
19
        let eq_expr = SpannedTyped::new(
174
19
            &self_var.span,
175
19
            &vir::ast_util::bool_typ(),
176
19
            ExprX::Binary(BinaryOp::Eq(Mode::Spec), ret_var.clone(), self_var.clone()),
177
19
        );
178
19
179
19
        let eq_expr = cleanup_span_ids(ctxt, span, hir_id, &eq_expr);
180
19
        functionx.ensure.0 = Arc::new(vec![eq_expr]);
181
19
    } else {
182
1
        warn_unsupported();
183
1
    }
184
185
20
    Ok(())
186
20
}
187
188
// TODO better place for this
189
19
fn cleanup_span_ids<'tcx>(ctxt: &Context<'tcx>, span: Span, hir_id: HirId, expr: &Expr) -> Expr {
190
19
    vir::ast_visitor::map_expr_place_visitor(
191
19
        expr,
192
57
        &|e: &Expr| {
193
57
            let e = ctxt.spans.spanned_typed_new(span, &e.typ, e.x.clone());
194
57
            let mut erasure_info = ctxt.erasure_info.borrow_mut();
195
57
            erasure_info.hir_vir_ids.push((hir_id, e.span.id));
196
57
            Ok(e)
197
57
        },
198
19
        &|p: &Place| {
199
19
            let p = ctxt.spans.spanned_typed_new(span, &p.typ, p.x.clone());
200
19
            let mut erasure_info = ctxt.erasure_info.borrow_mut();
201
19
            erasure_info.hir_vir_ids.push((hir_id, p.span.id));
202
19
            Ok(p)
203
19
        },
204
    )
205
19
    .unwrap()
206
19
}