Skip to main content

php_rs_parser/
diagnostics.rs

1use php_ast::Span;
2use php_lexer::TokenKind;
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)]
12pub enum ParseError {
13    /// A specific token was expected but a different one was found.
14    #[error("expected {expected}, found {found}")]
15    Expected {
16        expected: Cow<'static, str>,
17        found: TokenKind,
18        span: Span,
19    },
20
21    /// A token appeared where it is not valid.
22    #[error("unexpected token {found}")]
23    Unexpected { found: TokenKind, span: Span },
24
25    /// An expression was expected but not found (e.g. empty parentheses).
26    #[error("expected expression")]
27    ExpectedExpression { span: Span },
28
29    /// A statement was expected but not found.
30    #[error("expected statement")]
31    ExpectedStatement { span: Span },
32
33    /// PHP source must start with `<?php` or `<?`.
34    #[error("expected opening PHP tag")]
35    ExpectedOpenTag { span: Span },
36
37    /// A string literal was opened but never closed.
38    #[error("unterminated string literal")]
39    UnterminatedString { span: Span },
40
41    /// A required token was missing after another construct.
42    #[error("expected {expected} after {after}")]
43    ExpectedAfter {
44        expected: Cow<'static, str>,
45        after: Cow<'static, str>,
46        span: Span,
47    },
48
49    /// A delimiter (parenthesis, bracket, brace) was opened but never closed.
50    #[error("unclosed {delimiter} opened at {opened_at:?}")]
51    UnclosedDelimiter {
52        delimiter: Cow<'static, str>,
53        opened_at: Span,
54        span: Span,
55    },
56
57    /// A construct that is syntactically valid but semantically forbidden
58    /// (e.g. `(unset)` cast, deprecated syntax).
59    #[error("{message}")]
60    Forbidden {
61        message: Cow<'static, str>,
62        span: Span,
63    },
64
65    /// Syntax that requires a newer PHP version than the targeted one.
66    /// Emitted by [`crate::parse_versioned`] when the source uses features
67    /// unavailable in the specified [`crate::PhpVersion`].
68    #[error("'{feature}' requires PHP {required} or higher (targeting PHP {used})")]
69    VersionTooLow {
70        feature: Cow<'static, str>,
71        required: Cow<'static, str>,
72        used: Cow<'static, str>,
73        span: Span,
74    },
75}
76
77impl ParseError {
78    pub fn span(&self) -> Span {
79        match self {
80            ParseError::Expected { span, .. }
81            | ParseError::Unexpected { span, .. }
82            | ParseError::ExpectedExpression { span }
83            | ParseError::ExpectedStatement { span }
84            | ParseError::ExpectedOpenTag { span }
85            | ParseError::UnterminatedString { span }
86            | ParseError::ExpectedAfter { span, .. }
87            | ParseError::UnclosedDelimiter { span, .. }
88            | ParseError::Forbidden { span, .. }
89            | ParseError::VersionTooLow { span, .. } => *span,
90        }
91    }
92}