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