Start detecting version-related syntax errors in the parser (#16090)

## Summary

This PR builds on the changes in #16220 to pass a target Python version
to the parser. It also adds the `Parser::unsupported_syntax_errors` field, which
collects version-related syntax errors while parsing. These syntax
errors are then turned into `Message`s in ruff (in preview mode).

This PR only detects one syntax error (`match` statement before Python
3.10), but it has been pretty quick to extend to several other simple
errors (see #16308 for example).

## Test Plan

The current tests are CLI tests in the linter crate, but these could be
supplemented with inline parser tests after #16357.

I also tested the display of these syntax errors in VS Code:


![image](https://github.com/user-attachments/assets/062b4441-740e-46c3-887c-a954049ef26e)

![image](https://github.com/user-attachments/assets/101f55b8-146c-4d59-b6b0-922f19bcd0fa)

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Brent Westbrook 2025-02-25 23:03:48 -05:00 committed by GitHub
parent b39a4ad01d
commit 78806361fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 356 additions and 37 deletions

View file

@ -1,5 +1,6 @@
use std::fmt;
use std::fmt::{self, Display};
use ruff_python_ast::PythonVersion;
use ruff_text_size::TextRange;
use crate::TokenKind;
@ -426,6 +427,50 @@ impl std::fmt::Display for LexicalErrorType {
}
}
/// Represents a version-related syntax error detected during parsing.
///
/// An example of a version-related error is the use of a `match` statement before Python 3.10, when
/// it was first introduced. See [`UnsupportedSyntaxErrorKind`] for other kinds of errors.
#[derive(Debug, PartialEq, Clone)]
pub struct UnsupportedSyntaxError {
pub kind: UnsupportedSyntaxErrorKind,
pub range: TextRange,
/// The target [`PythonVersion`] for which this error was detected.
///
/// This is different from the version reported by the
/// [`minimum_version`](UnsupportedSyntaxError::minimum_version) method, which is the earliest
/// allowed version for this piece of syntax. The `target_version` is primarily used for
/// user-facing error messages.
pub target_version: PythonVersion,
}
impl UnsupportedSyntaxError {
/// The earliest allowed version for the syntax associated with this error.
pub const fn minimum_version(&self) -> PythonVersion {
match self.kind {
UnsupportedSyntaxErrorKind::MatchBeforePy310 => PythonVersion::PY310,
}
}
}
impl Display for UnsupportedSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
UnsupportedSyntaxErrorKind::MatchBeforePy310 => write!(
f,
"Cannot use `match` statement on Python {} (syntax was added in Python {})",
self.target_version,
self.minimum_version(),
),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum UnsupportedSyntaxErrorKind {
MatchBeforePy310,
}
#[cfg(target_pointer_width = "64")]
mod sizes {
use crate::error::{LexicalError, LexicalErrorType};