1#![no_std]
3
4extern crate alloc;
5
6#[cfg(feature = "std")]
7extern crate std;
8
9pub mod error;
10mod post_process;
11pub mod preprocess;
12pub mod visitor;
13#[cfg(feature = "ffi")]
14pub mod ffi {
15 pub mod ast;
16}
17
18use crate::error::{Error, Result};
19use alloc::{
20 boxed::Box,
21 format,
22 string::{String, ToString},
23 vec::Vec,
24};
25use core::str::FromStr;
26use pest::Parser;
27use pest::iterators::{Pair, Pairs};
28pub use preprocess::{IdlLoader, IdlSource};
29#[cfg(feature = "std")]
30pub use preprocess::{fs::FsLoader, git::GitLoader};
31use sails_idl_ast::*;
32pub use sails_idl_ast as ast;
34
35#[derive(pest_derive::Parser)]
36#[grammar = "idl.pest"]
37pub struct IdlParser;
38
39pub fn parse_tokens(src: &str) -> Result<Pairs<'_, Rule>> {
43 let pairs = IdlParser::parse(Rule::Top, src)?;
44 Ok(pairs)
45}
46
47pub fn parse_idl(src: &str) -> Result<IdlDoc> {
49 let mut pairs = IdlParser::parse(Rule::Top, src)?;
50 let mut doc = build_idl(
51 pairs
52 .next()
53 .ok_or(Error::Rule("expected Top".to_string()))?,
54 )?;
55
56 post_process::validate_and_post_process(&mut doc)?;
57 Ok(doc)
58}
59
60pub fn parse_idl_with_loaders(path: &str, loaders: &[&dyn IdlLoader]) -> Result<IdlDoc> {
65 let src = preprocess::preprocess(path, loaders)?;
66 parse_idl(&src)
67}
68
69fn build_idl(top: Pair<Rule>) -> Result<IdlDoc> {
71 let mut globals = Vec::new();
72 let mut services = Vec::new();
73 let mut program = None;
74 for p in top.into_inner() {
75 match p.as_rule() {
76 Rule::GlobalAnn => {
77 globals.push(parse_annotation(p)?);
78 }
79 Rule::ServiceDecl => services.push(parse_service(p)?),
80 Rule::ProgramDecl if program.replace(parse_program(p)?).is_some() => {
81 return Err(Error::Validation(
82 "expected at most one program per IDL document".to_string(),
83 ));
84 }
85 _ => {}
86 }
87 }
88 Ok(IdlDoc {
89 globals,
90 services,
91 program,
92 })
93}
94
95fn parse_ident(p: Pair<Rule>) -> Result<String> {
96 if p.as_rule() == Rule::Ident {
97 return Ok(p.as_str().to_string());
98 }
99 Err(Error::Rule("expected Ident".to_string()))
100}
101
102fn parse_service_ident(p: Pair<Rule>) -> Result<ServiceIdent> {
103 if p.as_rule() == Rule::ServiceIdent {
104 p.as_str().parse::<ServiceIdent>().map_err(Error::Parse)
105 } else {
106 Err(Error::Rule("expected ServiceIdent".to_string()))
107 }
108}
109
110fn parse_annotation(p: Pair<Rule>) -> Result<Annotation> {
111 let mut key = None;
112 let mut val = None;
113 for i in p.into_inner() {
114 match i.as_rule() {
115 Rule::Ident => key = Some(i.as_str().trim().to_string()),
116 Rule::StrToEol => val = Some(i.as_str().trim().to_string()),
117 _ => {}
118 }
119 }
120 let key = key.ok_or(Error::Rule("expected Ident".to_string()))?;
121 Ok((key, val))
122}
123
124fn parse_type_decl(p: Pair<Rule>) -> Result<TypeDecl> {
125 Ok(match p.as_rule() {
126 Rule::TypeDecl => parse_type_decl(
128 p.into_inner()
129 .next()
130 .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
131 )?,
132 Rule::Tuple => {
133 let mut types = Vec::new();
134 for el in p.into_inner() {
135 types.push(parse_type_decl(el)?);
136 }
137 if types.is_empty() {
138 TypeDecl::Primitive(PrimitiveType::Void)
139 } else {
140 TypeDecl::Tuple { types }
141 }
142 }
143 Rule::Slice => {
144 let mut it = p.into_inner();
145 let ty = expect_next(&mut it, parse_type_decl)?;
146 TypeDecl::Slice { item: Box::new(ty) }
147 }
148 Rule::Array => {
149 let mut it = p.into_inner();
150 let ty = expect_next(&mut it, parse_type_decl)?;
151 let len = expect_rule(&mut it, Rule::Number)?
152 .as_str()
153 .parse::<u32>()
154 .map_err(|e| Error::Parse(e.to_string()))?;
155 TypeDecl::Array {
156 item: Box::new(ty),
157 len,
158 }
159 }
160 Rule::Primitive => {
161 let primitive_type = PrimitiveType::from_str(p.as_str()).map_err(Error::Parse)?;
162 TypeDecl::Primitive(primitive_type)
163 }
164 Rule::Named => {
165 let mut name = String::new();
166 let mut generics: Vec<TypeDecl> = Vec::new();
167 for part in p.into_inner() {
168 match part.as_rule() {
169 Rule::Ident => name = part.as_str().to_string(),
170 Rule::Generics => {
171 for t in part.into_inner() {
172 generics.push(parse_type_decl(t)?);
173 }
174 }
175 _ => {}
176 }
177 }
178 TypeDecl::Named { name, generics }
179 }
180 other => {
181 return Err(Error::Rule(format!(
182 "unexpected rule in TypeDecl: {other:?}"
183 )));
184 }
185 })
186}
187
188fn parse_param(p: Pair<'_, Rule>) -> Result<FuncParam> {
189 let mut it = p.into_inner();
190 let name = expect_next(&mut it, parse_ident)?;
191 let ty = expect_next(&mut it, parse_type_decl)?;
192 Ok(FuncParam {
193 name,
194 type_decl: ty,
195 })
196}
197
198fn parse_field(p: Pair<'_, Rule>) -> Result<StructField> {
199 let mut it = p.into_inner();
200 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
201 let part = it
202 .next()
203 .ok_or(Error::Rule("expected Ident | TypeDecl".to_string()))?;
204 let (name, type_decl) = match part.as_rule() {
205 Rule::Ident => {
206 let name = part.as_str().to_string();
207 let ty = expect_next(&mut it, parse_type_decl)?;
208 (Some(name), ty)
209 }
210 _ => (None, parse_type_decl(part)?),
211 };
212 Ok(StructField {
213 name,
214 type_decl,
215 annotations,
216 docs,
217 })
218}
219
220pub fn parse_type(p: Pair<Rule>) -> Result<Type> {
221 let mut ty = match p.as_rule() {
222 Rule::StructDecl => parse_struct_type(p),
223 Rule::EnumDecl => parse_enum_type(p),
224 Rule::AliasDecl => parse_alias_decl(p),
225 _ => Err(Error::Rule(
226 "expected StructDecl | EnumDecl | AliasDecl".to_string(),
227 )),
228 }?;
229 post_process::normalize_type_generics(&mut ty);
230 Ok(ty)
231}
232
233fn parse_alias_decl(p: Pair<Rule>) -> Result<Type> {
234 let mut it = p.into_inner();
235 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
236 let name = expect_next(&mut it, parse_ident)?;
237 let mut type_params = Vec::new();
238 let mut target = None;
239
240 for part in it {
241 match part.as_rule() {
242 Rule::TypeParams => {
243 for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
244 let name = i.as_str().to_string();
245 type_params.push(TypeParameter { name, ty: None });
246 }
247 }
248 Rule::Tuple | Rule::Slice | Rule::Array | Rule::Primitive | Rule::Named => {
249 target = Some(parse_type_decl(part)?);
250 }
251 _ => {}
252 }
253 }
254
255 let target = target.ok_or_else(|| Error::Rule("expected TypeDecl".to_string()))?;
256
257 Ok(Type {
258 name,
259 type_params,
260 def: TypeDef::Alias(AliasDef { target }),
261 docs,
262 annotations,
263 })
264}
265
266fn parse_struct_type(p: Pair<Rule>) -> Result<Type> {
267 let mut it = p.into_inner();
268 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
269 let name = expect_next(&mut it, parse_ident)?;
270 let mut type_params = Vec::new();
271 let mut fields = Vec::new();
272 for part in it {
273 match part.as_rule() {
274 Rule::TypeParams => {
275 for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
276 let name = i.as_str().to_string();
277 type_params.push(TypeParameter { name, ty: None });
278 }
279 }
280 Rule::Fields => {
281 for f in part.into_inner().filter(|x| x.as_rule() == Rule::Field) {
282 fields.push(parse_field(f)?);
283 }
284 }
285 _ => {
286 return Err(Error::Rule(
287 "expected StructDef | TupleDef | UnitDef".to_string(),
288 ));
289 }
290 };
291 }
292
293 Ok(Type {
294 name,
295 type_params,
296 def: TypeDef::Struct(StructDef { fields }),
297 docs,
298 annotations,
299 })
300}
301
302fn parse_enum_type(p: Pair<Rule>) -> Result<Type> {
303 let mut it = p.into_inner();
304 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
305 let name = expect_next(&mut it, parse_ident)?;
306 let mut type_params = Vec::new();
307 let mut variants = Vec::new();
308 for part in it {
309 match part.as_rule() {
310 Rule::TypeParams => {
311 for i in part.into_inner().filter(|x| x.as_rule() == Rule::Ident) {
312 let name = i.as_str().to_string();
313 type_params.push(TypeParameter { name, ty: None });
314 }
315 }
316 Rule::Variants => {
317 for v in part.into_inner().filter(|x| x.as_rule() == Rule::Variant) {
318 variants.push(parse_enum_variant(v)?);
319 }
320 }
321 _ => {
322 return Err(Error::Rule("expected TypeParams | Variants".to_string()));
323 }
324 };
325 }
326
327 Ok(Type {
328 name,
329 type_params,
330 def: TypeDef::Enum(EnumDef { variants }),
331 docs,
332 annotations,
333 })
334}
335
336fn parse_enum_variant(p: Pair<Rule>) -> Result<EnumVariant> {
337 let mut it = p.into_inner();
338 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
339 let name = expect_next(&mut it, parse_ident)?;
340 let mut fields = Vec::new();
341 for part in it {
342 match part.as_rule() {
343 Rule::Fields => {
344 for f in part.into_inner().filter(|x| x.as_rule() == Rule::Field) {
345 fields.push(parse_field(f)?);
346 }
347 }
348 _ => {
349 return Err(Error::Rule("expected Fields".to_string()));
350 }
351 };
352 }
353
354 Ok(EnumVariant {
355 name,
356 def: StructDef { fields },
357 entry_id: 0,
358 docs,
359 annotations,
360 })
361}
362
363fn parse_func(p: Pair<Rule>) -> Result<ServiceFunc> {
364 let mut it = p.into_inner();
365 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
366 let name = expect_next(&mut it, parse_ident)?;
367 let kind = if annotations.iter().any(|(k, _)| k == "query") {
368 FunctionKind::Query
369 } else {
370 FunctionKind::Command
371 };
372 let mut params = Vec::new();
373 let mut output = None;
374 let mut throws = None;
375 for part in it {
376 match part.as_rule() {
377 Rule::Params => {
378 for p in part.into_inner().filter(|x| x.as_rule() == Rule::Param) {
379 params.push(parse_param(p)?);
380 }
381 }
382 Rule::Ret => {
383 output = Some(parse_type_decl(
384 part.into_inner()
385 .next()
386 .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
387 )?)
388 }
389 Rule::Throws => {
390 throws = Some(parse_type_decl(
391 part.into_inner()
392 .next()
393 .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
394 )?)
395 }
396 _ => {}
397 }
398 }
399 let output = output.unwrap_or(TypeDecl::Primitive(PrimitiveType::Void));
400 Ok(ServiceFunc {
401 name,
402 params,
403 output,
404 throws,
405 kind,
406 entry_id: 0,
407 docs,
408 annotations,
409 })
410}
411
412fn parse_service(p: Pair<Rule>) -> Result<ServiceUnit> {
413 let mut it = p.into_inner();
414 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
415 let name = expect_next(&mut it, parse_service_ident)?;
416
417 let mut extends = Vec::new();
418 let mut events = Vec::new();
419 let mut funcs = Vec::new();
420 let mut types = Vec::new();
421 for item in it {
422 match item.as_rule() {
423 Rule::ExtendsBlock => {
424 for i in item.into_inner() {
425 let ident = parse_service_ident(i)?;
426 extends.push(ident);
427 }
428 }
429 Rule::EventsBlock => {
430 for e in item.into_inner().filter(|x| x.as_rule() == Rule::Variant) {
431 events.push(parse_enum_variant(e)?);
432 }
433 }
434 Rule::FunctionsBlock => {
435 for f in item.into_inner().filter(|x| x.as_rule() == Rule::FuncDecl) {
436 funcs.push(parse_func(f)?);
437 }
438 }
439 Rule::TypesBlock => {
440 for t in item.into_inner() {
441 types.push(parse_type(t)?);
442 }
443 }
444 _ => {}
445 }
446 }
447
448 let mut unit = ServiceUnit {
449 name,
450 extends,
451 events,
452 funcs,
453 types,
454 docs,
455 annotations,
456 };
457 unit.normalize();
458 Ok(unit)
459}
460
461fn parse_ctor_func(p: Pair<Rule>) -> Result<CtorFunc> {
462 let mut it = p.into_inner();
463 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
464 let name = expect_rule(&mut it, Rule::Ident)?.as_str().to_string();
465 let mut params = Vec::new();
466 let mut throws = None;
467 for part in it {
468 match part.as_rule() {
469 Rule::Params => {
470 for p in part.into_inner().filter(|x| x.as_rule() == Rule::Param) {
471 params.push(parse_param(p)?);
472 }
473 }
474 Rule::Throws => {
475 throws = Some(parse_type_decl(
476 part.into_inner()
477 .next()
478 .ok_or(Error::Rule("expected TypeDecl".to_string()))?,
479 )?)
480 }
481 _ => {}
482 }
483 }
484 Ok(CtorFunc {
485 name,
486 params,
487 throws,
488 entry_id: 0,
489 docs,
490 annotations,
491 })
492}
493
494fn parse_program(p: Pair<Rule>) -> Result<ProgramUnit> {
495 let mut it = p.into_inner();
496 let (docs, annotations) = parse_docs_and_annotations(&mut it)?;
497 let name = expect_next(&mut it, parse_ident)?;
498 let mut ctors = Vec::new();
499 let mut services = Vec::new();
500 let mut types = Vec::new();
501 for item in it {
502 match item.as_rule() {
503 Rule::ConstructorsBlock => {
504 for f in item.into_inner().filter(|x| x.as_rule() == Rule::CtorDecl) {
505 ctors.push(parse_ctor_func(f)?);
506 }
507 }
508 Rule::ServicesBlock => {
509 for s in item
510 .into_inner()
511 .filter(|x| x.as_rule() == Rule::ServiceExpo)
512 {
513 let len = services.len();
514 if len >= u8::MAX as usize {
515 return Err(Error::Validation(
516 "Too many services in program. Max: 255".to_string(),
517 ));
518 }
519 let mut sit = s.into_inner();
520 let (docs, annotations) = parse_docs_and_annotations(&mut sit)?;
521 let name = expect_next(&mut sit, parse_service_ident)?;
522 let route = if let Some(p) = sit.next()
523 && p.as_rule() == Rule::Ident
524 {
525 Some(p.as_str().to_string())
526 } else {
527 None
528 };
529 services.push(ServiceExpo {
530 name,
531 route,
532 route_idx: (len as u8) + 1,
533 docs,
534 annotations,
535 });
536 }
537 }
538 Rule::TypesBlock => {
539 for t in item.into_inner() {
540 types.push(parse_type(t)?);
541 }
542 }
543 _ => {
544 return Err(Error::Rule(
545 "expected ConstructorsBlock | ServicesBlock | TypesBlock".to_string(),
546 ));
547 }
548 }
549 }
550 let mut program = ProgramUnit {
551 name,
552 ctors,
553 services,
554 types,
555 docs,
556 annotations,
557 };
558 program.normalize();
559 Ok(program)
560}
561
562fn parse_docs_and_annotations(pairs: &mut Pairs<Rule>) -> Result<(Vec<String>, Vec<Annotation>)> {
565 let mut docs = Vec::new();
566 let mut anns = Vec::new();
567 for p in pairs.clone() {
569 match p.as_rule() {
571 Rule::DocLine => {
572 let _ = pairs.next();
574 for d in p.into_inner() {
575 if d.as_rule() == Rule::StrToEol {
576 docs.push(
577 d.as_str()
578 .strip_prefix(' ')
579 .unwrap_or(d.as_str())
580 .trim_end()
581 .to_string(),
582 );
583 }
584 }
585 }
586 Rule::LocalAnn => {
587 let _ = pairs.next();
589 let ann = parse_annotation(p)?;
590 if ann.0 == "doc" {
591 if let Some(val) = ann.1 {
592 docs.push(val);
593 }
594 } else {
595 anns.push(ann);
596 }
597 }
598 _ => break,
599 }
600 }
601 Ok((docs, anns))
602}
603
604fn expect_next<'a, F: FnOnce(Pair<'a, Rule>) -> Result<T>, T>(
605 it: &mut impl Iterator<Item = Pair<'a, Rule>>,
606 f: F,
607) -> Result<T> {
608 if let Some(p) = it.next() {
609 return f(p);
610 }
611 Err(Error::Rule("expected next Rule".to_string()))
612}
613
614fn expect_rule<'a>(
615 it: &mut impl Iterator<Item = Pair<'a, Rule>>,
616 r: Rule,
617) -> Result<Pair<'a, Rule>> {
618 if let Some(p) = it.next()
619 && p.as_rule() == r
620 {
621 return Ok(p);
622 }
623 Err(Error::Rule(format!("expected {r:?}")))
624}
625
626#[cfg(test)]
628mod tests {
629 use super::*;
630 use alloc::vec;
631
632 #[test]
633 fn parse_docs_and_annotations_lines() {
634 const SRC: &str = r#"
635 // Comment
636 /// Defines status of some point as colored by somebody or dead for some reason.
637 /// Dead point - won't be available for coloring anymore.
638 @indexed
639 "#;
640
641 let mut pairs = IdlParser::parse(Rule::DocsAndAnnotations, SRC).expect("parse idl");
642 let (docs, anns) = parse_docs_and_annotations(&mut pairs).expect("parse annotations");
643
644 assert_eq!(
645 docs,
646 vec![
647 "Defines status of some point as colored by somebody or dead for some reason.",
648 "Dead point - won't be available for coloring anymore."
649 ],
650 );
651 assert_eq!(anns, vec![("indexed".to_string(), None)])
652 }
653
654 #[test]
655 fn parse_docs_with_blank_doc_separator() {
656 const SRC: &str = r#"
657 /// Returns the number of roles assigned to the specified member.
658 ///
659 /// # Arguments
660 /// * `member_id` - The account identifier.
661 "#;
662
663 let mut pairs = IdlParser::parse(Rule::DocsAndAnnotations, SRC).expect("parse idl");
664 let (docs, anns) = parse_docs_and_annotations(&mut pairs).expect("parse annotations");
665
666 assert_eq!(
667 docs,
668 vec![
669 "Returns the number of roles assigned to the specified member.",
670 "",
671 "# Arguments",
672 "* `member_id` - The account identifier.",
673 ],
674 );
675 assert!(anns.is_empty());
676 }
677
678 #[test]
679 fn parse_global_annotations() {
680 const SRC: &str = r#"
681 // Global annotations
682 !@sails: 0.1.0
683 !@include: ownable.idl
684 !@include: git://github.com/some_repo/tippable.idl
685 "#;
686
687 let doc = parse_idl(SRC).expect("parse idl");
688
689 assert_eq!(3, doc.globals.len());
690 }
691
692 #[test]
693 fn parse_vector_of_tuples() {
694 use PrimitiveType::*;
695 use TypeDecl::*;
696
697 const SRC: &str = r#"[(Point<u32>, Option<PointStatus>, u32)]"#;
698 let mut pairs = IdlParser::parse(Rule::TypeDecl, SRC).expect("parse idl");
699 let ty = expect_next(&mut pairs, parse_type_decl).expect("parse TypeDecl");
700
701 assert_eq!(
702 ty,
703 Slice {
704 item: Box::new(Tuple {
705 types: vec![
706 Named {
707 name: "Point".to_string(),
708 generics: vec![Primitive(U32)]
709 },
710 TypeDecl::option(TypeDecl::named("PointStatus".to_string())),
711 Primitive(U32)
712 ]
713 })
714 }
715 );
716 }
717
718 #[test]
719 fn pars_service_func() {
720 use PrimitiveType::*;
721 use TypeDecl::*;
722
723 const SRC: &str = r#"
724 /// Sets color for the point.
725 /// app -> `fn color_point(&mut self, point: Point<u32>, color: Color) -> Result<(), ColorError>`
726 /// On `Ok` - auto-reply. On `Err` -> app will encode error bytes of `ColorError` (`gr_panic_bytes`).
727 ColorPoint(point: (u32, u32), color: Color) throws ColorError;"#;
728 let mut pairs = IdlParser::parse(Rule::FuncDecl, SRC).expect("parse idl");
729
730 let func = parse_func(pairs.next().expect("FuncDecl")).expect("parse func");
731
732 assert_eq!(
733 func,
734 ServiceFunc {
735 name: "ColorPoint".to_string(),
736 params: vec![
737 FuncParam {
738 name: "point".to_string(),
739 type_decl: TypeDecl::tuple(vec![Primitive(U32), Primitive(U32)])
740 },
741 FuncParam {
742 name: "color".to_string(),
743 type_decl: TypeDecl::named("Color".to_string())
744 }
745 ],
746 output: Primitive(Void),
747 throws: Some(Named {
748 name: "ColorError".to_string(),
749 generics: vec![]
750 }),
751 kind: FunctionKind::Command,
752 entry_id: 0,
753 annotations: vec![],
754 docs: vec![
755 "Sets color for the point.".to_string(),
756 "app -> `fn color_point(&mut self, point: Point<u32>, color: Color) -> Result<(), ColorError>`".to_string(),
757 "On `Ok` - auto-reply. On `Err` -> app will encode error bytes of `ColorError` (`gr_panic_bytes`).".to_string(),
758 ],
759 }
760 );
761 }
762
763 #[test]
764 fn parse_minimal_service() {
765 const SRC: &str = r#"
766 /// Example
767 service X {
768 functions { Ping() -> bool; }
769 events { E }
770 types { struct T; }
771 }
772 "#;
773 let mut pairs = IdlParser::parse(Rule::ServiceDecl, SRC).expect("parse idl");
774 let svc = expect_next(&mut pairs, parse_service).expect("parse");
775 assert_eq!(svc.name.to_string(), "X");
776 assert!(svc.funcs.iter().any(|f| f.name == "Ping"));
777 }
778
779 #[test]
780 fn parse_test_idl() {
781 const SRC: &str = include_str!("../tests/idls/test.idl");
782
783 let doc = parse_idl(SRC).expect("parse idl");
784
785 assert_eq!(2, doc.globals.len());
786 assert_eq!(4, doc.services.len());
787 }
788
789 #[test]
790 fn parse_demo_idl() {
791 const SRC: &str = include_str!("../tests/idls/demo.idl");
792
793 let doc = parse_idl(SRC).expect("parse idl");
794
795 assert_eq!(3, doc.globals.len());
796 }
797
798 #[test]
799 fn parse_idl_rejects_multiple_programs() {
800 const SRC: &str = r#"
801 program First {}
802 program Second {}
803 "#;
804
805 let err = parse_idl(SRC).expect_err("multiple programs should fail");
806 assert!(matches!(err, Error::Validation(_)));
807 assert!(
808 err.to_string()
809 .contains("expected at most one program per IDL document")
810 );
811 }
812
813 #[test]
814 fn parse_alias_decl() {
815 const SRC: &str = r#"alias AliasName = u32;"#;
816
817 let mut pairs = IdlParser::parse(Rule::AliasDecl, SRC).expect("parse alias");
818 let ty = parse_type(pairs.next().expect("alias")).expect("alias should be supported");
819
820 assert_eq!(ty.name, "AliasName");
821 assert!(matches!(ty.def, TypeDef::Alias(_)));
822 if let TypeDef::Alias(alias) = ty.def {
823 assert!(matches!(
824 alias.target,
825 TypeDecl::Primitive(PrimitiveType::U32)
826 ));
827 }
828 }
829
830 #[test]
831 fn parse_type_normalizes_declared_generic_refs() {
832 const SRC: &str = r#"struct Wrapper<T> { value: T }"#;
833
834 let mut pairs = IdlParser::parse(Rule::StructDecl, SRC).expect("parse struct");
835 let ty = parse_type(pairs.next().expect("struct")).expect("parse type");
836
837 let TypeDef::Struct(def) = ty.def else {
838 panic!("expected struct");
839 };
840 assert_eq!(
841 def.fields[0].type_decl,
842 TypeDecl::Generic {
843 name: "T".to_string()
844 },
845 );
846 }
847
848 #[test]
849 fn parse_idl_rejects_self_extends() {
850 const SRC: &str = r#"
853 service A {
854 extends { A }
855 functions { Ping() -> bool; }
856 }
857 "#;
858
859 let err = parse_idl(SRC).expect_err("self-extends should fail");
860 assert!(matches!(err, Error::Validation(_)));
861 assert!(err.to_string().contains("cyclic"));
862 }
863
864 #[test]
865 fn parse_idl_rejects_extends_cycle() {
866 const SRC: &str = r#"
868 service A {
869 extends { B }
870 functions { Ping() -> bool; }
871 }
872 service B {
873 extends { A }
874 functions { Pong() -> bool; }
875 }
876 "#;
877
878 let err = parse_idl(SRC).expect_err("cycle should fail");
879 assert!(matches!(err, Error::Validation(_)));
880 assert!(err.to_string().contains("cyclic"));
881 }
882
883 #[test]
884 fn parse_idl_rejects_duplicate_service_names() {
885 const SRC: &str = r#"
888 service S {
889 functions { A() -> bool; }
890 }
891 service S {
892 functions { B() -> bool; }
893 }
894 "#;
895
896 let err = parse_idl(SRC).expect_err("duplicate names should fail");
897 assert!(matches!(err, Error::Validation(_)));
898 assert!(err.to_string().contains("duplicate"));
899 }
900}