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, LazyLock};
23
24use either::Either;
25use lalrpop_util as lalr;
26use miette::{Diagnostic, LabeledSpan, SourceSpan};
27use nonempty::NonEmpty;
28use smol_str::SmolStr;
29use thiserror::Error;
30
31use crate::ast::{self, ReservedNameError};
32use crate::parser::fmt::join_with_conjunction;
33use crate::parser::node::Node;
34use crate::parser::unescape::UnescapeError;
35use crate::parser::Loc;
36
37use super::cst;
38
39pub(crate) type RawLocation = usize;
40pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
41pub(crate) type RawUserError = Node<String>;
42
43pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
44pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
45
46type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
47
48#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
50pub enum ParseError {
51 #[error(transparent)]
53 #[diagnostic(transparent)]
54 ToCST(#[from] ToCSTError),
55 #[error(transparent)]
57 #[diagnostic(transparent)]
58 ToAST(#[from] ToASTError),
59}
60
61#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
63pub enum LiteralParseError {
64 #[error(transparent)]
66 #[diagnostic(transparent)]
67 Parse(#[from] ParseErrors),
68 #[error("invalid literal: {0}")]
70 InvalidLiteral(ast::Expr),
71}
72
73#[derive(Debug, Error, Clone, PartialEq, Eq)]
75#[error("{kind}")]
76pub struct ToASTError {
77 kind: ToASTErrorKind,
78 loc: Option<Loc>,
79}
80
81impl Diagnostic for ToASTError {
84 impl_diagnostic_from_source_loc_opt_field!(loc);
85
86 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
87 self.kind.code()
88 }
89
90 fn severity(&self) -> Option<miette::Severity> {
91 self.kind.severity()
92 }
93
94 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
95 self.kind.help()
96 }
97
98 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
99 self.kind.url()
100 }
101
102 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
103 self.kind.diagnostic_source()
104 }
105}
106
107impl ToASTError {
108 pub fn new(kind: ToASTErrorKind, loc: Option<Loc>) -> Self {
110 Self { kind, loc }
111 }
112
113 pub fn kind(&self) -> &ToASTErrorKind {
115 &self.kind
116 }
117
118 pub(crate) fn source_loc(&self) -> Option<&Loc> {
119 self.loc.as_ref()
120 }
121}
122
123const POLICY_SCOPE_HELP: &str =
124 "policy scopes must contain a `principal`, `action`, and `resource` element in that order";
125
126#[derive(Debug, Diagnostic, Error, Clone, PartialEq, Eq)]
130#[non_exhaustive]
131pub enum ToASTErrorKind {
132 #[error("a template with id `{0}` already exists in the policy set")]
134 DuplicateTemplateId(ast::PolicyID),
135 #[error("a policy with id `{0}` already exists in the policy set")]
137 DuplicatePolicyId(ast::PolicyID),
138 #[error(transparent)]
140 #[diagnostic(transparent)]
141 ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
142 #[error(transparent)]
144 #[diagnostic(transparent)]
145 ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
146 #[error("duplicate annotation: @{0}")]
149 DuplicateAnnotation(ast::AnyId),
150 #[error(transparent)]
153 #[diagnostic(transparent)]
154 SlotsInConditionClause(#[from] parse_errors::SlotsInConditionClause),
155 #[error("this policy is missing the `{0}` variable in the scope")]
158 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
159 MissingScopeVariable(ast::Var),
160 #[error("this policy has an extra element in the scope: {0}")]
162 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
163 ExtraScopeElement(Box<cst::VariableDef>),
164 #[error("this identifier is reserved and cannot be used: {0}")]
166 ReservedIdentifier(cst::Ident),
167 #[error("invalid identifier: {0}")]
171 InvalidIdentifier(String),
172 #[error("'=' is not a valid operator in Cedar")]
175 #[diagnostic(help("try using '==' instead"))]
176 InvalidSingleEq,
177 #[error("invalid policy effect: {0}")]
179 #[diagnostic(help("effect must be either `permit` or `forbid`"))]
180 InvalidEffect(cst::Ident),
181 #[error("invalid policy condition: {0}")]
183 #[diagnostic(help("condition must be either `when` or `unless`"))]
184 InvalidCondition(cst::Ident),
185 #[error("found an invalid variable in the policy scope: {0}")]
188 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
189 InvalidScopeVariable(cst::Ident),
190 #[error("found the variable `{got}` where the variable `{expected}` must be used")]
193 #[diagnostic(help("{POLICY_SCOPE_HELP}"))]
194 IncorrectVariable {
195 expected: ast::Var,
197 got: ast::Var,
199 },
200 #[error("invalid operator in the policy scope: {0}")]
202 #[diagnostic(help("policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"))]
203 InvalidScopeOperator(cst::RelOp),
204 #[error("invalid operator in the action scope: {0}")]
207 #[diagnostic(help("action scope clauses can only use `==` or `in`"))]
208 InvalidActionScopeOperator(cst::RelOp),
209 #[error("`is` cannot appear in the action scope")]
211 #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
212 IsInActionScope,
213 #[error("`is` cannot be used together with `==`")]
215 #[diagnostic(help("try using `_ is _ in _`"))]
216 IsWithEq,
217 #[error(transparent)]
219 #[diagnostic(transparent)]
220 InvalidActionType(#[from] parse_errors::InvalidActionType),
221 #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{ident}` "), None => "".to_string() })]
223 EmptyClause(Option<cst::Ident>),
224 #[error("internal invariant violated. Membership chain did not resolve to an expression")]
227 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
228 MembershipInvariantViolation,
229 #[error("invalid string literal: {0}")]
231 InvalidString(String),
232 #[error("invalid variable: {0}")]
235 #[diagnostic(help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `{0}` in quotes to make a string?"))]
236 ArbitraryVariable(SmolStr),
237 #[error("invalid attribute name: {0}")]
239 #[diagnostic(help("attribute names can either be identifiers or string literals"))]
240 InvalidAttribute(SmolStr),
241 #[error("invalid RHS of a `has` operation: {0}")]
243 #[diagnostic(help("valid RHS of a `has` operation is either a sequence of identifiers separated by `.` or a string literal"))]
244 InvalidHasRHS(SmolStr),
245 #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
247 PathAsAttribute(String),
248 #[error("`{0}` is a method, not a function")]
250 #[diagnostic(help("use a method-style call `e.{0}(..)`"))]
251 FunctionCallOnMethod(ast::UnreservedId),
252 #[error("`{0}` is a function, not a method")]
254 #[diagnostic(help("use a function-style call `{0}(..)`"))]
255 MethodCallOnFunction(ast::UnreservedId),
256 #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
258 InvalidPattern(String),
259 #[error("right hand side of an `is` expression must be an entity type name, but got `{rhs}`")]
261 #[diagnostic(help("{}", invalid_is_help(lhs, rhs)))]
262 InvalidIsType {
263 lhs: String,
265 rhs: String,
267 },
268 #[error("expected {expected}, found {got}")]
270 WrongNode {
271 expected: &'static str,
273 got: String,
275 #[help]
277 suggestion: Option<String>,
278 },
279 #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
282 AmbiguousOperators,
283 #[error("division is not supported")]
285 UnsupportedDivision,
286 #[error("remainder/modulo is not supported")]
288 UnsupportedModulo,
289 #[error(transparent)]
291 #[diagnostic(transparent)]
292 ExpressionConstructionError(#[from] ast::ExpressionConstructionError),
293 #[error("integer literal `{0}` is too large")]
295 #[diagnostic(help("maximum allowed integer literal is `{}`", ast::InputInteger::MAX))]
296 IntegerLiteralTooLarge(u64),
297 #[error("too many occurrences of `{0}`")]
299 #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
300 UnaryOpLimit(ast::UnaryOp),
301 #[error("`{0}(...)` is not a valid function call")]
304 #[diagnostic(help("variables cannot be called as functions"))]
305 VariableCall(ast::Var),
306 #[error("attempted to call `{0}.{1}(...)`, but `{0}` does not have any methods")]
308 NoMethods(ast::Name, ast::UnreservedId),
309 #[error("`{id}` is not a valid method")]
311 UnknownMethod {
312 id: ast::UnreservedId,
314 #[help]
316 hint: Option<String>,
317 },
318 #[error("`{id}` is not a valid function")]
320 UnknownFunction {
321 id: ast::Name,
323 #[help]
325 hint: Option<String>,
326 },
327 #[error("invalid entity literal: {0}")]
329 #[diagnostic(help("entity literals should have a form like `Namespace::User::\"alice\"`"))]
330 InvalidEntityLiteral(String),
331 #[error("function calls must be of the form `<name>(arg1, arg2, ...)`")]
334 ExpressionCall,
335 #[error("invalid member access `{lhs}.{field}`, `{lhs}` has no fields or methods")]
337 InvalidAccess {
338 lhs: ast::Name,
340 field: SmolStr,
342 },
343 #[error("invalid indexing expression `{lhs}[\"{}\"]`, `{lhs}` has no fields", .field.escape_debug())]
345 InvalidIndex {
346 lhs: ast::Name,
348 field: SmolStr,
350 },
351 #[error("the contents of an index expression must be a string literal")]
353 NonStringIndex,
354 #[error("type constraints using `:` are not supported")]
358 #[diagnostic(help("try using `is` instead"))]
359 TypeConstraints,
360 #[error("`{kind}` needs to be normalized (e.g., whitespace removed): {src}")]
362 #[diagnostic(help("the normalized form is `{normalized_src}`"))]
363 NonNormalizedString {
364 kind: &'static str,
366 src: String,
368 normalized_src: String,
370 },
371 #[error("internal invariant violated. Parsed data node should not be empty")]
376 #[diagnostic(help("please file an issue at <https://github.com/cedar-policy/cedar/issues> including the text that failed to parse"))]
377 EmptyNodeInvariantViolation,
378 #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
380 WrongArity {
381 name: &'static str,
383 expected: usize,
385 got: usize,
387 },
388 #[error(transparent)]
390 #[diagnostic(transparent)]
391 Unescape(#[from] UnescapeError),
392 #[error(transparent)]
394 #[diagnostic(transparent)]
395 WrongEntityArgument(#[from] parse_errors::WrongEntityArgument),
396 #[error("`{0}` is not a valid template slot")]
398 #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
399 InvalidSlot(SmolStr),
400 #[error(transparent)]
402 #[diagnostic(transparent)]
403 ReservedNamespace(#[from] ReservedNameError),
404 #[error("when `is` and `in` are used together, `is` must come first")]
406 #[diagnostic(help("try `_ is _ in _`"))]
407 InvertedIsIn,
408 #[cfg(feature = "tolerant-ast")]
410 #[error("Trying to convert CST error node")]
411 CSTErrorNode,
412 #[cfg(feature = "tolerant-ast")]
414 #[error("Trying to convert AST error node")]
415 ASTErrorNode,
416}
417
418fn invalid_is_help(lhs: &str, rhs: &str) -> String {
419 match strip_surrounding_doublequotes(rhs).map(ast::Id::from_str) {
422 Some(Ok(stripped)) => format!("try removing the quotes: `{lhs} is {stripped}`"),
423 _ => format!("try using `==` to test for equality: `{lhs} == {rhs}`"),
424 }
425}
426
427fn strip_surrounding_doublequotes(s: &str) -> Option<&str> {
431 s.strip_prefix('"')?.strip_suffix('"')
432}
433
434impl ToASTErrorKind {
435 pub fn wrong_node(
437 expected: &'static str,
438 got: impl Into<String>,
439 suggestion: Option<impl Into<String>>,
440 ) -> Self {
441 Self::WrongNode {
442 expected,
443 got: got.into(),
444 suggestion: suggestion.map(Into::into),
445 }
446 }
447
448 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
450 Self::WrongArity {
451 name,
452 expected,
453 got,
454 }
455 }
456
457 pub fn slots_in_condition_clause(slot: ast::Slot, clause_type: &'static str) -> Self {
459 parse_errors::SlotsInConditionClause { slot, clause_type }.into()
460 }
461
462 pub fn expected_static_policy(slot: ast::Slot) -> Self {
464 parse_errors::ExpectedStaticPolicy { slot }.into()
465 }
466
467 pub fn expected_template() -> Self {
469 parse_errors::ExpectedTemplate::new().into()
470 }
471
472 pub fn wrong_entity_argument_one_expected(
475 expected: parse_errors::Ref,
476 got: parse_errors::Ref,
477 ) -> Self {
478 parse_errors::WrongEntityArgument {
479 expected: Either::Left(expected),
480 got,
481 }
482 .into()
483 }
484
485 pub fn wrong_entity_argument_two_expected(
488 r1: parse_errors::Ref,
489 r2: parse_errors::Ref,
490 got: parse_errors::Ref,
491 ) -> Self {
492 let expected = Either::Right((r1, r2));
493 parse_errors::WrongEntityArgument { expected, got }.into()
494 }
495}
496
497pub mod parse_errors {
499
500 use std::sync::Arc;
501
502 use super::*;
503
504 #[derive(Debug, Clone, Error, PartialEq, Eq)]
506 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
507 pub struct ExpectedStaticPolicy {
508 pub(crate) slot: ast::Slot,
510 }
511
512 impl Diagnostic for ExpectedStaticPolicy {
513 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
514 Some(Box::new(
515 "try removing the template slot(s) from this policy",
516 ))
517 }
518
519 impl_diagnostic_from_source_loc_opt_field!(slot.loc);
520 }
521
522 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
523 fn from(err: ast::UnexpectedSlotError) -> Self {
524 match err {
525 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
526 }
527 }
528 }
529
530 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
532 #[error("expected a template, got a static policy")]
533 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
534 pub struct ExpectedTemplate {
535 _dummy: (),
540 }
541
542 impl ExpectedTemplate {
543 pub(crate) fn new() -> Self {
544 Self { _dummy: () }
545 }
546 }
547
548 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
550 #[error("found template slot {} in a `{clause_type}` clause", slot.id)]
551 #[diagnostic(help("slots are currently unsupported in `{clause_type}` clauses"))]
552 pub struct SlotsInConditionClause {
553 pub(crate) slot: ast::Slot,
555 pub(crate) clause_type: &'static str,
557 }
558
559 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
561 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
562 pub struct InvalidActionType {
563 pub(crate) euids: NonEmpty<Arc<ast::EntityUID>>,
564 }
565
566 impl std::fmt::Display for InvalidActionType {
567 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568 let subject = if self.euids.len() > 1 {
569 "entity uids"
570 } else {
571 "an entity uid"
572 };
573 write!(f, "expected {subject} with type `Action` but got ")?;
574 join_with_conjunction(f, "and", self.euids.iter(), |f, e| write!(f, "`{e}`"))
575 }
576 }
577
578 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
580 #[error("expected {}, found {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
581 pub struct WrongEntityArgument {
582 pub(crate) expected: Either<Ref, (Ref, Ref)>,
585 pub(crate) got: Ref,
587 }
588
589 #[derive(Debug, Clone, PartialEq, Eq)]
591 pub enum Ref {
592 Single,
594 Set,
596 Template,
598 }
599
600 impl std::fmt::Display for Ref {
601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602 match self {
603 Ref::Single => write!(f, "single entity uid"),
604 Ref::Template => write!(f, "template slot"),
605 Ref::Set => write!(f, "set of entity uids"),
606 }
607 }
608 }
609}
610
611#[derive(Clone, Debug, Error, PartialEq, Eq)]
613pub struct ToCSTError {
614 err: OwnedRawParseError,
615 src: Arc<str>,
616}
617
618impl ToCSTError {
619 pub fn primary_source_span(&self) -> Option<SourceSpan> {
621 match &self.err {
622 OwnedRawParseError::InvalidToken { location } => Some(SourceSpan::from(*location)),
623 OwnedRawParseError::UnrecognizedEof { location, .. } => {
624 Some(SourceSpan::from(*location))
625 }
626 OwnedRawParseError::UnrecognizedToken {
627 token: (token_start, _, token_end),
628 ..
629 } => Some(SourceSpan::from(*token_start..*token_end)),
630 OwnedRawParseError::ExtraToken {
631 token: (token_start, _, token_end),
632 } => Some(SourceSpan::from(*token_start..*token_end)),
633 OwnedRawParseError::User { error } => error.loc.clone().map(|loc| loc.span),
634 }
635 }
636
637 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>, src: Arc<str>) -> Self {
638 Self {
639 err: err.map_token(|token| token.to_string()),
640 src,
641 }
642 }
643
644 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>, src: Arc<str>) -> Self {
645 Self::from_raw_parse_err(recovery.error, src)
646 }
647}
648
649impl Display for ToCSTError {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651 match &self.err {
652 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
653 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
654 OwnedRawParseError::UnrecognizedToken {
655 token: (_, token, _),
656 ..
657 } => write!(f, "unexpected token `{token}`"),
658 OwnedRawParseError::ExtraToken {
659 token: (_, token, _),
660 ..
661 } => write!(f, "extra token `{token}`"),
662 OwnedRawParseError::User { error } => write!(f, "{error}"),
663 }
664 }
665}
666
667impl Diagnostic for ToCSTError {
668 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
669 Some(&self.src as &dyn miette::SourceCode)
670 }
671
672 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
673 let span = self.primary_source_span()?;
674 let labeled_span = match &self.err {
675 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(span),
676 OwnedRawParseError::UnrecognizedEof { expected, .. } => {
677 LabeledSpan::new_with_span(expected_to_string(expected, &POLICY_TOKEN_CONFIG), span)
678 }
679 OwnedRawParseError::UnrecognizedToken { expected, .. } => {
680 LabeledSpan::new_with_span(expected_to_string(expected, &POLICY_TOKEN_CONFIG), span)
681 }
682 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(span),
683 OwnedRawParseError::User { .. } => LabeledSpan::underline(span),
684 };
685 Some(Box::new(iter::once(labeled_span)))
686 }
687}
688
689#[derive(Debug)]
692pub struct ExpectedTokenConfig {
693 pub friendly_token_names: HashMap<&'static str, &'static str>,
697
698 pub impossible_tokens: HashSet<&'static str>,
703
704 pub special_identifier_tokens: HashSet<&'static str>,
711
712 pub identifier_sentinel: &'static str,
715
716 pub first_set_identifier_tokens: HashSet<&'static str>,
721
722 pub first_set_sentinel: &'static str,
726}
727
728static POLICY_TOKEN_CONFIG: LazyLock<ExpectedTokenConfig> = LazyLock::new(|| ExpectedTokenConfig {
729 friendly_token_names: HashMap::from([
730 ("TRUE", "`true`"),
731 ("FALSE", "`false`"),
732 ("IF", "`if`"),
733 ("PERMIT", "`permit`"),
734 ("FORBID", "`forbid`"),
735 ("WHEN", "`when`"),
736 ("UNLESS", "`unless`"),
737 ("IN", "`in`"),
738 ("HAS", "`has`"),
739 ("LIKE", "`like`"),
740 ("IS", "`is`"),
741 ("THEN", "`then`"),
742 ("ELSE", "`else`"),
743 ("PRINCIPAL", "`principal`"),
744 ("ACTION", "`action`"),
745 ("RESOURCE", "`resource`"),
746 ("CONTEXT", "`context`"),
747 ("PRINCIPAL_SLOT", "`?principal`"),
748 ("RESOURCE_SLOT", "`?resource`"),
749 ("IDENTIFIER", "identifier"),
750 ("NUMBER", "number"),
751 ("STRINGLIT", "string literal"),
752 ]),
753 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
754 special_identifier_tokens: HashSet::from([
755 "PERMIT",
756 "FORBID",
757 "WHEN",
758 "UNLESS",
759 "IN",
760 "HAS",
761 "LIKE",
762 "IS",
763 "THEN",
764 "ELSE",
765 "PRINCIPAL",
766 "ACTION",
767 "RESOURCE",
768 "CONTEXT",
769 ]),
770 identifier_sentinel: "IDENTIFIER",
771 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
772 first_set_sentinel: "\"!\"",
773});
774
775pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
777 let mut expected = expected
778 .iter()
779 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
780 .map(|e| e.as_str())
781 .collect::<BTreeSet<_>>();
782 if expected.contains(config.identifier_sentinel) {
783 for token in config.special_identifier_tokens.iter() {
784 expected.remove(*token);
785 }
786 if !expected.contains(config.first_set_sentinel) {
787 for token in config.first_set_identifier_tokens.iter() {
788 expected.remove(*token);
789 }
790 }
791 }
792 if expected.is_empty() {
793 return None;
794 }
795
796 let mut expected_string = "expected ".to_owned();
797 #[allow(clippy::expect_used)]
799 join_with_conjunction(
800 &mut expected_string,
801 "or",
802 expected,
803 |f, token| match config.friendly_token_names.get(token) {
804 Some(friendly_token_name) => write!(f, "{friendly_token_name}"),
805 None => write!(f, "{}", token.replace('"', "`")),
806 },
807 )
808 .expect("failed to format expected tokens");
809 Some(expected_string)
810}
811
812#[derive(Clone, Debug, PartialEq, Eq)]
815pub struct ParseErrors(NonEmpty<ParseError>);
816
817impl ParseErrors {
818 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
820 Self(NonEmpty::singleton(err.into()))
821 }
822
823 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
825 Self(NonEmpty {
826 head: first,
827 tail: rest.into_iter().collect::<Vec<_>>(),
828 })
829 }
830
831 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
833 Self(errs)
834 }
835
836 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
837 NonEmpty::collect(i).map(Self::new_from_nonempty)
838 }
839
840 pub(crate) fn flatten(errs: impl IntoIterator<Item = ParseErrors>) -> Option<Self> {
843 let mut errs = errs.into_iter();
844 let mut first = errs.next()?;
845 for inner in errs {
846 first.extend(inner);
847 }
848 Some(first)
849 }
850
851 pub(crate) fn transpose<T>(
855 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
856 ) -> Result<Vec<T>, Self> {
857 let iter = i.into_iter();
858 let (lower, upper) = iter.size_hint();
859 let capacity = upper.unwrap_or(lower);
860
861 let mut oks = Vec::with_capacity(capacity);
862 let mut errs = Vec::new();
863
864 for r in iter {
865 match r {
866 Ok(v) => oks.push(v),
867 Err(e) => errs.push(e),
868 }
869 }
870
871 if errs.is_empty() {
872 Ok(oks)
873 } else {
874 #[allow(clippy::unwrap_used)]
876 Err(Self::flatten(errs).unwrap())
877 }
878 }
879}
880
881impl Display for ParseErrors {
882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883 write!(f, "{}", self.first()) }
885}
886
887impl std::error::Error for ParseErrors {
888 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
889 self.first().source()
890 }
891
892 #[allow(deprecated)]
893 fn description(&self) -> &str {
894 self.first().description()
895 }
896
897 #[allow(deprecated)]
898 fn cause(&self) -> Option<&dyn std::error::Error> {
899 self.first().cause()
900 }
901}
902
903impl Diagnostic for ParseErrors {
908 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
909 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
911 errs.next().map(move |first_err| match first_err.related() {
912 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
913 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
914 })
915 }
916
917 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
918 self.first().code()
919 }
920
921 fn severity(&self) -> Option<miette::Severity> {
922 self.first().severity()
923 }
924
925 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
926 self.first().help()
927 }
928
929 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
930 self.first().url()
931 }
932
933 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
934 self.first().source_code()
935 }
936
937 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
938 self.first().labels()
939 }
940
941 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
942 self.first().diagnostic_source()
943 }
944}
945
946impl AsRef<NonEmpty<ParseError>> for ParseErrors {
947 fn as_ref(&self) -> &NonEmpty<ParseError> {
948 &self.0
949 }
950}
951
952impl AsMut<NonEmpty<ParseError>> for ParseErrors {
953 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
954 &mut self.0
955 }
956}
957
958impl Deref for ParseErrors {
959 type Target = NonEmpty<ParseError>;
960
961 fn deref(&self) -> &Self::Target {
962 &self.0
963 }
964}
965
966impl DerefMut for ParseErrors {
967 fn deref_mut(&mut self) -> &mut Self::Target {
968 &mut self.0
969 }
970}
971
972impl<T: Into<ParseError>> From<T> for ParseErrors {
973 fn from(err: T) -> Self {
974 ParseErrors::singleton(err.into())
975 }
976}
977
978impl<T: Into<ParseError>> Extend<T> for ParseErrors {
979 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
980 self.0.extend(iter.into_iter().map(Into::into))
981 }
982}
983
984impl IntoIterator for ParseErrors {
985 type Item = ParseError;
986 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
987
988 fn into_iter(self) -> Self::IntoIter {
989 self.0.into_iter()
990 }
991}
992
993impl<'a> IntoIterator for &'a ParseErrors {
994 type Item = &'a ParseError;
995 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
996
997 fn into_iter(self) -> Self::IntoIter {
998 iter::once(&self.head).chain(self.tail.iter())
999 }
1000}