Skip to main content

cove_sema/
capability.rs

1//! Capabilities required by Cove code.
2//!
3//! Cove code has no ambient authority. External operations are typed Host APIs,
4//! and the compiler derives which capabilities each function needs from its
5//! call graph.
6
7use std::fmt;
8
9/// A coarse capability named in `cove.toml`.
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Capability(pub String);
12
13impl Capability {
14    pub fn new(name: impl Into<String>) -> Self {
15        Capability(name.into())
16    }
17
18    pub fn as_str(&self) -> &str {
19        &self.0
20    }
21}
22
23impl fmt::Display for Capability {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.write_str(&self.0)
26    }
27}
28
29/// Why a declaration's derived capability set is a lower bound rather than
30/// the whole of what calling it can reach.
31///
32/// ADR 0015 makes a derived set a lower bound and nothing more: it names the
33/// capabilities the call graph can see, and a call the call graph cannot
34/// follow is reported here rather than left out in silence. A declaration
35/// carrying none of these is *capability-closed* — the call graph followed
36/// every call it makes, so its set is the whole of what it needs.
37#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub enum OpenCall {
39    /// The body calls a value rather than a declaration: `work()` where
40    /// `work` is a parameter, a local bound out of a collection, or the
41    /// result of another call. What that value requires belongs to whoever
42    /// wrote it, which is somewhere this call graph does not lead.
43    FunctionValue,
44    /// The body calls a method on a value whose implementation its caller
45    /// chose: a `dyn Trait` value, or a value of a generic parameter. The
46    /// conformance that runs is picked where the value was made.
47    DynamicDispatch,
48    /// Every call in this body is one the call graph followed, but one of
49    /// them leads to a capability-open declaration, so the incompleteness
50    /// reaches here too.
51    ReachedOpenCall,
52}
53
54impl OpenCall {
55    /// The clause a report prints to say why a set is a lower bound.
56    pub fn reason(self) -> &'static str {
57        match self {
58            OpenCall::FunctionValue => "calls a function value",
59            OpenCall::DynamicDispatch => "dispatches through a `dyn` or generic value",
60            OpenCall::ReachedOpenCall => "calls a capability-open declaration",
61        }
62    }
63}
64
65/// The reasons `open` carries, as the one clause every report prints after
66/// `capability-open:`.
67///
68/// One rendering rather than one per command: `cove outline`, `cove impact`,
69/// and `cove test` are all saying the same thing about the same fact.
70pub fn open_reasons<'a>(open: impl IntoIterator<Item = &'a OpenCall>) -> String {
71    open.into_iter()
72        .map(|reason| reason.reason())
73        .collect::<Vec<_>>()
74        .join(", ")
75}