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}