1#![allow(unused_assignments)]
8
9use miette::{Diagnostic, NamedSource, SourceSpan};
10use thiserror::Error;
11
12#[derive(Error, Debug, Diagnostic)]
14pub enum AidlError {
15 #[error(transparent)]
16 #[diagnostic(transparent)]
17 Parse(Box<ParseError>),
18
19 #[error(transparent)]
20 #[diagnostic(transparent)]
21 Semantic(Box<SemanticError>),
22
23 #[error(transparent)]
24 #[diagnostic(transparent)]
25 Resolution(Box<ResolutionError>),
26
27 #[error(transparent)]
28 Io(#[from] std::io::Error),
29
30 #[error("{} error(s) occurred during AIDL compilation", errors.len())]
32 #[diagnostic(code(aidl::multiple_errors))]
33 Multiple {
34 #[related]
35 errors: Vec<AidlError>,
36 },
37}
38
39impl From<ParseError> for AidlError {
40 fn from(e: ParseError) -> Self {
41 AidlError::Parse(Box::new(e))
42 }
43}
44
45impl From<SemanticError> for AidlError {
46 fn from(e: SemanticError) -> Self {
47 AidlError::Semantic(Box::new(e))
48 }
49}
50
51impl From<ResolutionError> for AidlError {
52 fn from(e: ResolutionError) -> Self {
53 AidlError::Resolution(Box::new(e))
54 }
55}
56
57impl AidlError {
58 pub fn collect(errors: Vec<AidlError>) -> Option<AidlError> {
60 let flat: Vec<AidlError> = errors
61 .into_iter()
62 .flat_map(|e| match e {
63 AidlError::Multiple { errors } => errors,
64 other => vec![other],
65 })
66 .collect();
67 match flat.len() {
68 0 => None,
69 1 => flat.into_iter().next(),
70 _ => Some(AidlError::Multiple { errors: flat }),
71 }
72 }
73}
74
75#[allow(unused)]
77#[derive(Error, Debug, Diagnostic)]
78#[error("AIDL syntax error")]
79#[diagnostic(code(aidl::parse_error))]
80pub struct ParseError {
81 #[source_code]
82 pub src: NamedSource<String>,
83 #[label("{message}")]
84 pub span: SourceSpan,
85 pub message: String,
86 #[help]
87 pub help: Option<String>,
88}
89
90#[allow(unused)]
92#[derive(Error, Debug, Diagnostic)]
93pub enum SemanticError {
94 #[error("Interface '{interface}': transaction code {code} conflict between '{method1}' and '{method2}'")]
95 #[diagnostic(
96 code(aidl::duplicate_transaction_code),
97 help("use unique positive integer values for each method's transaction code")
98 )]
99 DuplicateTransactionCode {
100 interface: String,
101 method1: String,
102 method2: String,
103 code: i64,
104 #[source_code]
105 src: NamedSource<String>,
106 #[label("method '{method1}' uses code {code}")]
107 span: SourceSpan,
108 #[related]
109 related: Vec<DuplicateCodeRelated>,
110 },
111
112 #[error("Interface '{interface}': mixed explicit/implicit transaction IDs")]
113 #[diagnostic(
114 code(aidl::mixed_transaction_ids),
115 help("either all methods must have explicitly assigned transaction IDs or none of them should")
116 )]
117 MixedTransactionIds {
118 interface: String,
119 #[source_code]
120 src: NamedSource<String>,
121 #[label("defined in this interface")]
122 span: SourceSpan,
123 },
124
125 #[error("Interface '{interface}': method '{method}' has negative transaction code {code}")]
129 #[diagnostic(
130 code(aidl::negative_transaction_code),
131 help("transaction codes must be non-negative integers within u32 range")
132 )]
133 NegativeTransactionCode {
134 interface: String,
135 method: String,
136 code: i64,
137 #[source_code]
138 src: NamedSource<String>,
139 #[label("negative code here")]
140 span: SourceSpan,
141 },
142
143 #[error("Interface '{interface}': method '{method}' has transaction code {code} exceeding u32 range")]
144 #[diagnostic(
145 code(aidl::transaction_code_overflow),
146 help("transaction codes must fit within u32 (0..=4294967295)")
147 )]
148 TransactionCodeOverflow {
149 interface: String,
150 method: String,
151 code: i64,
152 #[source_code]
153 src: NamedSource<String>,
154 #[label("overflowing code here")]
155 span: SourceSpan,
156 },
157
158 #[error("unsupported type: {type_name}")]
159 #[diagnostic(code(aidl::unsupported_type))]
160 UnsupportedType {
161 type_name: String,
162 #[help]
163 help: Option<String>,
164 #[source_code]
165 src: NamedSource<String>,
166 #[label("this type is not supported")]
167 span: SourceSpan,
168 },
169
170 #[error("invalid operation: {message}")]
171 #[diagnostic(code(aidl::invalid_operation))]
172 InvalidOperation {
173 message: String,
174 #[source_code]
175 src: NamedSource<String>,
176 #[label("here")]
177 span: SourceSpan,
178 },
179}
180
181#[allow(unused)]
188#[derive(Error, Debug, Diagnostic)]
189#[error("conflicting method defined here")]
190pub struct DuplicateCodeRelated {
191 pub method: String,
192 #[source_code]
193 pub src: NamedSource<String>,
194 #[label("method '{method}' also uses this code")]
195 pub span: SourceSpan,
196}
197
198#[allow(unused)]
200#[derive(Error, Debug, Diagnostic)]
201pub enum ResolutionError {
202 #[error("import '{import}' not found")]
203 #[diagnostic(
204 code(aidl::import_not_found),
205 help("check that the imported type exists in the include paths")
206 )]
207 ImportNotFound {
208 import: String,
209 #[source_code]
210 src: NamedSource<String>,
211 #[label("imported here")]
212 span: SourceSpan,
213 },
214
215 #[error("unknown type '{name}'")]
216 #[diagnostic(
217 code(aidl::unknown_type),
218 help("verify that the type is defined and imported correctly")
219 )]
220 UnknownType {
221 name: String,
222 #[source_code]
223 src: NamedSource<String>,
224 #[label("referenced here")]
225 span: SourceSpan,
226 },
227}
228
229#[derive(Error, Debug)]
233#[error("{message}")]
234pub struct ConstExprError {
235 pub message: String,
236}
237
238impl ConstExprError {
239 pub fn new(message: impl Into<String>) -> Self {
240 Self {
241 message: message.into(),
242 }
243 }
244}
245
246pub fn pest_error_to_diagnostic<R: pest::RuleType>(
251 err: pest::error::Error<R>,
252 filename: &str,
253 source: &str,
254) -> ParseError {
255 let (offset, length) = match err.location {
256 pest::error::InputLocation::Pos(pos) => {
257 let len = if pos >= source.len() { 0 } else { 1 };
259 (pos, len)
260 }
261 pest::error::InputLocation::Span((start, end)) => (start, end - start),
262 };
263
264 let message = match &err.variant {
265 pest::error::ErrorVariant::ParsingError {
266 positives,
267 negatives,
268 } => format_pest_expectations(positives, negatives),
269 pest::error::ErrorVariant::CustomError { message } => message.clone(),
270 };
271
272 ParseError {
273 src: NamedSource::new(filename, source.to_string()),
274 span: SourceSpan::new(offset.into(), length),
275 message,
276 help: None,
277 }
278}
279
280fn format_pest_expectations<R: std::fmt::Debug>(positives: &[R], negatives: &[R]) -> String {
282 let mut parts = Vec::new();
283
284 if !positives.is_empty() {
285 let pos_str: Vec<String> = positives.iter().map(|r| format!("{r:?}")).collect();
286 parts.push(format!("expected {}", pos_str.join(", ")));
287 }
288
289 if !negatives.is_empty() {
290 let neg_str: Vec<String> = negatives.iter().map(|r| format!("{r:?}")).collect();
291 parts.push(format!("unexpected {}", neg_str.join(", ")));
292 }
293
294 if parts.is_empty() {
295 "syntax error".to_string()
296 } else {
297 parts.join("; ")
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn span(offset: usize, len: usize) -> SourceSpan {
306 SourceSpan::new(offset.into(), len)
307 }
308
309 #[test]
311 fn test_parse_error_display() {
312 let err = ParseError {
313 src: NamedSource::new("test.aidl", "parcelable Foo {}".to_string()),
314 span: span(0, 1),
315 message: "unexpected token".to_string(),
316 help: None,
317 };
318 let display = format!("{err}");
319 assert!(display.contains("AIDL syntax error"), "Got: {display}");
320 }
321
322 #[test]
324 fn test_parse_error_diagnostic_code() {
325 use miette::Diagnostic;
326 let err = ParseError {
327 src: NamedSource::new("test.aidl", "parcelable Foo {}".to_string()),
328 span: span(0, 1),
329 message: "test error".to_string(),
330 help: None,
331 };
332 let code = err.code().expect("ParseError must have a diagnostic code");
333 assert_eq!(code.to_string(), "aidl::parse_error");
334 }
335
336 #[test]
338 fn test_parse_error_source_span() {
339 use miette::Diagnostic;
340 let err = ParseError {
341 src: NamedSource::new("test.aidl", "parcelable Foo {}".to_string()),
342 span: span(10, 3),
343 message: "test error".to_string(),
344 help: None,
345 };
346 let labels: Vec<_> = err.labels().expect("must have labels").collect();
347 assert_eq!(labels.len(), 1);
348 let label_span = labels[0].inner();
349 assert_eq!(label_span.offset(), 10);
350 assert_eq!(label_span.len(), 3);
351 }
352
353 #[test]
355 fn test_semantic_error_variants_display() {
356 let err = SemanticError::MixedTransactionIds {
357 interface: "IFoo".to_string(),
358 src: NamedSource::new("test.aidl", "interface IFoo {}".to_string()),
359 span: span(0, 4),
360 };
361 let display = format!("{err}");
362 assert!(
363 display.contains("IFoo"),
364 "Display should contain interface name, got: {display}"
365 );
366 assert!(
367 display.contains("mixed"),
368 "Display should contain 'mixed', got: {display}"
369 );
370 }
371
372 #[test]
374 fn test_resolution_error_display() {
375 let err = ResolutionError::ImportNotFound {
376 import: "foo.bar.Baz".to_string(),
377 src: NamedSource::new("test.aidl", "import foo.bar.Baz;".to_string()),
378 span: span(0, 18),
379 };
380 let display = format!("{err}");
381 assert!(display.contains("foo.bar.Baz"), "Got: {display}");
382 assert!(display.contains("not found"), "Got: {display}");
383 }
384
385 #[test]
387 fn test_aidl_error_from_parse_error() {
388 let parse_err = ParseError {
389 src: NamedSource::new("test.aidl", "bad".to_string()),
390 span: span(0, 3),
391 message: "syntax error".to_string(),
392 help: None,
393 };
394 let aidl_err: AidlError = parse_err.into();
395 assert!(matches!(aidl_err, AidlError::Parse(_)));
396 }
397
398 #[test]
400 fn test_aidl_error_into_box_dyn_error() {
401 use std::error::Error;
402 let parse_err = ParseError {
403 src: NamedSource::new("test.aidl", "bad".to_string()),
404 span: span(0, 3),
405 message: "syntax error".to_string(),
406 help: None,
407 };
408 let aidl_err: AidlError = parse_err.into();
409 let _box_err: Box<dyn Error> = aidl_err.into();
410 }
411
412 #[test]
414 fn test_pest_error_to_diagnostic_pos() {
415 use miette::Diagnostic;
416 let source = "a".repeat(100);
418 let err = ParseError {
419 src: NamedSource::new("test.aidl", source.clone()),
420 span: span(42, 1),
421 message: "test".to_string(),
422 help: None,
423 };
424 let labels: Vec<_> = err.labels().expect("must have labels").collect();
425 assert_eq!(labels[0].inner().offset(), 42);
426 assert_eq!(labels[0].inner().len(), 1);
427 }
428
429 #[test]
431 fn test_pest_error_to_diagnostic_span() {
432 use miette::Diagnostic;
433 let err = ParseError {
434 src: NamedSource::new("test.aidl", "0123456789abcdefghij".to_string()),
435 span: span(10, 10),
436 message: "test".to_string(),
437 help: None,
438 };
439 let labels: Vec<_> = err.labels().expect("must have labels").collect();
440 assert_eq!(labels[0].inner().offset(), 10);
441 assert_eq!(labels[0].inner().len(), 10);
442 }
443
444 #[test]
446 fn test_pest_error_to_diagnostic_eof() {
447 let source = "abc";
448 let (offset, length) = {
450 let pos = source.len();
451 let len = if pos >= source.len() { 0 } else { 1 };
452 (pos, len)
453 };
454 let err = ParseError {
455 src: NamedSource::new("test.aidl", source.to_string()),
456 span: span(offset, length),
457 message: "unexpected EOF".to_string(),
458 help: None,
459 };
460 use miette::Diagnostic;
461 let labels: Vec<_> = err.labels().expect("must have labels").collect();
462 assert_eq!(labels[0].inner().offset(), 3);
463 assert_eq!(labels[0].inner().len(), 0);
464 }
465
466 #[test]
468 fn test_aidl_error_collect_flatten() {
469 let make_parse_err = |msg: &str| {
470 AidlError::Parse(Box::new(ParseError {
471 src: NamedSource::new("test.aidl", msg.to_string()),
472 span: span(0, 1),
473 message: msg.to_string(),
474 help: None,
475 }))
476 };
477
478 let a = make_parse_err("error A");
479 let b = make_parse_err("error B");
480 let c = make_parse_err("error C");
481
482 let nested = AidlError::Multiple { errors: vec![a, b] };
484 let result = AidlError::collect(vec![nested, c]);
485
486 match result {
487 Some(AidlError::Multiple { errors }) => {
488 assert_eq!(errors.len(), 3, "Expected 3 flattened errors");
489 }
490 other => panic!("Expected Multiple, got: {other:?}"),
491 }
492 }
493
494 #[test]
496 fn test_aidl_error_collect_single() {
497 let err = AidlError::Parse(Box::new(ParseError {
498 src: NamedSource::new("test.aidl", "bad".to_string()),
499 span: span(0, 3),
500 message: "syntax error".to_string(),
501 help: None,
502 }));
503 let result = AidlError::collect(vec![err]);
504 assert!(
505 matches!(result, Some(AidlError::Parse(_))),
506 "Single error should not be wrapped in Multiple"
507 );
508 }
509
510 #[test]
512 fn test_aidl_error_collect_empty() {
513 let result = AidlError::collect(vec![]);
514 assert!(result.is_none(), "Empty collection should return None");
515 }
516}