1extern crate alloc;
6
7use alloc::boxed::Box;
8use alloc::string::{String, ToString};
9use alloc::vec::Vec;
10
11use crate::document::{EureDocument, NodeId};
12use crate::identifier::Identifier;
13use crate::parse::{DocumentParser, FromEure};
14
15use super::variant_path::VariantPath;
16use super::{
17 AccessedSet, AccessedSnapshot, BestParseVariantMatch, ParseContext, ParseError, ParseErrorKind,
18 UnionParseError,
19};
20
21pub const VARIANT: Identifier = Identifier::new_unchecked("variant");
23
24pub fn extract_explicit_variant_path(
26 doc: &EureDocument,
27 node_id: NodeId,
28) -> Result<Option<VariantPath>, ParseError> {
29 let node = doc.node(node_id);
30 let Some(&variant_node_id) = node.extensions.get(&VARIANT) else {
31 return Ok(None);
32 };
33
34 let variant_node = doc.node(variant_node_id);
35 let s: &str = doc.parse(variant_node_id).map_err(|_| ParseError {
36 node_id: variant_node_id,
37 kind: ParseErrorKind::InvalidVariantType(variant_node.content.value_kind()),
38 })?;
39
40 VariantPath::parse(s).map(Some).map_err(|_| ParseError {
41 node_id: variant_node_id,
42 kind: ParseErrorKind::InvalidVariantPath(s.to_string()),
43 })
44}
45
46pub fn has_explicit_variant_tag(doc: &EureDocument, node_id: NodeId) -> Result<bool, ParseError> {
49 Ok(extract_explicit_variant_path(doc, node_id)?.is_some())
50}
51
52pub struct UnionParser<'doc, 'ctx, T, E = ParseError> {
88 ctx: &'ctx ParseContext<'doc>,
89 variant: Option<(String, ParseContext<'doc>, Option<VariantPath>)>,
94 variant_result: Option<Result<T, E>>,
96 priority_result: Option<T>,
98 other_results: Vec<(String, T, AccessedSnapshot)>,
101 failures: Vec<(String, E)>,
103 accessed: AccessedSet,
105}
106
107impl<'doc, 'ctx, T, E> UnionParser<'doc, 'ctx, T, E>
108where
109 E: UnionParseError,
110{
111 pub(crate) fn new(ctx: &'ctx ParseContext<'doc>) -> Result<Self, ParseError> {
115 let variant = Self::resolve_variant(ctx)?;
116 let accessed = ctx.accessed().clone();
117 accessed.push_snapshot();
118
119 Ok(Self {
120 ctx,
121 variant,
122 variant_result: None,
123 priority_result: None,
124 other_results: Vec::new(),
125 failures: Vec::new(),
126 accessed,
127 })
128 }
129
130 fn resolve_variant(
137 ctx: &ParseContext<'doc>,
138 ) -> Result<Option<(String, ParseContext<'doc>, Option<VariantPath>)>, ParseError> {
139 let explicit_variant = match ctx.variant_path() {
141 Some(vp) if !vp.is_empty() => Some(vp.clone()),
142 Some(_) => None, None => {
144 let variant = Self::extract_explicit_variant(ctx)?;
145 if variant.is_some() {
146 ctx.accessed().add_ext(VARIANT.clone());
148 }
149 variant
150 }
151 };
152
153 match explicit_variant {
154 Some(ev) => {
156 let name = ev
157 .first()
158 .map(|i| i.as_ref().to_string())
159 .unwrap_or_default();
160 let rest = ev.rest().unwrap_or_else(VariantPath::empty);
161 Ok(Some((name, ctx.clone(), Some(rest))))
162 }
163 None => Ok(None),
165 }
166 }
167
168 fn extract_explicit_variant(
170 ctx: &ParseContext<'doc>,
171 ) -> Result<Option<VariantPath>, ParseError> {
172 extract_explicit_variant_path(ctx.doc(), ctx.node_id())
173 }
174
175 pub fn variant<P: DocumentParser<'doc, Output = T, Error = E>>(
180 mut self,
181 name: &str,
182 f: P,
183 ) -> Self {
184 self.try_variant(name, f, true);
185 self
186 }
187
188 pub fn parse_variant<V: FromEure<'doc, Error = E>>(
190 mut self,
191 name: &str,
192 mut then: impl FnMut(V) -> Result<T, E>,
193 ) -> Self {
194 self.try_variant(
195 name,
196 move |ctx: &ParseContext<'doc>| {
197 let v = V::parse(ctx)?;
198 then(v)
199 },
200 true,
201 );
202 self
203 }
204
205 pub fn variant_unambiguous<P: DocumentParser<'doc, Output = T, Error = E>>(
211 mut self,
212 name: &str,
213 f: P,
214 ) -> Self {
215 self.try_variant(name, f, false);
216 self
217 }
218
219 pub fn parse_variant_unambiguous<V: FromEure<'doc, Error = E>>(
221 mut self,
222 name: &str,
223 mut then: impl FnMut(V) -> Result<T, E>,
224 ) -> Self {
225 self.try_variant(
226 name,
227 move |ctx: &ParseContext<'doc>| {
228 let v = V::parse(ctx)?;
229 then(v)
230 },
231 false,
232 );
233 self
234 }
235
236 fn try_variant<P: DocumentParser<'doc, Output = T, Error = E>>(
238 &mut self,
239 name: &str,
240 mut f: P,
241 is_priority: bool,
242 ) {
243 if let Some((ref v_name, ref v_ctx, ref rest)) = self.variant {
245 if v_name == name && self.variant_result.is_none() {
246 let child_ctx = v_ctx.with_variant_rest(rest.clone());
247 let result = f.parse(&child_ctx);
248 self.variant_result = Some(result);
249 }
250 return;
251 }
252
253 if self.priority_result.is_some() {
257 return;
258 }
259
260 let child_ctx = self.ctx.with_variant_rest(None);
261 match f.parse(&child_ctx) {
262 Ok(value) => {
263 if is_priority {
264 self.priority_result = Some(value);
265 } else {
266 let captured = self.accessed.capture_current_state();
267 self.accessed.restore_to_current_snapshot();
268 self.other_results.push((name.to_string(), value, captured));
269 }
270 }
271 Err(e) => {
272 self.accessed.restore_to_current_snapshot();
273 self.failures.push((name.to_string(), e));
274 }
275 }
276 }
277
278 pub fn parse(self) -> Result<T, E> {
280 let node_id = self.ctx.node_id();
281
282 if let Some((v_name, _, _)) = self.variant {
284 let result = self.variant_result.unwrap_or_else(|| {
285 Err(ParseError {
286 node_id,
287 kind: ParseErrorKind::UnknownVariant(v_name),
288 }
289 .into())
290 });
291 match &result {
292 Ok(_) => self.accessed.pop_without_restore(),
293 Err(_) => self.accessed.pop_and_restore(),
294 }
295 return result;
296 }
297
298 if let Some(value) = self.priority_result {
300 self.accessed.pop_without_restore();
301 return Ok(value);
302 }
303
304 match self.other_results.len() {
306 0 => {
307 self.accessed.pop_and_restore();
308 Err(self.no_match_error(node_id))
309 }
310 1 => {
311 let (_, value, captured_state) = self.other_results.into_iter().next().unwrap();
312 self.accessed.restore_to_state(captured_state);
313 self.accessed.pop_without_restore();
314 Ok(value)
315 }
316 _ => {
317 self.accessed.pop_and_restore();
318 Err(ParseError {
319 node_id,
320 kind: ParseErrorKind::AmbiguousUnion(
321 self.other_results
322 .into_iter()
323 .map(|(name, _, _)| name)
324 .collect(),
325 ),
326 }
327 .into())
328 }
329 }
330 }
331
332 fn no_match_error(self, node_id: crate::document::NodeId) -> E {
334 E::from_no_matching_variant(
335 node_id,
336 None,
337 select_best_parse_variant_match(&self.failures),
338 &self.failures,
339 )
340 }
341}
342
343fn select_best_parse_variant_match<E>(failures: &[(String, E)]) -> Option<BestParseVariantMatch>
344where
345 E: UnionParseError,
346{
347 failures
348 .iter()
349 .filter_map(|(variant_name, error)| {
350 error
351 .as_parse_error()
352 .map(|parse_error| (variant_name, parse_error))
353 })
354 .max_by_key(|(_, parse_error)| parse_error_match_metrics(parse_error))
355 .map(|(variant_name, parse_error)| BestParseVariantMatch {
356 variant_name: variant_name.clone(),
357 error: Box::new(parse_error.clone()),
358 })
359}
360
361fn parse_error_match_metrics(error: &ParseError) -> (bool, usize, u8) {
362 parse_error_kind_metrics(&error.kind)
363}
364
365fn parse_error_kind_metrics(kind: &ParseErrorKind) -> (bool, usize, u8) {
366 match kind {
367 ParseErrorKind::Nested { source, .. } => {
368 let (structural, depth, priority) = parse_error_kind_metrics(source);
369 (structural, depth + 1, priority)
370 }
371 ParseErrorKind::NoMatchingVariant {
372 best_match: Some(best),
373 ..
374 } => {
375 let (structural, depth, priority) = parse_error_match_metrics(&best.error);
376 (structural, depth + 1, priority)
377 }
378 _ => (
379 is_structural_parse_mismatch(kind),
380 1,
381 parse_error_priority(kind),
382 ),
383 }
384}
385
386fn is_structural_parse_mismatch(kind: &ParseErrorKind) -> bool {
387 matches!(
388 kind,
389 ParseErrorKind::MissingField(_)
390 | ParseErrorKind::MissingExtension(_)
391 | ParseErrorKind::UnknownField(_)
392 | ParseErrorKind::UnknownExtension(_)
393 | ParseErrorKind::LiteralMismatch { .. }
394 | ParseErrorKind::InvalidPattern { .. }
395 )
396}
397
398fn parse_error_priority(kind: &ParseErrorKind) -> u8 {
399 match kind {
400 ParseErrorKind::UnknownField(_) | ParseErrorKind::UnknownExtension(_) => 4,
401 ParseErrorKind::MissingField(_) | ParseErrorKind::MissingExtension(_) => 3,
402 ParseErrorKind::UnknownVariant(_) | ParseErrorKind::UnexpectedVariantPath(_) => 2,
403 ParseErrorKind::LiteralMismatch { .. } | ParseErrorKind::InvalidPattern { .. } => 2,
404 ParseErrorKind::TypeMismatch { .. }
405 | ParseErrorKind::UnexpectedTupleLength { .. }
406 | ParseErrorKind::UnexpectedArrayLength { .. }
407 | ParseErrorKind::NotPrimitive { .. } => 1,
408 _ => 0,
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use crate::eure;
416 use crate::parse::AlwaysParser;
417 use crate::parse::DocumentParserExt as _;
418
419 #[derive(Debug, PartialEq, Clone)]
420 enum TestEnum {
421 Foo,
422 Bar,
423 }
424
425 #[test]
426 fn test_union_single_match() {
427 let doc = eure!({ = "foo" });
428 let root_id = doc.get_root_id();
429 let ctx = doc.parse_context(root_id);
430
431 let result: TestEnum = ctx
432 .parse_union()
433 .unwrap()
434 .variant("foo", |ctx: &ParseContext<'_>| {
435 let s: &str = ctx.parse()?;
436 if s == "foo" {
437 Ok(TestEnum::Foo)
438 } else {
439 Err(ParseError {
440 node_id: ctx.node_id(),
441 kind: ParseErrorKind::UnknownVariant(s.to_string()),
442 })
443 }
444 })
445 .variant("bar", |ctx: &ParseContext<'_>| {
446 let s: &str = ctx.parse()?;
447 if s == "bar" {
448 Ok(TestEnum::Bar)
449 } else {
450 Err(ParseError {
451 node_id: ctx.node_id(),
452 kind: ParseErrorKind::UnknownVariant(s.to_string()),
453 })
454 }
455 })
456 .parse()
457 .unwrap();
458
459 assert_eq!(result, TestEnum::Foo);
460 }
461
462 #[test]
463 fn test_union_priority_short_circuit() {
464 let doc = eure!({ = "value" });
465 let root_id = doc.get_root_id();
466 let ctx = doc.parse_context(root_id);
467
468 let result: String = ctx
470 .parse_union()
471 .unwrap()
472 .variant("first", String::parse)
473 .variant("second", String::parse)
474 .parse()
475 .unwrap();
476
477 assert_eq!(result, "value");
478 }
479
480 #[test]
481 fn test_union_no_match() {
482 let doc = eure!({ = "baz" });
483 let root_id = doc.get_root_id();
484 let ctx = doc.parse_context(root_id);
485
486 let result: Result<TestEnum, ParseError> = ctx
487 .parse_union()
488 .unwrap()
489 .variant("foo", |ctx: &ParseContext<'_>| {
490 let s: &str = ctx.parse()?;
491 if s == "foo" {
492 Ok(TestEnum::Foo)
493 } else {
494 Err(ParseError {
495 node_id: ctx.node_id(),
496 kind: ParseErrorKind::UnknownVariant(s.to_string()),
497 })
498 }
499 })
500 .parse();
501
502 assert!(result.is_err());
503 }
504
505 #[test]
508 fn test_variant_extension_match_success() {
509 let doc = eure!({ %variant = "baz", = "anything" });
512 let root_id = doc.get_root_id();
513 let ctx = doc.parse_context(root_id);
514
515 let result: TestEnum = ctx
516 .parse_union()
517 .unwrap()
518 .variant(
519 "foo",
520 AlwaysParser::<TestEnum, ParseError>::new(TestEnum::Foo),
521 )
522 .variant_unambiguous("baz", AlwaysParser::new(TestEnum::Bar))
523 .parse()
524 .unwrap();
525
526 assert_eq!(result, TestEnum::Bar);
527 }
528
529 #[test]
530 fn test_variant_extension_unknown() {
531 let doc = eure!({ %variant = "unknown", = "anything" });
534 let root_id = doc.get_root_id();
535 let ctx = doc.parse_context(root_id);
536
537 let err: ParseError = ctx
538 .parse_union()
539 .unwrap()
540 .variant("foo", AlwaysParser::new(TestEnum::Foo))
541 .variant_unambiguous("baz", AlwaysParser::new(TestEnum::Bar))
542 .parse()
543 .unwrap_err();
544
545 assert_eq!(err.node_id, root_id);
546 assert_eq!(
547 err.kind,
548 ParseErrorKind::UnknownVariant("unknown".to_string())
549 );
550 }
551
552 #[test]
553 fn test_variant_extension_match_parse_failure() {
554 let doc = eure!({ %variant = "baz", = "anything" });
556 let root_id = doc.get_root_id();
557 let ctx = doc.parse_context(root_id);
558
559 let err = ctx
560 .parse_union()
561 .unwrap()
562 .variant("foo", AlwaysParser::new(TestEnum::Foo))
563 .variant_unambiguous("baz", |ctx: &ParseContext<'_>| {
564 Err(ParseError {
565 node_id: ctx.node_id(),
566 kind: ParseErrorKind::MissingField("test".to_string()),
567 })
568 })
569 .parse()
570 .unwrap_err();
571
572 assert_eq!(err.node_id, root_id);
574 assert_eq!(err.kind, ParseErrorKind::MissingField("test".to_string()));
575 }
576
577 #[test]
578 fn test_union_rolls_back_accessed_fields_between_untagged_variants() {
579 let doc = eure!({ age = 42 });
580 let root_id = doc.get_root_id();
581 let ctx = doc.parse_context(root_id);
582
583 let err = ctx
584 .parse_union()
585 .unwrap()
586 .variant("full", |ctx: &ParseContext<'_>| {
587 let rec = ctx.parse_record()?;
588 let _: Option<i64> = rec.parse_field_optional("age")?;
589 let name: Option<String> = rec.parse_field_optional("name")?;
590 rec.deny_unknown_fields()?;
591 if name.is_none() {
592 return Err(ParseError {
593 node_id: ctx.node_id(),
594 kind: ParseErrorKind::MissingField("name".to_string()),
595 });
596 }
597 Ok(TestEnum::Foo)
598 })
599 .variant("minimal", |ctx: &ParseContext<'_>| {
600 let rec = ctx.parse_record()?;
601 let unknown_fields: Vec<_> = rec
602 .unknown_fields()
603 .filter_map(|result| match result {
604 Ok((field_name, _)) => Some(field_name.to_string()),
605 Err(_) => None,
606 })
607 .collect();
608 if unknown_fields.iter().any(|field| field == "age") {
609 return Err(ParseError {
610 node_id: ctx.node_id(),
611 kind: ParseErrorKind::UnknownField("age".to_string()),
612 });
613 }
614 let name: Option<String> = rec.parse_field_optional("name")?;
615 rec.deny_unknown_fields()?;
616 if name.is_none() {
617 return Err(ParseError {
618 node_id: ctx.node_id(),
619 kind: ParseErrorKind::MissingField("name".to_string()),
620 });
621 }
622 Ok(TestEnum::Bar)
623 })
624 .parse()
625 .unwrap_err();
626
627 let ParseErrorKind::NoMatchingVariant {
628 best_match: Some(best_match),
629 ..
630 } = err.kind
631 else {
632 panic!("expected no matching variant with best match, got {err:?}");
633 };
634 assert_eq!(best_match.variant_name, "minimal");
635 assert_eq!(
636 best_match.error.kind,
637 ParseErrorKind::UnknownField("age".to_string())
638 );
639 }
640
641 #[derive(Debug, PartialEq, Clone)]
644 enum Outer {
645 A(Inner),
646 B(i32),
647 }
648
649 #[derive(Debug, PartialEq, Clone)]
650 enum Inner {
651 X,
652 Y,
653 }
654
655 fn parse_inner(ctx: &ParseContext<'_>) -> Result<Inner, ParseError> {
656 ctx.parse_union()
657 .unwrap()
658 .variant("x", AlwaysParser::new(Inner::X))
659 .variant("y", AlwaysParser::new(Inner::Y))
660 .parse()
661 }
662
663 #[test]
664 fn test_variant_nested_single_segment() {
665 let doc = eure!({ %variant = "a", = "value" });
667 let root_id = doc.get_root_id();
668 let ctx = doc.parse_context(root_id);
669
670 let result: Outer = ctx
671 .parse_union()
672 .unwrap()
673 .variant("a", parse_inner.map(Outer::A))
674 .variant("b", AlwaysParser::new(Outer::B(42)))
675 .parse()
676 .unwrap();
677
678 assert_eq!(result, Outer::A(Inner::X));
679 }
680
681 #[test]
682 fn test_variant_nested_multi_segment() {
683 let doc = eure!({ %variant = "a.y", = "value" });
685 let root_id = doc.get_root_id();
686 let ctx = doc.parse_context(root_id);
687
688 let result: Outer = ctx
689 .parse_union()
690 .unwrap()
691 .variant("a", parse_inner.map(Outer::A))
692 .variant("b", AlwaysParser::new(Outer::B(42)))
693 .parse()
694 .unwrap();
695
696 assert_eq!(result, Outer::A(Inner::Y));
697 }
698
699 #[test]
700 fn test_variant_nested_invalid_inner() {
701 let doc = eure!({ %variant = "a.z", = "value" });
703 let root_id = doc.get_root_id();
704 let ctx = doc.parse_context(root_id);
705
706 let err = ctx
707 .parse_union()
708 .unwrap()
709 .variant("a", parse_inner.map(Outer::A))
710 .variant("b", AlwaysParser::new(Outer::B(42)))
711 .parse()
712 .unwrap_err();
713
714 assert_eq!(err.kind, ParseErrorKind::UnknownVariant("z".to_string()));
715 }
716
717 #[test]
718 fn test_variant_non_nested_with_nested_path() {
719 let doc = eure!({ %variant = "b.x", = "value" });
723 let root_id = doc.get_root_id();
724 let ctx = doc.parse_context(root_id);
725
726 let err = ctx
731 .parse_union()
732 .unwrap()
733 .variant("a", parse_inner.map(Outer::A))
734 .variant("b", |ctx: &ParseContext<'_>| {
735 ctx.parse_primitive()?;
737 Ok(Outer::B(42))
738 })
739 .parse()
740 .unwrap_err();
741
742 assert!(matches!(err.kind, ParseErrorKind::UnexpectedVariantPath(_)));
744 }
745
746 use crate::value::ValueKind;
749
750 #[test]
751 fn test_invalid_variant_type_errors() {
752 use crate::document::node::NodeValue;
755 use crate::value::PrimitiveValue;
756 use num_bigint::BigInt;
757
758 let mut doc = eure!({ = "foo" });
760 let root_id = doc.get_root_id();
761 let variant_node_id = doc
762 .add_extension("variant".parse().unwrap(), root_id)
763 .unwrap()
764 .node_id;
765 doc.node_mut(variant_node_id).content =
766 NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(123)));
767
768 let ctx = doc.parse_context(root_id);
769
770 let Err(err) = ctx.parse_union::<TestEnum, ParseError>() else {
771 panic!("Expected error");
772 };
773 assert_eq!(
774 err,
775 ParseError {
776 node_id: variant_node_id,
777 kind: ParseErrorKind::InvalidVariantType(ValueKind::Integer),
778 }
779 );
780 }
781
782 #[test]
783 fn test_invalid_variant_path_syntax_errors() {
784 let doc = eure!({ %variant = "foo..bar", = "foo" });
786 let root_id = doc.get_root_id();
787 let variant_node_id = *doc.node(root_id).extensions.get(&VARIANT).unwrap();
788 let ctx = doc.parse_context(root_id);
789
790 let Err(err) = ctx.parse_union::<TestEnum, ParseError>() else {
791 panic!("Expected error");
792 };
793 assert_eq!(
794 err,
795 ParseError {
796 node_id: variant_node_id,
797 kind: ParseErrorKind::InvalidVariantPath("foo..bar".to_string()),
798 }
799 );
800 }
801
802 #[test]
803 fn test_variant_path_empty_uses_untagged() {
804 let doc = eure!({ = "value" });
807 let root_id = doc.get_root_id();
808 let ctx = doc.parse_context(root_id);
809
810 let child_ctx = ctx.with_variant_rest(Some(VariantPath::empty()));
812
813 let result: String = child_ctx
815 .parse_union()
816 .unwrap()
817 .variant("first", String::parse)
818 .variant("second", String::parse)
819 .parse()
820 .unwrap();
821
822 assert_eq!(result, "value");
824 }
825
826 #[derive(Debug, PartialEq, Clone)]
832 enum OuterUnion {
833 Normal(InnerUnion),
834 List(Vec<InnerUnion>),
835 }
836
837 #[derive(Debug, PartialEq, Clone)]
839 enum InnerUnion {
840 Text(String),
841 Number(i64),
842 }
843
844 fn parse_inner_union(ctx: &ParseContext<'_>) -> Result<InnerUnion, ParseError> {
845 ctx.parse_union()?
846 .variant("text", |ctx: &ParseContext<'_>| {
847 let s: String = ctx.parse()?;
848 Ok(InnerUnion::Text(s))
849 })
850 .variant("number", |ctx: &ParseContext<'_>| {
851 let n: i64 = ctx.parse()?;
852 Ok(InnerUnion::Number(n))
853 })
854 .parse()
855 }
856
857 fn parse_outer_union(ctx: &ParseContext<'_>) -> Result<OuterUnion, ParseError> {
858 use crate::document::node::NodeArray;
859
860 ctx.parse_union()?
861 .variant("normal", |ctx: &ParseContext<'_>| {
862 let inner = parse_inner_union(ctx)?;
863 Ok(OuterUnion::Normal(inner))
864 })
865 .variant("list", |ctx: &ParseContext<'_>| {
866 let arr: &NodeArray = ctx.parse()?;
868 let items: Result<Vec<InnerUnion>, _> = arr
869 .iter()
870 .map(|&node_id| parse_inner_union(&ctx.at(node_id)))
871 .collect();
872 Ok(OuterUnion::List(items?))
873 })
874 .parse()
875 }
876
877 #[test]
878 fn test_nested_union_basic_text() {
879 let doc = eure!({ = "hello" });
881 let root_id = doc.get_root_id();
882 let ctx = doc.parse_context(root_id);
883
884 let result = parse_outer_union(&ctx).unwrap();
885 assert_eq!(
886 result,
887 OuterUnion::Normal(InnerUnion::Text("hello".to_string()))
888 );
889 }
890
891 #[test]
892 fn test_nested_union_basic_number() {
893 let doc = eure!({ = 42 });
894 let root_id = doc.get_root_id();
895 let ctx = doc.parse_context(root_id);
896 let result = parse_outer_union(&ctx).unwrap();
897 assert_eq!(result, OuterUnion::Normal(InnerUnion::Number(42)));
898 }
899
900 #[test]
901 fn test_nested_union_variant_path_propagation() {
902 let doc = eure!({ %variant = "normal.text", = "test value" });
904 let root_id = doc.get_root_id();
905 let ctx = doc.parse_context(root_id);
906
907 let result = parse_outer_union(&ctx).unwrap();
908 assert_eq!(
909 result,
910 OuterUnion::Normal(InnerUnion::Text("test value".to_string()))
911 );
912 }
913
914 #[test]
915 fn test_nested_union_variant_path_number() {
916 let doc = eure!({ %variant = "normal.number", = 99 });
918 let root_id = doc.get_root_id();
919 let ctx = doc.parse_context(root_id);
920 let result = parse_outer_union(&ctx).unwrap();
921 assert_eq!(result, OuterUnion::Normal(InnerUnion::Number(99)));
922 }
923
924 #[test]
925 fn test_nested_union_inner_fails_outer_recovers() {
926 let doc = eure!({ = ["a", "b"] });
930 let root_id = doc.get_root_id();
931 let ctx = doc.parse_context(root_id);
932
933 let result = parse_outer_union(&ctx).unwrap();
934 assert_eq!(
935 result,
936 OuterUnion::List(alloc::vec![
937 InnerUnion::Text("a".to_string()),
938 InnerUnion::Text("b".to_string()),
939 ])
940 );
941 }
942
943 #[derive(Debug, PartialEq, Clone)]
948 enum Level1 {
949 A(Level2Union),
950 B(String),
951 }
952
953 #[derive(Debug, PartialEq, Clone)]
954 enum Level2Union {
955 X(Level3),
956 Y(i64),
957 }
958
959 #[derive(Debug, PartialEq, Clone)]
960 enum Level3 {
961 Leaf(String),
962 }
963
964 fn parse_level3(ctx: &ParseContext<'_>) -> Result<Level3, ParseError> {
965 ctx.parse_union()?
966 .variant("leaf", |ctx: &ParseContext<'_>| {
967 let s: String = ctx.parse()?;
968 Ok(Level3::Leaf(s))
969 })
970 .parse()
971 }
972
973 fn parse_level2(ctx: &ParseContext<'_>) -> Result<Level2Union, ParseError> {
974 ctx.parse_union()?
975 .variant("x", |ctx: &ParseContext<'_>| {
976 let inner = parse_level3(ctx)?;
977 Ok(Level2Union::X(inner))
978 })
979 .variant("y", |ctx: &ParseContext<'_>| {
980 let n: i64 = ctx.parse()?;
981 Ok(Level2Union::Y(n))
982 })
983 .parse()
984 }
985
986 fn parse_level1(ctx: &ParseContext<'_>) -> Result<Level1, ParseError> {
987 ctx.parse_union()?
988 .variant("a", |ctx: &ParseContext<'_>| {
989 let inner = parse_level2(ctx)?;
990 Ok(Level1::A(inner))
991 })
992 .variant("b", |ctx: &ParseContext<'_>| {
993 let s: String = ctx.parse()?;
994 Ok(Level1::B(s))
995 })
996 .parse()
997 }
998
999 #[test]
1000 fn test_nested_union_three_levels_untagged() {
1001 let doc = eure!({ = "deep value" });
1004 let root_id = doc.get_root_id();
1005 let ctx = doc.parse_context(root_id);
1006
1007 let result = parse_level1(&ctx).unwrap();
1008 assert_eq!(
1009 result,
1010 Level1::A(Level2Union::X(Level3::Leaf("deep value".to_string())))
1011 );
1012 }
1013
1014 #[test]
1015 fn test_nested_union_three_levels_variant_path() {
1016 let doc = eure!({ %variant = "a.x.leaf", = "explicit deep" });
1018 let root_id = doc.get_root_id();
1019 let ctx = doc.parse_context(root_id);
1020
1021 let result = parse_level1(&ctx).unwrap();
1022 assert_eq!(
1023 result,
1024 Level1::A(Level2Union::X(Level3::Leaf("explicit deep".to_string())))
1025 );
1026 }
1027
1028 #[test]
1029 fn test_nested_union_three_levels_variant_path_partial() {
1030 let doc = eure!({ %variant = "a.y", = 123 });
1032 let root_id = doc.get_root_id();
1033 let ctx = doc.parse_context(root_id);
1034 let result = parse_level1(&ctx).unwrap();
1035 assert_eq!(result, Level1::A(Level2Union::Y(123)));
1036 }
1037
1038 #[test]
1039 fn test_nested_union_invalid_inner_variant_path() {
1040 let doc = eure!({ %variant = "a.x.invalid", = "value" });
1042 let root_id = doc.get_root_id();
1043 let ctx = doc.parse_context(root_id);
1044
1045 let err = parse_level1(&ctx).unwrap_err();
1046 assert_eq!(
1047 err.kind,
1048 ParseErrorKind::UnknownVariant("invalid".to_string())
1049 );
1050 }
1051
1052 #[test]
1057 fn test_flatten_nested_union_accessed_fields_basic() {
1058 use crate::parse::AccessedSet;
1059 use crate::parse::FlattenContext;
1060 use crate::parse::ParserScope;
1061
1062 let doc = eure!({
1064 field_a = "value_a"
1065 field_b = "value_b"
1066 });
1067 let root_id = doc.get_root_id();
1068
1069 let flatten_ctx = FlattenContext::new(AccessedSet::new(), ParserScope::Record);
1071 let ctx = ParseContext::with_flatten_ctx(&doc, root_id, flatten_ctx.clone());
1072
1073 let record = ctx.parse_record().unwrap();
1075 let _field_a: String = record.parse_field("field_a").unwrap();
1076
1077 let (accessed, _) = flatten_ctx.capture_current_state();
1079 assert!(accessed.contains("field_a"));
1080 assert!(!accessed.contains("field_b"));
1081 }
1082}