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 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
689 match self.err {
690 OwnedRawParseError::InvalidToken { location } => {
691 let tok_last_char = self.src.get(location..)?.chars().next()?;
692 match tok_last_char {
693 '"' => Some(Box::new(
694 "try checking that all strings are closed properly",
695 )),
696 '\'' => Some(Box::new(
697 "strings must use double quotes, not single quotes",
698 )),
699 _ => None,
700 }
701 }
702 _ => None,
703 }
704 }
705}
706
707#[derive(Debug)]
710pub struct ExpectedTokenConfig {
711 pub friendly_token_names: HashMap<&'static str, &'static str>,
715
716 pub impossible_tokens: HashSet<&'static str>,
721
722 pub special_identifier_tokens: HashSet<&'static str>,
729
730 pub identifier_sentinel: &'static str,
733
734 pub first_set_identifier_tokens: HashSet<&'static str>,
739
740 pub first_set_sentinel: &'static str,
744}
745
746static POLICY_TOKEN_CONFIG: LazyLock<ExpectedTokenConfig> = LazyLock::new(|| ExpectedTokenConfig {
747 friendly_token_names: HashMap::from([
748 ("TRUE", "`true`"),
749 ("FALSE", "`false`"),
750 ("IF", "`if`"),
751 ("PERMIT", "`permit`"),
752 ("FORBID", "`forbid`"),
753 ("WHEN", "`when`"),
754 ("UNLESS", "`unless`"),
755 ("IN", "`in`"),
756 ("HAS", "`has`"),
757 ("LIKE", "`like`"),
758 ("IS", "`is`"),
759 ("THEN", "`then`"),
760 ("ELSE", "`else`"),
761 ("PRINCIPAL", "`principal`"),
762 ("ACTION", "`action`"),
763 ("RESOURCE", "`resource`"),
764 ("CONTEXT", "`context`"),
765 ("PRINCIPAL_SLOT", "`?principal`"),
766 ("RESOURCE_SLOT", "`?resource`"),
767 ("IDENTIFIER", "identifier"),
768 ("NUMBER", "number"),
769 ("STRINGLIT", "string literal"),
770 ]),
771 impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
772 special_identifier_tokens: HashSet::from([
773 "PERMIT",
774 "FORBID",
775 "WHEN",
776 "UNLESS",
777 "IN",
778 "HAS",
779 "LIKE",
780 "IS",
781 "THEN",
782 "ELSE",
783 "PRINCIPAL",
784 "ACTION",
785 "RESOURCE",
786 "CONTEXT",
787 ]),
788 identifier_sentinel: "IDENTIFIER",
789 first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
790 first_set_sentinel: "\"!\"",
791});
792
793pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
795 let mut expected = expected
796 .iter()
797 .filter(|e| !config.impossible_tokens.contains(e.as_str()))
798 .map(|e| e.as_str())
799 .collect::<BTreeSet<_>>();
800 if expected.contains(config.identifier_sentinel) {
801 for token in config.special_identifier_tokens.iter() {
802 expected.remove(*token);
803 }
804 if !expected.contains(config.first_set_sentinel) {
805 for token in config.first_set_identifier_tokens.iter() {
806 expected.remove(*token);
807 }
808 }
809 }
810 if expected.is_empty() {
811 return None;
812 }
813
814 let mut expected_string = "expected ".to_owned();
815 #[allow(clippy::expect_used)]
817 join_with_conjunction(
818 &mut expected_string,
819 "or",
820 expected,
821 |f, token| match config.friendly_token_names.get(token) {
822 Some(friendly_token_name) => write!(f, "{friendly_token_name}"),
823 None => write!(f, "{}", token.replace('"', "`")),
824 },
825 )
826 .expect("failed to format expected tokens");
827 Some(expected_string)
828}
829
830#[derive(Clone, Debug, PartialEq, Eq)]
833pub struct ParseErrors(NonEmpty<ParseError>);
834
835impl ParseErrors {
836 pub(crate) fn singleton(err: impl Into<ParseError>) -> Self {
838 Self(NonEmpty::singleton(err.into()))
839 }
840
841 pub(crate) fn new(first: ParseError, rest: impl IntoIterator<Item = ParseError>) -> Self {
843 Self(NonEmpty {
844 head: first,
845 tail: rest.into_iter().collect::<Vec<_>>(),
846 })
847 }
848
849 pub(crate) fn new_from_nonempty(errs: NonEmpty<ParseError>) -> Self {
851 Self(errs)
852 }
853
854 pub(crate) fn from_iter(i: impl IntoIterator<Item = ParseError>) -> Option<Self> {
855 NonEmpty::collect(i).map(Self::new_from_nonempty)
856 }
857
858 pub(crate) fn flatten(errs: impl IntoIterator<Item = ParseErrors>) -> Option<Self> {
861 let mut errs = errs.into_iter();
862 let mut first = errs.next()?;
863 for inner in errs {
864 first.extend(inner);
865 }
866 Some(first)
867 }
868
869 pub(crate) fn transpose<T>(
873 i: impl IntoIterator<Item = Result<T, ParseErrors>>,
874 ) -> Result<Vec<T>, Self> {
875 let iter = i.into_iter();
876 let (lower, upper) = iter.size_hint();
877 let capacity = upper.unwrap_or(lower);
878
879 let mut oks = Vec::with_capacity(capacity);
880 let mut errs = Vec::new();
881
882 for r in iter {
883 match r {
884 Ok(v) => oks.push(v),
885 Err(e) => errs.push(e),
886 }
887 }
888
889 if errs.is_empty() {
890 Ok(oks)
891 } else {
892 #[allow(clippy::unwrap_used)]
894 Err(Self::flatten(errs).unwrap())
895 }
896 }
897}
898
899impl Display for ParseErrors {
900 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901 write!(f, "{}", self.first()) }
903}
904
905impl std::error::Error for ParseErrors {
906 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
907 self.first().source()
908 }
909
910 #[allow(deprecated)]
911 fn description(&self) -> &str {
912 self.first().description()
913 }
914
915 #[allow(deprecated)]
916 fn cause(&self) -> Option<&dyn std::error::Error> {
917 self.first().cause()
918 }
919}
920
921impl Diagnostic for ParseErrors {
926 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
927 let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
929 errs.next().map(move |first_err| match first_err.related() {
930 Some(first_err_related) => Box::new(first_err_related.chain(errs)),
931 None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
932 })
933 }
934
935 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
936 self.first().code()
937 }
938
939 fn severity(&self) -> Option<miette::Severity> {
940 self.first().severity()
941 }
942
943 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
944 self.first().help()
945 }
946
947 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
948 self.first().url()
949 }
950
951 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
952 self.first().source_code()
953 }
954
955 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
956 self.first().labels()
957 }
958
959 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
960 self.first().diagnostic_source()
961 }
962}
963
964impl AsRef<NonEmpty<ParseError>> for ParseErrors {
965 fn as_ref(&self) -> &NonEmpty<ParseError> {
966 &self.0
967 }
968}
969
970impl AsMut<NonEmpty<ParseError>> for ParseErrors {
971 fn as_mut(&mut self) -> &mut NonEmpty<ParseError> {
972 &mut self.0
973 }
974}
975
976impl Deref for ParseErrors {
977 type Target = NonEmpty<ParseError>;
978
979 fn deref(&self) -> &Self::Target {
980 &self.0
981 }
982}
983
984impl DerefMut for ParseErrors {
985 fn deref_mut(&mut self) -> &mut Self::Target {
986 &mut self.0
987 }
988}
989
990impl<T: Into<ParseError>> From<T> for ParseErrors {
991 fn from(err: T) -> Self {
992 ParseErrors::singleton(err.into())
993 }
994}
995
996impl<T: Into<ParseError>> Extend<T> for ParseErrors {
997 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
998 self.0.extend(iter.into_iter().map(Into::into))
999 }
1000}
1001
1002impl IntoIterator for ParseErrors {
1003 type Item = ParseError;
1004 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
1005
1006 fn into_iter(self) -> Self::IntoIter {
1007 self.0.into_iter()
1008 }
1009}
1010
1011impl<'a> IntoIterator for &'a ParseErrors {
1012 type Item = &'a ParseError;
1013 type IntoIter = iter::Chain<iter::Once<Self::Item>, std::slice::Iter<'a, ParseError>>;
1014
1015 fn into_iter(self) -> Self::IntoIter {
1016 iter::once(&self.head).chain(self.tail.iter())
1017 }
1018}