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 #[cfg(feature = "tolerant-ast")]
411 #[error("Trying to convert CST error node")]
412 CSTErrorNode,
413 #[cfg(feature = "tolerant-ast")]
415 #[error("Trying to convert AST error node")]
416 ASTErrorNode,
417}
418
419fn invalid_is_help(lhs: &str, rhs: &str) -> String {
420 match strip_surrounding_doublequotes(rhs).map(ast::Id::from_str) {
423 Some(Ok(stripped)) => format!("try removing the quotes: `{lhs} is {stripped}`"),
424 _ => format!("try using `==` to test for equality: `{lhs} == {rhs}`"),
425 }
426}
427
428fn strip_surrounding_doublequotes(s: &str) -> Option<&str> {
432 s.strip_prefix('"')?.strip_suffix('"')
433}
434
435impl ToASTErrorKind {
436 pub fn wrong_node(
438 expected: &'static str,
439 got: impl Into<String>,
440 suggestion: Option<impl Into<String>>,
441 ) -> Self {
442 Self::WrongNode {
443 expected,
444 got: got.into(),
445 suggestion: suggestion.map(Into::into),
446 }
447 }
448
449 pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
451 Self::WrongArity {
452 name,
453 expected,
454 got,
455 }
456 }
457
458 pub fn slots_in_condition_clause(slot: ast::Slot, clause_type: &'static str) -> Self {
460 parse_errors::SlotsInConditionClause { slot, clause_type }.into()
461 }
462
463 pub fn expected_static_policy(slot: ast::Slot) -> Self {
465 parse_errors::ExpectedStaticPolicy { slot }.into()
466 }
467
468 pub fn expected_template() -> Self {
470 parse_errors::ExpectedTemplate::new().into()
471 }
472
473 pub fn wrong_entity_argument_one_expected(
476 expected: parse_errors::Ref,
477 got: parse_errors::Ref,
478 ) -> Self {
479 parse_errors::WrongEntityArgument {
480 expected: Either::Left(expected),
481 got,
482 }
483 .into()
484 }
485
486 pub fn wrong_entity_argument_two_expected(
489 r1: parse_errors::Ref,
490 r2: parse_errors::Ref,
491 got: parse_errors::Ref,
492 ) -> Self {
493 let expected = Either::Right((r1, r2));
494 parse_errors::WrongEntityArgument { expected, got }.into()
495 }
496}
497
498pub mod parse_errors {
500
501 use std::sync::Arc;
502
503 use super::*;
504
505 #[derive(Debug, Clone, Error, PartialEq, Eq)]
507 #[error("expected a static policy, got a template containing the slot {}", slot.id)]
508 pub struct ExpectedStaticPolicy {
509 pub(crate) slot: ast::Slot,
511 }
512
513 impl Diagnostic for ExpectedStaticPolicy {
514 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
515 Some(Box::new(
516 "try removing the template slot(s) from this policy",
517 ))
518 }
519
520 impl_diagnostic_from_source_loc_opt_field!(slot.loc);
521 }
522
523 impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
524 fn from(err: ast::UnexpectedSlotError) -> Self {
525 match err {
526 ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
527 }
528 }
529 }
530
531 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
533 #[error("expected a template, got a static policy")]
534 #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
535 pub struct ExpectedTemplate {
536 _dummy: (),
541 }
542
543 impl ExpectedTemplate {
544 pub(crate) fn new() -> Self {
545 Self { _dummy: () }
546 }
547 }
548
549 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
551 #[error("found template slot {} in a `{clause_type}` clause", slot.id)]
552 #[diagnostic(help("slots are currently unsupported in `{clause_type}` clauses"))]
553 pub struct SlotsInConditionClause {
554 pub(crate) slot: ast::Slot,
556 pub(crate) clause_type: &'static str,
558 }
559
560 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
562 #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
563 pub struct InvalidActionType {
564 pub(crate) euids: NonEmpty<Arc<ast::EntityUID>>,
565 }
566
567 impl std::fmt::Display for InvalidActionType {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 let subject = if self.euids.len() > 1 {
570 "entity uids"
571 } else {
572 "an entity uid"
573 };
574 write!(f, "expected {subject} with type `Action` but got ")?;
575 join_with_conjunction(f, "and", self.euids.iter(), |f, e| write!(f, "`{e}`"))
576 }
577 }
578
579 #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
581 #[error("expected {}, found {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
582 pub struct WrongEntityArgument {
583 pub(crate) expected: Either<Ref, (Ref, Ref)>,
586 pub(crate) got: Ref,
588 }
589
590 #[derive(Debug, Clone, PartialEq, Eq)]
592 pub enum Ref {
593 Single,
595 Set,
597 Template,
599 }
600
601 impl std::fmt::Display for Ref {
602 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603 match self {
604 Ref::Single => write!(f, "single entity uid"),
605 Ref::Template => write!(f, "template slot"),
606 Ref::Set => write!(f, "set of entity uids"),
607 }
608 }
609 }
610}
611
612#[derive(Clone, Debug, Error, PartialEq, Eq)]
614pub struct ToCSTError {
615 err: OwnedRawParseError,
616 src: Arc<str>,
617}
618
619impl ToCSTError {
620 pub fn primary_source_span(&self) -> SourceSpan {
622 match &self.err {
623 OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
624 OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
625 OwnedRawParseError::UnrecognizedToken {
626 token: (token_start, _, token_end),
627 ..
628 } => SourceSpan::from(*token_start..*token_end),
629 OwnedRawParseError::ExtraToken {
630 token: (token_start, _, token_end),
631 } => SourceSpan::from(*token_start..*token_end),
632 OwnedRawParseError::User { error } => error.loc.span,
633 }
634 }
635
636 pub(crate) fn from_raw_parse_err(err: RawParseError<'_>, src: Arc<str>) -> Self {
637 Self {
638 err: err.map_token(|token| token.to_string()),
639 src,
640 }
641 }
642
643 pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>, src: Arc<str>) -> Self {
644 Self::from_raw_parse_err(recovery.error, src)
645 }
646}
647
648impl Display for ToCSTError {
649 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650 match &self.err {
651 OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
652 OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
653 OwnedRawParseError::UnrecognizedToken {
654 token: (_, token, _),
655 ..
656 } => write!(f, "unexpected token `{token}`"),
657 OwnedRawParseError::ExtraToken {
658 token: (_, token, _),
659 ..
660 } => write!(f, "extra token `{token}`"),
661 OwnedRawParseError::User { error } => write!(f, "{error}"),
662 }
663 }
664}
665
666impl Diagnostic for ToCSTError {
667 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
668 Some(&self.src as &dyn miette::SourceCode)
669 }
670
671 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
672 let primary_source_span = self.primary_source_span();
673 let labeled_span = match &self.err {
674 OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
675 OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
676 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
677 primary_source_span,
678 ),
679 OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
680 expected_to_string(expected, &POLICY_TOKEN_CONFIG),
681 primary_source_span,
682 ),
683 OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
684 OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
685 };
686 Some(Box::new(iter::once(labeled_span)))
687 }
688}
689
690#[derive(Debug)]
693pub struct ExpectedTokenConfig {
694 pub friendly_token_names: HashMap<&'static str, &'static str>,
698
699 pub impossible_tokens: HashSet<&'static str>,
704
705 pub special_identifier_tokens: HashSet<&'static str>,
712
713 pub identifier_sentinel: &'static str,
716
717 pub first_set_identifier_tokens: HashSet<&'static str>,
722
723 pub first_set_sentinel: &'static str,
727}
728
729lazy_static! {
730 static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
731 friendly_token_names: HashMap::from([
732 ("TRUE", "`true`"),
733 ("FALSE", "`false`"),
734 ("IF", "`if`"),
735 ("PERMIT", "`permit`"),
736 ("FORBID", "`forbid`"),
737 ("WHEN", "`when`"),
738 ("UNLESS", "`unless`"),
739 ("IN", "`in`"),
740 ("HAS", "`has`"),
741 ("LIKE", "`like`"),
742 ("IS", "`is`"),
743 ("THEN", "`then`"),
744 ("ELSE", "`else`"),
745 ("PRINCIPAL", "`principal`"),
746 ("ACTION", "`action`"),
747 ("RESOURCE", "`resource`"),
748 ("CONTEXT", "`context`"),
749 ("PRINCIPAL_SLOT", "`?principal`"),
750 ("RESOURCE_SLOT", "`?resource`"),
751 ("IDENTIFIER", "identifier"),
752 ("NUMBER", "number"),
753 ("STRINGLIT", "string literal"),
754 ]),
755 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
756 special_identifier_tokens: HashSet::from([
757 "PERMIT",
758 "FORBID",
759 "WHEN",
760 "UNLESS",
761 "IN",
762 "HAS",
763 "LIKE",
764 "IS",
765 "THEN",
766 "ELSE",
767 "PRINCIPAL",
768 "ACTION",
769 "RESOURCE",
770 "CONTEXT",
771 ]),
772 identifier_sentinel: "IDENTIFIER",
773 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
774 first_set_sentinel: "\"!\"",
775 };
776}
777
778pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
780 let mut expected = expected
781 .iter()
782 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
783 .map(|e| e.as_str())
784 .collect::<BTreeSet<_>>();
785 if expected.contains(config.identifier_sentinel) {
786 for token in config.special_identifier_tokens.iter() {
787 expected.remove(*token);
788 }
789 if !expected.contains(config.first_set_sentinel) {
790 for token in config.first_set_identifier_tokens.iter() {
791 expected.remove(*token);
792 }
793 }
794 }
795 if expected.is_empty() {
796 return None;
797 }
798
799 let mut expected_string = "expected ".to_owned();
800 #[allow(clippy::expect_used)]
802 join_with_conjunction(
803 &mut expected_string,
804 "or",
805 expected,
806 |f, token| match config.friendly_token_names.get(token) {
807 Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
808 None => write!(f, "{}", token.replace('"', "`")),
809 },
810 )
811 .expect("failed to format expected tokens");
812 Some(expected_string)
813}
814
815#[derive(Clone, Debug, PartialEq, Eq)]
818pub struct ParseErrors(NonEmpty<ParseError>);
819
820impl ParseErrors {
821 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
823 Self(NonEmpty::singleton(err.into()))
824 }
825
826 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
828 Self(NonEmpty {
829 head: first,
830 tail: rest.into_iter().collect::<Vec<_>>(),
831 })
832 }
833
834 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
836 Self(errs)
837 }
838
839 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
840 NonEmpty::collect(i).map(Self::new_from_nonempty)
841 }
842
843 pub(crate) fn flatten(errs: impl IntoIterator<Item = ParseErrors>) -> Option<Self> {
846 let mut errs = errs.into_iter();
847 let mut first = errs.next()?;
848 for inner in errs {
849 first.extend(inner);
850 }
851 Some(first)
852 }
853
854 pub(crate) fn transpose<T>(
858 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
859 ) -> Result<Vec<T>, Self> {
860 let iter = i.into_iter();
861 let (lower, upper) = iter.size_hint();
862 let capacity = upper.unwrap_or(lower);
863
864 let mut oks = Vec::with_capacity(capacity);
865 let mut errs = Vec::new();
866
867 for r in iter {
868 match r {
869 Ok(v) => oks.push(v),
870 Err(e) => errs.push(e),
871 }
872 }
873
874 if errs.is_empty() {
875 Ok(oks)
876 } else {
877 #[allow(clippy::unwrap_used)]
879 Err(Self::flatten(errs).unwrap())
880 }
881 }
882}
883
884impl Display for ParseErrors {
885 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
886 write!(f, "{}", self.first()) }
888}
889
890impl std::error::Error for ParseErrors {
891 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
892 self.first().source()
893 }
894
895 #[allow(deprecated)]
896 fn description(&self) -> &str {
897 self.first().description()
898 }
899
900 #[allow(deprecated)]
901 fn cause(&self) -> Option<&dyn std::error::Error> {
902 self.first().cause()
903 }
904}
905
906impl Diagnostic for ParseErrors {
911 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
912 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
914 errs.next().map(move |first_err| match first_err.related() {
915 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
916 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
917 })
918 }
919
920 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
921 self.first().code()
922 }
923
924 fn severity(&self) -> Option<miette::Severity> {
925 self.first().severity()
926 }
927
928 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
929 self.first().help()
930 }
931
932 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
933 self.first().url()
934 }
935
936 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
937 self.first().source_code()
938 }
939
940 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
941 self.first().labels()
942 }
943
944 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
945 self.first().diagnostic_source()
946 }
947}
948
949impl AsRef<NonEmpty<ParseError>> for ParseErrors {
950 fn as_ref(&self) -> &NonEmpty<ParseError> {
951 &self.0
952 }
953}
954
955impl AsMut<NonEmpty<ParseError>> for ParseErrors {
956 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
957 &mut self.0
958 }
959}
960
961impl Deref for ParseErrors {
962 type Target = NonEmpty<ParseError>;
963
964 fn deref(&self) -> &Self::Target {
965 &self.0
966 }
967}
968
969impl DerefMut for ParseErrors {
970 fn deref_mut(&mut self) -> &mut Self::Target {
971 &mut self.0
972 }
973}
974
975impl<T: Into<ParseError>> From<T> for ParseErrors {
976 fn from(err: T) -> Self {
977 ParseErrors::singleton(err.into())
978 }
979}
980
981impl<T: Into<ParseError>> Extend<T> for ParseErrors {
982 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
983 self.0.extend(iter.into_iter().map(Into::into))
984 }
985}
986
987impl IntoIterator for ParseErrors {
988 type Item = ParseError;
989 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
990
991 fn into_iter(self) -> Self::IntoIter {
992 self.0.into_iter()
993 }
994}
995
996impl<'a> IntoIterator for &'a ParseErrors {
997 type Item = &'a ParseError;
998 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
999
1000 fn into_iter(self) -> Self::IntoIter {
1001 iter::once(&self.head).chain(self.tail.iter())
1002 }
1003}