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    /// An enum has an unusually large number of variants (more than 100).
15    LargeEnumVariantCount {
16        /// Enum that tripped the threshold.
17        enum_name: String,
18        /// Number of variants the enum declares.
19        count: usize,
20    },
21    /// A type is nested more deeply than recommended (more than 3 levels).
22    DeepNesting {
23        /// Where the deeply nested type appears (a `module::fn::param` path).
24        location: String,
25        /// Measured nesting depth.
26        depth: usize,
27    },
28    /// A module has functions but none of them carry a doc comment.
29    EmptyModuleDoc {
30        /// Module with no documented functions.
31        module: String,
32    },
33    /// An async function declares no return type, which is unusual.
34    AsyncVoidFunction {
35        /// Module that contains the function.
36        module: String,
37        /// Async function with no return type.
38        function: String,
39    },
40    /// A `mutable` flag sits on a value-type parameter, where it has no effect.
41    MutableOnValueType {
42        /// Module that contains the function.
43        module: String,
44        /// Function that declares the parameter.
45        function: String,
46        /// Parameter that carries the no-op `mutable` flag.
47        param: String,
48    },
49    /// A function is marked deprecated; the message is surfaced to consumers.
50    DeprecatedFunction {
51        /// Module that contains the function.
52        module: String,
53        /// Deprecated function.
54        function: String,
55        /// Deprecation message declared in the IDL.
56        message: String,
57    },
58}
59
60impl std::fmt::Display for ValidationWarning {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Self::LargeEnumVariantCount { enum_name, count } => {
64                write!(f, "enum '{enum_name}' has {count} variants (>100)")
65            }
66            Self::DeepNesting { location, depth } => {
67                write!(
68                    f,
69                    "deep type nesting at {location} (depth {depth}, max recommended 3)"
70                )
71            }
72            Self::EmptyModuleDoc { module } => {
73                write!(f, "module '{module}' has no doc comments on any function")
74            }
75            Self::AsyncVoidFunction { module, function } => {
76                write!(
77                    f,
78                    "async function {module}::{function} has no return type; async void is unusual"
79                )
80            }
81            Self::MutableOnValueType {
82                module,
83                function,
84                param,
85            } => {
86                write!(
87                    f,
88                    "'mutable' on value-type parameter {module}::{function}::{param} has no effect; only meaningful for pointer/reference types (struct, string, bytes)"
89                )
90            }
91            Self::DeprecatedFunction {
92                module,
93                function,
94                message,
95            } => {
96                write!(f, "function {module}::{function} is deprecated: {message}")
97            }
98        }
99    }
100}
101
102/// Walk every module and collect all advisory warnings for `api`.
103///
104/// Assumes `api` has already passed hard validation; it does not re-check
105/// structural invariants.
106pub fn collect_warnings(api: &Api) -> Vec<ValidationWarning> {
107    let mut warnings = Vec::new();
108    for module in &api.modules {
109        for e in &module.enums {
110            if e.variants.len() > 100 {
111                warnings.push(ValidationWarning::LargeEnumVariantCount {
112                    enum_name: e.name.clone(),
113                    count: e.variants.len(),
114                });
115            }
116        }
117
118        for f in &module.functions {
119            for p in &f.params {
120                let depth = nesting_depth(&p.ty);
121                if depth > 3 {
122                    warnings.push(ValidationWarning::DeepNesting {
123                        location: format!("{}::{}::{}", module.name, f.name, p.name),
124                        depth,
125                    });
126                }
127            }
128            if let Some(ret) = &f.returns {
129                let depth = nesting_depth(ret);
130                if depth > 3 {
131                    warnings.push(ValidationWarning::DeepNesting {
132                        location: format!("{}::{}::return", module.name, f.name),
133                        depth,
134                    });
135                }
136            }
137        }
138        for s in &module.structs {
139            for field in &s.fields {
140                let depth = nesting_depth(&field.ty);
141                if depth > 3 {
142                    warnings.push(ValidationWarning::DeepNesting {
143                        location: format!("{}::{}::{}", module.name, s.name, field.name),
144                        depth,
145                    });
146                }
147            }
148        }
149
150        for f in &module.functions {
151            if f.r#async && f.returns.is_none() {
152                warnings.push(ValidationWarning::AsyncVoidFunction {
153                    module: module.name.clone(),
154                    function: f.name.clone(),
155                });
156            }
157            for p in &f.params {
158                if p.mutable && is_value_type(&p.ty) {
159                    warnings.push(ValidationWarning::MutableOnValueType {
160                        module: module.name.clone(),
161                        function: f.name.clone(),
162                        param: p.name.clone(),
163                    });
164                }
165            }
166        }
167
168        for f in &module.functions {
169            if let Some(msg) = &f.deprecated {
170                warnings.push(ValidationWarning::DeprecatedFunction {
171                    module: module.name.clone(),
172                    function: f.name.clone(),
173                    message: msg.clone(),
174                });
175            }
176        }
177
178        if !module.functions.is_empty() && module.functions.iter().all(|f| f.doc.is_none()) {
179            warnings.push(ValidationWarning::EmptyModuleDoc {
180                module: module.name.clone(),
181            });
182        }
183    }
184    warnings
185}
186
187fn is_value_type(ty: &TypeRef) -> bool {
188    matches!(
189        ty,
190        TypeRef::I32
191            | TypeRef::U32
192            | TypeRef::I64
193            | TypeRef::F64
194            | TypeRef::Bool
195            | TypeRef::Enum(_)
196            | TypeRef::Handle
197    )
198}
199
200fn nesting_depth(ty: &TypeRef) -> usize {
201    match ty {
202        TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
203            1 + nesting_depth(inner)
204        }
205        TypeRef::Map(k, v) => nesting_depth(k).max(nesting_depth(v)),
206        _ => 0,
207    }
208}