Skip to main content

php_rs_parser/
diagnostics.rs

1use crate::PhpVersion;
2use php_ast::Span;
3use std::borrow::Cow;
4use thiserror::Error;
5
6/// A parse error or diagnostic emitted during parsing.
7///
8/// The parser recovers from all errors and always produces a complete AST,
9/// so errors are informational rather than fatal. Each variant carries a
10/// [`Span`] identifying the source location.
11#[derive(Debug, Clone, Error)]
12#[non_exhaustive]
13pub enum ParseError {
14    /// A specific token was expected but a different one was found.
15    #[error("expected {expected}, found {found}")]
16    #[non_exhaustive]
17    Expected {
18        /// Description of the expected token.
19        expected: Cow<'static, str>,
20        /// Description of the token actually found, e.g. `"';'"` or
21        /// `"identifier"`. A description rather than the lexer's token kind,
22        /// so that new tokens are not a breaking change for this type.
23        found: Cow<'static, str>,
24        /// Source range of the error.
25        span: Span,
26    },
27
28    /// An expression was expected but not found (e.g. empty parentheses).
29    #[error("expected expression")]
30    #[non_exhaustive]
31    ExpectedExpression {
32        /// Source range of the error.
33        span: Span,
34    },
35
36    /// A statement was expected but not found.
37    #[error("expected statement")]
38    #[non_exhaustive]
39    ExpectedStatement {
40        /// Source range of the error.
41        span: Span,
42    },
43
44    /// PHP source must start with `<?php` or `<?`.
45    #[error("expected opening PHP tag")]
46    #[non_exhaustive]
47    ExpectedOpenTag {
48        /// Source range of the error.
49        span: Span,
50    },
51
52    /// A string literal was opened but never closed.
53    #[error("unterminated string literal")]
54    #[non_exhaustive]
55    UnterminatedString {
56        /// Source range of the error.
57        span: Span,
58    },
59
60    /// A required token was missing after another construct.
61    #[error("expected {expected} after {after}")]
62    #[non_exhaustive]
63    ExpectedAfter {
64        /// Description of the expected token.
65        expected: Cow<'static, str>,
66        /// Construct the token should follow.
67        after: Cow<'static, str>,
68        /// Source range of the error.
69        span: Span,
70    },
71
72    /// A delimiter (parenthesis, bracket, brace) was opened but never closed.
73    #[error("expected {delimiter} to close the delimiter opened at byte {}", opened_at.start)]
74    #[non_exhaustive]
75    UnclosedDelimiter {
76        /// The expected closing delimiter, e.g. `"')'"`.
77        delimiter: Cow<'static, str>,
78        /// Where the delimiter was opened.
79        opened_at: Span,
80        /// Source range of the error.
81        span: Span,
82    },
83
84    /// A construct that is syntactically valid but semantically forbidden
85    /// (e.g. `(unset)` cast, deprecated syntax). Equivalent to a PHP fatal.
86    #[error("{message}")]
87    #[non_exhaustive]
88    Forbidden {
89        /// Error message.
90        message: Cow<'static, str>,
91        /// Source range of the error.
92        span: Span,
93    },
94
95    /// Statements, expressions or string interpolations are nested deeper than
96    /// the parser's recursion budget. The innermost construct is replaced by an
97    /// error node so that parsing stays within a bounded stack.
98    #[error("maximum nesting depth exceeded")]
99    #[non_exhaustive]
100    NestingTooDeep {
101        /// Source range of the error.
102        span: Span,
103    },
104
105    /// The source is larger than 4 GiB, so byte offsets no longer fit in a
106    /// [`Span`]. Nothing is parsed and the program is empty.
107    #[error("source is {len} bytes; the maximum is 4 GiB")]
108    #[non_exhaustive]
109    SourceTooLarge {
110        /// Length of the rejected source in bytes.
111        len: usize,
112        /// Always empty (`0..0`).
113        span: Span,
114    },
115
116    /// Syntax that requires a newer PHP version than the targeted one.
117    /// Emitted by [`crate::parse_with`] when the source uses features
118    /// unavailable in the specified [`crate::PhpVersion`].
119    #[error("'{feature}' requires PHP {required} or higher")]
120    #[non_exhaustive]
121    VersionTooLow {
122        /// Name of the feature used.
123        feature: Cow<'static, str>,
124        /// Minimum PHP version required.
125        required: PhpVersion,
126        /// PHP version targeted.
127        used: PhpVersion,
128        /// Source range of the error.
129        span: Span,
130    },
131}
132
133impl ParseError {
134    /// Source range of the error.
135    pub fn span(&self) -> Span {
136        match self {
137            ParseError::Expected { span, .. }
138            | ParseError::ExpectedExpression { span }
139            | ParseError::ExpectedStatement { span }
140            | ParseError::ExpectedOpenTag { span }
141            | ParseError::UnterminatedString { span }
142            | ParseError::ExpectedAfter { span, .. }
143            | ParseError::UnclosedDelimiter { span, .. }
144            | ParseError::Forbidden { span, .. }
145            | ParseError::NestingTooDeep { span }
146            | ParseError::SourceTooLarge { span, .. }
147            | ParseError::VersionTooLow { span, .. } => *span,
148        }
149    }
150}