Skip to main content

weaveffi_core/validate/
warnings.rs

1//! Non-fatal, lint-style checks over a validated [`Api`].
2//!
3//! These are distinct from the hard validation errors in the parent
4//! [`crate::validate`] module: errors *reject* an IDL, whereas warnings
5//! merely flag stylistic or ergonomic concerns (deep nesting, undocumented
6//! modules, no-op `mutable` flags, …) that the caller can surface and the
7//! user can choose to ignore.
8
9use weaveffi_ir::ir::{Api, TypeRef};
10
11/// A non-fatal advisory emitted by [`collect_warnings`].
12#[derive(Debug, Clone)]
13pub enum ValidationWarning {
14    LargeEnumVariantCount {
15        enum_name: String,
16        count: usize,
17    },
18    DeepNesting {
19        location: String,
20        depth: usize,
21    },
22    EmptyModuleDoc {
23        module: String,
24    },
25    AsyncVoidFunction {
26        module: String,
27        function: String,
28    },
29    MutableOnValueType {
30        module: String,
31        function: String,
32        param: String,
33    },
34    DeprecatedFunction {
35        module: String,
36        function: String,
37        message: String,
38    },
39}
40
41impl std::fmt::Display for ValidationWarning {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Self::LargeEnumVariantCount { enum_name, count } => {
45                write!(f, "enum '{enum_name}' has {count} variants (>100)")
46            }
47            Self::DeepNesting { location, depth } => {
48                write!(
49                    f,
50                    "deep type nesting at {location} (depth {depth}, max recommended 3)"
51                )
52            }
53            Self::EmptyModuleDoc { module } => {
54                write!(f, "module '{module}' has no doc comments on any function")
55            }
56            Self::AsyncVoidFunction { module, function } => {
57                write!(
58                    f,
59                    "async function {module}::{function} has no return type; async void is unusual"
60                )
61            }
62            Self::MutableOnValueType {
63                module,
64                function,
65                param,
66            } => {
67                write!(
68                    f,
69                    "'mutable' on value-type parameter {module}::{function}::{param} has no effect; only meaningful for pointer/reference types (struct, string, bytes)"
70                )
71            }
72            Self::DeprecatedFunction {
73                module,
74                function,
75                message,
76            } => {
77                write!(f, "function {module}::{function} is deprecated: {message}")
78            }
79        }
80    }
81}
82
83/// Walk every module and collect all advisory warnings for `api`.
84///
85/// Assumes `api` has already passed hard validation; it does not re-check
86/// structural invariants.
87pub fn collect_warnings(api: &Api) -> Vec<ValidationWarning> {
88    let mut warnings = Vec::new();
89    for module in &api.modules {
90        for e in &module.enums {
91            if e.variants.len() > 100 {
92                warnings.push(ValidationWarning::LargeEnumVariantCount {
93                    enum_name: e.name.clone(),
94                    count: e.variants.len(),
95                });
96            }
97        }
98
99        for f in &module.functions {
100            for p in &f.params {
101                let depth = nesting_depth(&p.ty);
102                if depth > 3 {
103                    warnings.push(ValidationWarning::DeepNesting {
104                        location: format!("{}::{}::{}", module.name, f.name, p.name),
105                        depth,
106                    });
107                }
108            }
109            if let Some(ret) = &f.returns {
110                let depth = nesting_depth(ret);
111                if depth > 3 {
112                    warnings.push(ValidationWarning::DeepNesting {
113                        location: format!("{}::{}::return", module.name, f.name),
114                        depth,
115                    });
116                }
117            }
118        }
119        for s in &module.structs {
120            for field in &s.fields {
121                let depth = nesting_depth(&field.ty);
122                if depth > 3 {
123                    warnings.push(ValidationWarning::DeepNesting {
124                        location: format!("{}::{}::{}", module.name, s.name, field.name),
125                        depth,
126                    });
127                }
128            }
129        }
130
131        for f in &module.functions {
132            if f.r#async && f.returns.is_none() {
133                warnings.push(ValidationWarning::AsyncVoidFunction {
134                    module: module.name.clone(),
135                    function: f.name.clone(),
136                });
137            }
138            for p in &f.params {
139                if p.mutable && is_value_type(&p.ty) {
140                    warnings.push(ValidationWarning::MutableOnValueType {
141                        module: module.name.clone(),
142                        function: f.name.clone(),
143                        param: p.name.clone(),
144                    });
145                }
146            }
147        }
148
149        for f in &module.functions {
150            if let Some(msg) = &f.deprecated {
151                warnings.push(ValidationWarning::DeprecatedFunction {
152                    module: module.name.clone(),
153                    function: f.name.clone(),
154                    message: msg.clone(),
155                });
156            }
157        }
158
159        if !module.functions.is_empty() && module.functions.iter().all(|f| f.doc.is_none()) {
160            warnings.push(ValidationWarning::EmptyModuleDoc {
161                module: module.name.clone(),
162            });
163        }
164    }
165    warnings
166}
167
168fn is_value_type(ty: &TypeRef) -> bool {
169    matches!(
170        ty,
171        TypeRef::I32
172            | TypeRef::U32
173            | TypeRef::I64
174            | TypeRef::F64
175            | TypeRef::Bool
176            | TypeRef::Enum(_)
177            | TypeRef::Handle
178    )
179}
180
181fn nesting_depth(ty: &TypeRef) -> usize {
182    match ty {
183        TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
184            1 + nesting_depth(inner)
185        }
186        TypeRef::Map(k, v) => nesting_depth(k).max(nesting_depth(v)),
187        _ => 0,
188    }
189}