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}