1use crate::decisions::CDecisionCategory;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CDecisionContext {
10 pub c_construct: CConstruct,
12
13 pub category: CDecisionCategory,
15
16 pub c_span: Option<SourceSpan>,
18
19 pub rust_span: Option<SourceSpan>,
21
22 pub c_context: String,
24
25 pub hir_hash: u64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct SourceSpan {
32 pub file: String,
33 pub start_line: usize,
34 pub start_col: usize,
35 pub end_line: usize,
36 pub end_col: usize,
37}
38
39impl fmt::Display for SourceSpan {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{}:{}:{}", self.file, self.start_line, self.start_col)
42 }
43}
44
45impl SourceSpan {
46 pub fn line(file: impl Into<String>, line: usize) -> Self {
48 Self { file: file.into(), start_line: line, start_col: 1, end_line: line, end_col: 1 }
49 }
50
51 pub fn overlaps(&self, other: &Self) -> bool {
53 self.file == other.file
54 && self.start_line <= other.end_line
55 && self.end_line >= other.start_line
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum CConstruct {
62 RawPointer { is_const: bool, pointee: String },
64 Array { element: String, size: Option<usize> },
66 String { is_const: bool },
68 Struct { name: String, has_pointers: bool },
70 Union { name: String },
72 FunctionPointer { signature: String },
74 VoidPointer,
76}
77
78impl fmt::Display for CConstruct {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::RawPointer { is_const, pointee } => {
82 if *is_const {
83 write!(f, "const {}*", pointee)
84 } else {
85 write!(f, "{}*", pointee)
86 }
87 }
88 Self::Array { element, size } => {
89 if let Some(n) = size {
90 write!(f, "{}[{}]", element, n)
91 } else {
92 write!(f, "{}[]", element)
93 }
94 }
95 Self::String { is_const } => {
96 if *is_const {
97 write!(f, "const char*")
98 } else {
99 write!(f, "char*")
100 }
101 }
102 Self::Struct { name, .. } => write!(f, "struct {}", name),
103 Self::Union { name } => write!(f, "union {}", name),
104 Self::FunctionPointer { signature } => write!(f, "(*)({})", signature),
105 Self::VoidPointer => write!(f, "void*"),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum LifetimeDecision {
113 Elided,
115 Explicit(String),
117 Static,
119 InputBound(String),
121}
122
123impl CDecisionContext {
124 pub fn new(construct: CConstruct, category: CDecisionCategory) -> Self {
126 Self {
127 c_construct: construct,
128 category,
129 c_span: None,
130 rust_span: None,
131 c_context: String::new(),
132 hir_hash: 0,
133 }
134 }
135
136 pub fn with_c_span(mut self, span: SourceSpan) -> Self {
138 self.c_span = Some(span);
139 self
140 }
141
142 pub fn with_context(mut self, context: impl Into<String>) -> Self {
144 self.c_context = context.into();
145 self
146 }
147
148 pub fn to_context_strings(&self) -> Vec<String> {
150 vec![self.c_construct.to_string(), self.category.to_string(), self.c_context.clone()]
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_c_construct_display() {
160 let ptr = CConstruct::RawPointer { is_const: true, pointee: "int".into() };
161 assert_eq!(ptr.to_string(), "const int*");
162
163 let arr = CConstruct::Array { element: "char".into(), size: Some(256) };
164 assert_eq!(arr.to_string(), "char[256]");
165 }
166
167 #[test]
168 fn test_c_construct_all_variants() {
169 let ptr_mut = CConstruct::RawPointer { is_const: false, pointee: "float".into() };
171 assert_eq!(ptr_mut.to_string(), "float*");
172
173 let arr_unsized = CConstruct::Array { element: "int".into(), size: None };
175 assert_eq!(arr_unsized.to_string(), "int[]");
176
177 let str_const = CConstruct::String { is_const: true };
179 assert_eq!(str_const.to_string(), "const char*");
180
181 let str_mut = CConstruct::String { is_const: false };
183 assert_eq!(str_mut.to_string(), "char*");
184
185 let s = CConstruct::Struct { name: "Node".into(), has_pointers: true };
187 assert_eq!(s.to_string(), "struct Node");
188
189 let u = CConstruct::Union { name: "Data".into() };
191 assert_eq!(u.to_string(), "union Data");
192
193 let fp = CConstruct::FunctionPointer { signature: "int, int".into() };
195 assert_eq!(fp.to_string(), "(*)(int, int)");
196
197 let vp = CConstruct::VoidPointer;
199 assert_eq!(vp.to_string(), "void*");
200 }
201
202 #[test]
203 fn test_source_span_overlap() {
204 let span1 = SourceSpan::line("test.c", 10);
205 let span2 = SourceSpan::line("test.c", 10);
206 assert!(span1.overlaps(&span2));
207
208 let span3 = SourceSpan::line("test.c", 20);
209 assert!(!span1.overlaps(&span3));
210
211 let span4 = SourceSpan::line("other.c", 10);
212 assert!(!span1.overlaps(&span4));
213 }
214
215 #[test]
216 fn test_source_span_display() {
217 let span = SourceSpan::line("src/main.c", 42);
218 assert_eq!(span.to_string(), "src/main.c:42:1");
219 }
220
221 #[test]
222 fn test_source_span_multi_line() {
223 let span = SourceSpan {
224 file: "test.c".into(),
225 start_line: 10,
226 start_col: 5,
227 end_line: 15,
228 end_col: 10,
229 };
230
231 let single = SourceSpan::line("test.c", 12);
232 assert!(span.overlaps(&single)); let outside = SourceSpan::line("test.c", 20);
235 assert!(!span.overlaps(&outside));
236 }
237
238 #[test]
239 fn test_context_strings() {
240 let ctx = CDecisionContext::new(
241 CConstruct::RawPointer { is_const: false, pointee: "int".into() },
242 CDecisionCategory::PointerOwnership,
243 )
244 .with_context("function argument");
245
246 let strings = ctx.to_context_strings();
247 assert_eq!(strings.len(), 3);
248 assert!(strings[0].contains("int*"));
249 assert!(strings[1].contains("pointer_ownership"));
250 }
251
252 #[test]
253 fn test_context_with_c_span() {
254 let ctx = CDecisionContext::new(CConstruct::VoidPointer, CDecisionCategory::RawPointerCast)
255 .with_c_span(SourceSpan::line("test.c", 100));
256
257 assert!(ctx.c_span.is_some());
258 assert_eq!(ctx.c_span.as_ref().unwrap().start_line, 100);
259 }
260
261 #[test]
262 fn test_lifetime_decision_variants() {
263 let elided = LifetimeDecision::Elided;
264 assert!(matches!(elided, LifetimeDecision::Elided));
265
266 let explicit = LifetimeDecision::Explicit("'a".into());
267 assert!(matches!(explicit, LifetimeDecision::Explicit(_)));
268
269 let static_lt = LifetimeDecision::Static;
270 assert!(matches!(static_lt, LifetimeDecision::Static));
271
272 let bound = LifetimeDecision::InputBound("'input".into());
273 assert!(matches!(bound, LifetimeDecision::InputBound(_)));
274 }
275
276 #[test]
277 fn test_context_default_values() {
278 let ctx = CDecisionContext::new(CConstruct::VoidPointer, CDecisionCategory::UnsafeBlock);
279
280 assert!(ctx.c_span.is_none());
281 assert!(ctx.rust_span.is_none());
282 assert!(ctx.c_context.is_empty());
283 assert_eq!(ctx.hir_hash, 0);
284 }
285}