1use std::collections::{BTreeSet, HashMap, HashSet};
18use std::fmt::{self, Display, Write};
19use std::iter;
20use std::ops::{Deref, DerefMut};
21use std::str::FromStr;
22use std::sync::Arc;
23
24use either::Either;
25use lalrpop_util as lalr;
26use lazy_static::lazy_static;
27use miette::{Diagnostic, LabeledSpan, SourceSpan};
28use nonempty::NonEmpty;
29use smol_str::SmolStr;
30use thiserror::Error;
31
32use crate::ast::{self, ReservedNameError};
33use crate::parser::fmt::join_with_conjunction;
34use crate::parser::loc::Loc;
35use crate::parser::node::Node;
36use crate::parser::unescape::UnescapeError;
37
38use super::cst;
39
40pub(crate) type RawLocation = usize;
41pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
42pub(crate) type RawUserError = Node<String>;
43
44pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
45pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
46
47type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
48
49#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
51pub enum ParseError {
52 #[error(transparent)]
54 #[diagnostic(transparent)]
55 ToCST(#[from] ToCSTError),
56 #[error(transparent)]
58 #[diagnostic(transparent)]
59 ToAST(#[from] ToASTError),
60}
61
62#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
64pub enum LiteralParseError {
65 #[error(transparent)]
67 #[diagnostic(transparent)]
68 Parse(#[from] ParseErrors),
69 #[error("invalid literal: {0}")]
71 InvalidLiteral(ast::Expr),
72}
73
74#[derive(Debug, Error, Clone, PartialEq, Eq)]
76#[error("{kind}")]
77pub struct ToASTError {
78 kind: ToASTErrorKind,
79 loc: Loc,
80}
81
82impl Diagnostic for ToASTError {
85 impl_diagnostic_from_source_loc_field!(loc);
86
87 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
88 self.kind.code()
89 }
90
91 fn severity(&self) -> Option<miette::Severity> {
92 self.kind.severity()
93 }
94
95 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
96 self.kind.help()
97 }
98
99 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
100 self.kind.url()
101 }
102
103 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
104 self.kind.diagnostic_source()
105 }
106}
107
108impl ToASTError {
109 pub fn new(kind: ToASTErrorKind, loc: Loc) -> Self {
111 Self { kind, loc }
112 }
113
114 pub fn kind(&self) -> &ToASTErrorKind {
116 &self.kind
117 }
118
119 pub(crate) fn source_loc(&self) -> &Loc {
120 &self.loc
121 }
122}
123
124const POLICY_SCOPE_HELP: &str =
125 "policy scopes must contain a `principal`, `action`, and `resource` element in that order";
126
127#[derive(Debug, Diagnostic, Error, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum ToASTErrorKind {
133 #[error("a template with id `{0}` already exists in the policy set")]
135 DuplicateTemplateId(ast::PolicyID),
136 #[error("a policy with id `{0}` already exists in the policy set")]
138 DuplicatePolicyId(ast::PolicyID),
139 #[error(transparent)]
141 #[diagnostic(transparent)]
142 ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
143 #[error(transparent)]
145 #[diagnostic(transparent)]
146 ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
147 #[error("duplicate annotation: @{0}")]
150 DuplicateAnnotation(ast::AnyId),
151 #[error(transparent)]
154 #[diagnostic(transparent)]
155 SlotsInConditionClause(#[from] parse_errors::SlotsInConditionClause),
156 #[error("this policy is missing the `{0}` variable in the scope")]
159 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
160 MissingScopeVariable(ast::Var),
161 #[error("this policy has an extra element in the scope: {0}")]
163 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
164 ExtraScopeElement(Box<cst::VariableDef>),
165 #[error("this identifier is reserved and cannot be used: {0}")]
167 ReservedIdentifier(cst::Ident),
168 #[error("invalid identifier: {0}")]
172 InvalidIdentifier(String),
173 #[error("'=' is not a valid operator in Cedar")]
176 #[diagnostic(help("try using '==' instead"))]
177 InvalidSingleEq,
178 #[error("invalid policy effect: {0}")]
180 #[diagnostic(help("effect must be either `permit` or `forbid`"))]
181 InvalidEffect(cst::Ident),
182 #[error("invalid policy condition: {0}")]
184 #[diagnostic(help("condition must be either `when` or `unless`"))]
185 InvalidCondition(cst::Ident),
186 #[error("found an invalid variable in the policy scope: {0}")]
189 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
190 InvalidScopeVariable(cst::Ident),
191 #[error("found the variable `{got}` where the variable `{expected}` must be used")]
194 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
195 IncorrectVariable {
196 expected: ast::Var,
198 got: ast::Var,
200 },
201 #[error("invalid operator in the policy scope: {0}")]
203 #[diagnostic(help("policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"))]
204 InvalidScopeOperator(cst::RelOp),
205 #[error("invalid operator in the action scope: {0}")]
208 #[diagnostic(help("action scope clauses can only use `==` or `in`"))]
209 InvalidActionScopeOperator(cst::RelOp),
210 #[error("`is` cannot appear in the action scope")]
212 #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
213 IsInActionScope,
214 #[error("`is` cannot be used together with `==`")]
216 #[diagnostic(help("try using `_ is _ in _`"))]
217 IsWithEq,
218 #[error(transparent)]
220 #[diagnostic(transparent)]
221 InvalidActionType(#[from] parse_errors::InvalidActionType),
222 #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{}` ", ident), None => "".to_string() })]
224 EmptyClause(Option<cst::Ident>),
225 #[error("internal invariant violated. Membership chain did not resolve to an expression")]
228 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
229 MembershipInvariantViolation,
230 #[error("invalid string literal: {0}")]
232 InvalidString(String),
233 #[error("invalid variable: {0}")]
236 #[diagnostic(help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `{0}` in quotes to make a string?"))]
237 ArbitraryVariable(SmolStr),
238 #[error("invalid attribute name: {0}")]
240 #[diagnostic(help("attribute names can either be identifiers or string literals"))]
241 InvalidAttribute(SmolStr),
242 #[error("invalid RHS of a `has` operation: {0}")]
244 #[diagnostic(help("valid RHS of a `has` operation is either a sequence of identifiers separated by `.` or a string literal"))]
245 InvalidHasRHS(SmolStr),
246 #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
248 PathAsAttribute(String),
249 #[error("`{0}` is a method, not a function")]
251 #[diagnostic(help("use a method-style call `e.{0}(..)`"))]
252 FunctionCallOnMethod(ast::UnreservedId),
253 #[error("`{0}` is a function, not a method")]
255 #[diagnostic(help("use a function-style call `{0}(..)`"))]
256 MethodCallOnFunction(ast::UnreservedId),
257 #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
259 InvalidPattern(String),
260 #[error("right hand side of an `is` expression must be an entity type name, but got `{rhs}`")]
262 #[diagnostic(help("{}", invalid_is_help(lhs, rhs)))]
263 InvalidIsType {
264 lhs: String,
266 rhs: String,
268 },
269 #[error("expected {expected}, found {got}")]
271 WrongNode {
272 expected: &'static str,
274 got: String,
276 #[help]
278 suggestion: Option<String>,
279 },
280 #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
283 AmbiguousOperators,
284 #[error("division is not supported")]
286 UnsupportedDivision,
287 #[error("remainder/modulo is not supported")]
289 UnsupportedModulo,
290 #[error(transparent)]
292 #[diagnostic(transparent)]
293 ExpressionConstructionError(#[from] ast::ExpressionConstructionError),
294 #[error("integer literal `{0}` is too large")]
296 #[diagnostic(help("maximum allowed integer literal is `{}`", ast::InputInteger::MAX))]
297 IntegerLiteralTooLarge(u64),
298 #[error("too many occurrences of `{0}`")]
300 #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
301 UnaryOpLimit(ast::UnaryOp),
302 #[error("`{0}(...)` is not a valid function call")]
305 #[diagnostic(help("variables cannot be called as functions"))]
306 VariableCall(ast::Var),
307 #[error("attempted to call `{0}.{1}(...)`, but `{0}` does not have any methods")]
309 NoMethods(ast::Name, ast::UnreservedId),
310 #[error("`{id}` is not a valid method")]
312 UnknownMethod {
313 id: ast::UnreservedId,
315 #[help]
317 hint: Option<String>,
318 },
319 #[error("`{id}` is not a valid function")]
321 UnknownFunction {
322 id: ast::Name,
324 #[help]
326 hint: Option<String>,
327 },
328 #[error("invalid entity literal: {0}")]
330 #[diagnostic(help("entity literals should have a form like `Namespace::User::\"alice\"`"))]
331 InvalidEntityLiteral(String),
332 #[error("function calls must be of the form `<name>(arg1, arg2, ...)`")]
335 ExpressionCall,
336 #[error("invalid member access `{lhs}.{field}`, `{lhs}` has no fields or methods")]
338 InvalidAccess {
339 lhs: ast::Name,
341 field: SmolStr,
343 },
344 #[error("invalid indexing expression `{lhs}[\"{}\"]`, `{lhs}` has no fields", .field.escape_debug())]
346 InvalidIndex {
347 lhs: ast::Name,
349 field: SmolStr,
351 },
352 #[error("the contents of an index expression must be a string literal")]
354 NonStringIndex,
355 #[error("type constraints using `:` are not supported")]
359 #[diagnostic(help("try using `is` instead"))]
360 TypeConstraints,
361 #[error("`{kind}` needs to be normalized (e.g., whitespace removed): {src}")]
363 #[diagnostic(help("the normalized form is `{normalized_src}`"))]
364 NonNormalizedString {
365 kind: &'static str,
367 src: String,
369 normalized_src: String,
371 },
372 #[error("internal invariant violated. Parsed data node should not be empty")]
377 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
378 EmptyNodeInvariantViolation,
379 #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
381 WrongArity {
382 name: &'static str,
384 expected: usize,
386 got: usize,
388 },
389 #[error(transparent)]
391 #[diagnostic(transparent)]
392 Unescape(#[from] UnescapeError),
393 #[error(transparent)]
395 #[diagnostic(transparent)]
396 WrongEntityArgument(#[from] parse_errors::WrongEntityArgument),
397 #[error("`{0}` is not a valid template slot")]
399 #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
400 InvalidSlot(SmolStr),
401 #[error(transparent)]
403 #[diagnostic(transparent)]
404 ReservedNamespace(#[from] ReservedNameError),
405 #[error("when `is` and `in` are used together, `is` must come first")]
407 #[diagnostic(help("try `_ is _ in _`"))]
408 InvertedIsIn,
409}
410
411fn invalid_is_help(lhs: &str, rhs: &str) -> String {
412 match strip_surrounding_doublequotes(rhs).map(ast::Id::from_str) {
415 Some(Ok(stripped)) => format!("try removing the quotes: `{lhs} is {stripped}`"),
416 _ => format!("try using `==` to test for equality: `{lhs} == {rhs}`"),
417 }
418}
419
420fn strip_surrounding_doublequotes(s: &str) -> Option<&str> {
424 s.strip_prefix('"')?.strip_suffix('"')
425}
426
427impl ToASTErrorKind {
428 pub fn wrong_node(
430 expected: &'static str,
431 got: impl Into<String>,
432 suggestion: Option<impl Into<String>>,
433 ) -> Self {
434 Self::WrongNode {
435 expected,
436 got: got.into(),
437 suggestion: suggestion.map(Into::into),
438 }
439 }
440
441 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
443 Self::WrongArity {
444 name,
445 expected,
446 got,
447 }
448 }
449
450 pub fn slots_in_condition_clause(slot: ast::Slot, clause_type: &'static str) -> Self {
452 parse_errors::SlotsInConditionClause { slot, clause_type }.into()
453 }
454
455 pub fn expected_static_policy(slot: ast::Slot) -> Self {
457 parse_errors::ExpectedStaticPolicy { slot }.into()
458 }
459
460 pub fn expected_template() -> Self {
462 parse_errors::ExpectedTemplate::new().into()
463 }
464
465 pub fn wrong_entity_argument_one_expected(
468 expected: parse_errors::Ref,
469 got: parse_errors::Ref,
470 ) -> Self {
471 parse_errors::WrongEntityArgument {
472 expected: Either::Left(expected),
473 got,
474 }
475 .into()
476 }
477
478 pub fn wrong_entity_argument_two_expected(
481 r1: parse_errors::Ref,
482 r2: parse_errors::Ref,
483 got: parse_errors::Ref,
484 ) -> Self {
485 let expected = Either::Right((r1, r2));
486 parse_errors::WrongEntityArgument { expected, got }.into()
487 }
488}
489
490pub mod parse_errors {
492
493 use std::sync::Arc;
494
495 use super::*;
496
497 #[derive(Debug, Clone, Error, PartialEq, Eq)]
499 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
500 pub struct ExpectedStaticPolicy {
501 pub(crate) slot: ast::Slot,
503 }
504
505 impl Diagnostic for ExpectedStaticPolicy {
506 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
507 Some(Box::new(
508 "try removing the template slot(s) from this policy",
509 ))
510 }
511
512 impl_diagnostic_from_source_loc_opt_field!(slot.loc);
513 }
514
515 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
516 fn from(err: ast::UnexpectedSlotError) -> Self {
517 match err {
518 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
519 }
520 }
521 }
522
523 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
525 #[error("expected a template, got a static policy")]
526 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
527 pub struct ExpectedTemplate {
528 _dummy: (),
533 }
534
535 impl ExpectedTemplate {
536 pub(crate) fn new() -> Self {
537 Self { _dummy: () }
538 }
539 }
540
541 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
543 #[error("found template slot {} in a `{clause_type}` clause", slot.id)]
544 #[diagnostic(help("slots are currently unsupported in `{clause_type}` clauses"))]
545 pub struct SlotsInConditionClause {
546 pub(crate) slot: ast::Slot,
548 pub(crate) clause_type: &'static str,
550 }
551
552 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
554 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
555 pub struct InvalidActionType {
556 pub(crate) euids: NonEmpty<Arc<ast::EntityUID>>,
557 }
558
559 impl std::fmt::Display for InvalidActionType {
560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561 let subject = if self.euids.len() > 1 {
562 "entity uids"
563 } else {
564 "an entity uid"
565 };
566 write!(f, "expected {subject} with type `Action` but got ")?;
567 join_with_conjunction(f, "and", self.euids.iter(), |f, e| write!(f, "`{e}`"))
568 }
569 }
570
571 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
573 #[error("expected {}, found {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
574 pub struct WrongEntityArgument {
575 pub(crate) expected: Either<Ref, (Ref, Ref)>,
578 pub(crate) got: Ref,
580 }
581
582 #[derive(Debug, Clone, PartialEq, Eq)]
584 pub enum Ref {
585 Single,
587 Set,
589 Template,
591 }
592
593 impl std::fmt::Display for Ref {
594 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595 match self {
596 Ref::Single => write!(f, "single entity uid"),
597 Ref::Template => write!(f, "template slot"),
598 Ref::Set => write!(f, "set of entity uids"),
599 }
600 }
601 }
602}
603
604#[derive(Clone, Debug, Error, PartialEq, Eq)]
606pub struct ToCSTError {
607 err: OwnedRawParseError,
608 src: Arc<str>,
609}
610
611impl ToCSTError {
612 pub fn primary_source_span(&self) -> SourceSpan {
614 match &self.err {
615 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
616 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
617 OwnedRawParseError::UnrecognizedToken {
618 token: (token_start, _, token_end),
619 ..
620 } => SourceSpan::from(*token_start..*token_end),
621 OwnedRawParseError::ExtraToken {
622 token: (token_start, _, token_end),
623 } => SourceSpan::from(*token_start..*token_end),
624 OwnedRawParseError::User { error } => error.loc.span,
625 }
626 }
627
628 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>, src: Arc<str>) -> Self {
629 Self {
630 err: err.map_token(|token| token.to_string()),
631 src,
632 }
633 }
634
635 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>, src: Arc<str>) -> Self {
636 Self::from_raw_parse_err(recovery.error, src)
637 }
638}
639
640impl Display for ToCSTError {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 match &self.err {
643 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
644 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
645 OwnedRawParseError::UnrecognizedToken {
646 token: (_, token, _),
647 ..
648 } => write!(f, "unexpected token `{token}`"),
649 OwnedRawParseError::ExtraToken {
650 token: (_, token, _),
651 ..
652 } => write!(f, "extra token `{token}`"),
653 OwnedRawParseError::User { error } => write!(f, "{error}"),
654 }
655 }
656}
657
658impl Diagnostic for ToCSTError {
659 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
660 Some(&self.src as &dyn miette::SourceCode)
661 }
662
663 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
664 let primary_source_span = self.primary_source_span();
665 let labeled_span = match &self.err {
666 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
667 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
668 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
669 primary_source_span,
670 ),
671 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
672 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
673 primary_source_span,
674 ),
675 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
676 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
677 };
678 Some(Box::new(iter::once(labeled_span)))
679 }
680}
681
682#[derive(Debug)]
685pub struct ExpectedTokenConfig {
686 pub friendly_token_names: HashMap<&'static str, &'static str>,
690
691 pub impossible_tokens: HashSet<&'static str>,
696
697 pub special_identifier_tokens: HashSet<&'static str>,
704
705 pub identifier_sentinel: &'static str,
708
709 pub first_set_identifier_tokens: HashSet<&'static str>,
714
715 pub first_set_sentinel: &'static str,
719}
720
721lazy_static! {
722 static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
723 friendly_token_names: HashMap::from([
724 ("TRUE", "`true`"),
725 ("FALSE", "`false`"),
726 ("IF", "`if`"),
727 ("PERMIT", "`permit`"),
728 ("FORBID", "`forbid`"),
729 ("WHEN", "`when`"),
730 ("UNLESS", "`unless`"),
731 ("IN", "`in`"),
732 ("HAS", "`has`"),
733 ("LIKE", "`like`"),
734 ("IS", "`is`"),
735 ("THEN", "`then`"),
736 ("ELSE", "`else`"),
737 ("PRINCIPAL", "`principal`"),
738 ("ACTION", "`action`"),
739 ("RESOURCE", "`resource`"),
740 ("CONTEXT", "`context`"),
741 ("PRINCIPAL_SLOT", "`?principal`"),
742 ("RESOURCE_SLOT", "`?resource`"),
743 ("IDENTIFIER", "identifier"),
744 ("NUMBER", "number"),
745 ("STRINGLIT", "string literal"),
746 ]),
747 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
748 special_identifier_tokens: HashSet::from([
749 "PERMIT",
750 "FORBID",
751 "WHEN",
752 "UNLESS",
753 "IN",
754 "HAS",
755 "LIKE",
756 "IS",
757 "THEN",
758 "ELSE",
759 "PRINCIPAL",
760 "ACTION",
761 "RESOURCE",
762 "CONTEXT",
763 ]),
764 identifier_sentinel: "IDENTIFIER",
765 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
766 first_set_sentinel: "\"!\"",
767 };
768}
769
770pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
772 let mut expected = expected
773 .iter()
774 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
775 .map(|e| e.as_str())
776 .collect::<BTreeSet<_>>();
777 if expected.contains(config.identifier_sentinel) {
778 for token in config.special_identifier_tokens.iter() {
779 expected.remove(*token);
780 }
781 if !expected.contains(config.first_set_sentinel) {
782 for token in config.first_set_identifier_tokens.iter() {
783 expected.remove(*token);
784 }
785 }
786 }
787 if expected.is_empty() {
788 return None;
789 }
790
791 let mut expected_string = "expected ".to_owned();
792 #[allow(clippy::expect_used)]
794 join_with_conjunction(
795 &mut expected_string,
796 "or",
797 expected,
798 |f, token| match config.friendly_token_names.get(token) {
799 Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
800 None => write!(f, "{}", token.replace('"', "`")),
801 },
802 )
803 .expect("failed to format expected tokens");
804 Some(expected_string)
805}
806
807#[derive(Clone, Debug, PartialEq, Eq)]
810pub struct ParseErrors(NonEmpty<ParseError>);
811
812impl ParseErrors {
813 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
815 Self(NonEmpty::singleton(err.into()))
816 }
817
818 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
820 Self(NonEmpty {
821 head: first,
822 tail: rest.into_iter().collect::<Vec<_>>(),
823 })
824 }
825
826 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
828 Self(errs)
829 }
830
831 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
832 NonEmpty::collect(i).map(Self::new_from_nonempty)
833 }
834
835 pub(crate) fn flatten(errs: impl IntoIterator<Item = ParseErrors>) -> Option<Self> {
838 let mut errs = errs.into_iter();
839 let mut first = errs.next()?;
840 for inner in errs {
841 first.extend(inner);
842 }
843 Some(first)
844 }
845
846 pub(crate) fn transpose<T>(
850 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
851 ) -> Result<Vec<T>, Self> {
852 let mut errs = vec![];
853 let oks: Vec<_> = i
854 .into_iter()
855 .filter_map(|r| r.map_err(|e| errs.push(e)).ok())
856 .collect();
857 if let Some(combined_errs) = Self::flatten(errs) {
858 Err(combined_errs)
859 } else {
860 Ok(oks)
861 }
862 }
863}
864
865impl Display for ParseErrors {
866 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
867 write!(f, "{}", self.first()) }
869}
870
871impl std::error::Error for ParseErrors {
872 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
873 self.first().source()
874 }
875
876 #[allow(deprecated)]
877 fn description(&self) -> &str {
878 self.first().description()
879 }
880
881 #[allow(deprecated)]
882 fn cause(&self) -> Option<&dyn std::error::Error> {
883 self.first().cause()
884 }
885}
886
887impl Diagnostic for ParseErrors {
892 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
893 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
895 errs.next().map(move |first_err| match first_err.related() {
896 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
897 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
898 })
899 }
900
901 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
902 self.first().code()
903 }
904
905 fn severity(&self) -> Option<miette::Severity> {
906 self.first().severity()
907 }
908
909 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
910 self.first().help()
911 }
912
913 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
914 self.first().url()
915 }
916
917 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
918 self.first().source_code()
919 }
920
921 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
922 self.first().labels()
923 }
924
925 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
926 self.first().diagnostic_source()
927 }
928}
929
930impl AsRef<NonEmpty<ParseError>> for ParseErrors {
931 fn as_ref(&self) -> &NonEmpty<ParseError> {
932 &self.0
933 }
934}
935
936impl AsMut<NonEmpty<ParseError>> for ParseErrors {
937 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
938 &mut self.0
939 }
940}
941
942impl Deref for ParseErrors {
943 type Target = NonEmpty<ParseError>;
944
945 fn deref(&self) -> &Self::Target {
946 &self.0
947 }
948}
949
950impl DerefMut for ParseErrors {
951 fn deref_mut(&mut self) -> &mut Self::Target {
952 &mut self.0
953 }
954}
955
956impl<T: Into<ParseError>> From<T> for ParseErrors {
957 fn from(err: T) -> Self {
958 ParseErrors::singleton(err.into())
959 }
960}
961
962impl<T: Into<ParseError>> Extend<T> for ParseErrors {
963 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
964 self.0.extend(iter.into_iter().map(Into::into))
965 }
966}
967
968impl IntoIterator for ParseErrors {
969 type Item = ParseError;
970 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
971
972 fn into_iter(self) -> Self::IntoIter {
973 self.0.into_iter()
974 }
975}
976
977impl<'a> IntoIterator for &'a ParseErrors {
978 type Item = &'a ParseError;
979 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
980
981 fn into_iter(self) -> Self::IntoIter {
982 iter::once(&self.head).chain(self.tail.iter())
983 }
984}