Skip to content

Commit a85871d

Browse files
committed
Merge branch 'main' into cjm/cfg4
* main: CI: add job to run tests under minimum supported rust version (msrv) (#11737) Lexer should consider BOM for the start offset (#11732) Use cursor offset for lexer checkpoint (#11734) red-knot: Change `resolve_global_symbol` to take `Module` as an argument (#11723) red-knot: Use `parse_unchecked` to get all parse errors (#11725) Respect per-file ignores for blanket and redirected noqa rules (#11728) [`pygrep_hooks`] Check blanket ignores via file-level pragmas (`PGH004`) (#11540)
2 parents d42fff9 + 2c86502 commit a85871d

26 files changed

+501
-228
lines changed

.github/workflows/ci.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,38 @@ jobs:
212212
- name: "Build"
213213
run: cargo build --release --locked
214214

215+
cargo-build-msrv:
216+
name: "cargo build (msrv)"
217+
runs-on: ubuntu-latest
218+
needs: determine_changes
219+
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
220+
timeout-minutes: 20
221+
steps:
222+
- uses: actions/checkout@v4
223+
- uses: SebRollen/[email protected]
224+
id: msrv
225+
with:
226+
file: "Cargo.toml"
227+
field: "workspace.package.rust-version"
228+
- name: "Install Rust toolchain"
229+
run: rustup default ${{ steps.msrv.outputs.value }}
230+
- name: "Install mold"
231+
uses: rui314/setup-mold@v1
232+
- name: "Install cargo nextest"
233+
uses: taiki-e/install-action@v2
234+
with:
235+
tool: cargo-nextest
236+
- name: "Install cargo insta"
237+
uses: taiki-e/install-action@v2
238+
with:
239+
tool: cargo-insta
240+
- uses: Swatinem/rust-cache@v2
241+
- name: "Run tests"
242+
shell: bash
243+
env:
244+
NEXTEST_PROFILE: "ci"
245+
run: cargo +${{ steps.msrv.outputs.value }} insta test --all-features --unreferenced reject --test-runner nextest
246+
215247
cargo-fuzz:
216248
name: "cargo fuzz"
217249
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 0 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ resolver = "2"
44

55
[workspace.package]
66
edition = "2021"
7-
rust-version = "1.71"
7+
rust-version = "1.74"
88
homepage = "https://docs.astral.sh/ruff"
99
documentation = "https://docs.astral.sh/ruff"
1010
repository = "https://github.com/astral-sh/ruff"

crates/red_knot/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ tracing-subscriber = { workspace = true }
3535
tracing-tree = { workspace = true }
3636

3737
[dev-dependencies]
38-
textwrap = { version = "0.16.1" }
3938
tempfile = { workspace = true }
4039

4140
[lints]

crates/red_knot/src/lint.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use std::time::Duration;
55

66
use ruff_python_ast::visitor::Visitor;
77
use ruff_python_ast::{ModModule, StringLiteral};
8+
use ruff_python_parser::Parsed;
89

910
use crate::cache::KeyValueCache;
1011
use crate::db::{LintDb, LintJar, QueryResult};
1112
use crate::files::FileId;
12-
use crate::module::ModuleName;
13-
use crate::parse::{parse, Parsed};
13+
use crate::module::{resolve_module, ModuleName};
14+
use crate::parse::parse;
1415
use crate::source::{source_text, Source};
1516
use crate::symbols::{
1617
resolve_global_symbol, symbol_table, Definition, GlobalSymbolId, SymbolId, SymbolTable,
@@ -40,7 +41,7 @@ pub(crate) fn lint_syntax(db: &dyn LintDb, file_id: FileId) -> QueryResult<Diagn
4041
let parsed = parse(db.upcast(), *file_id)?;
4142

4243
if parsed.errors().is_empty() {
43-
let ast = parsed.ast();
44+
let ast = parsed.syntax();
4445

4546
let mut visitor = SyntaxLintVisitor {
4647
diagnostics,
@@ -86,7 +87,7 @@ pub(crate) fn lint_semantic(db: &dyn LintDb, file_id: FileId) -> QueryResult<Dia
8687
let context = SemanticLintContext {
8788
file_id: *file_id,
8889
source,
89-
parsed,
90+
parsed: &parsed,
9091
symbols,
9192
db,
9293
diagnostics: RefCell::new(Vec::new()),
@@ -144,9 +145,7 @@ fn lint_bad_overrides(context: &SemanticLintContext) -> QueryResult<()> {
144145
// TODO we should have a special marker on the real typing module (from typeshed) so if you
145146
// have your own "typing" module in your project, we don't consider it THE typing module (and
146147
// same for other stdlib modules that our lint rules care about)
147-
let Some(typing_override) =
148-
resolve_global_symbol(context.db.upcast(), ModuleName::new("typing"), "override")?
149-
else {
148+
let Some(typing_override) = context.resolve_global_symbol("typing", "override")? else {
150149
// TODO once we bundle typeshed, this should be unreachable!()
151150
return Ok(());
152151
};
@@ -194,7 +193,7 @@ fn lint_bad_overrides(context: &SemanticLintContext) -> QueryResult<()> {
194193
pub struct SemanticLintContext<'a> {
195194
file_id: FileId,
196195
source: Source,
197-
parsed: Parsed,
196+
parsed: &'a Parsed<ModModule>,
198197
symbols: Arc<SymbolTable>,
199198
db: &'a dyn LintDb,
200199
diagnostics: RefCell<Vec<String>>,
@@ -209,8 +208,8 @@ impl<'a> SemanticLintContext<'a> {
209208
self.file_id
210209
}
211210

212-
pub fn ast(&self) -> &ModModule {
213-
self.parsed.ast()
211+
pub fn ast(&self) -> &'a ModModule {
212+
self.parsed.syntax()
214213
}
215214

216215
pub fn symbols(&self) -> &SymbolTable {
@@ -234,6 +233,18 @@ impl<'a> SemanticLintContext<'a> {
234233
pub fn extend_diagnostics(&mut self, diagnostics: impl IntoIterator<Item = String>) {
235234
self.diagnostics.get_mut().extend(diagnostics);
236235
}
236+
237+
pub fn resolve_global_symbol(
238+
&self,
239+
module: &str,
240+
symbol_name: &str,
241+
) -> QueryResult<Option<GlobalSymbolId>> {
242+
let Some(module) = resolve_module(self.db.upcast(), ModuleName::new(module))? else {
243+
return Ok(None);
244+
};
245+
246+
resolve_global_symbol(self.db.upcast(), module, symbol_name)
247+
}
237248
}
238249

239250
#[derive(Debug)]

crates/red_knot/src/parse.rs

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,33 @@
11
use std::ops::{Deref, DerefMut};
22
use std::sync::Arc;
33

4-
use ruff_python_ast as ast;
5-
use ruff_python_parser::{Mode, ParseError};
6-
use ruff_text_size::{Ranged, TextRange};
4+
use ruff_python_ast::ModModule;
5+
use ruff_python_parser::Parsed;
76

87
use crate::cache::KeyValueCache;
98
use crate::db::{QueryResult, SourceDb};
109
use crate::files::FileId;
1110
use crate::source::source_text;
1211

13-
#[derive(Debug, Clone, PartialEq)]
14-
pub struct Parsed {
15-
inner: Arc<ParsedInner>,
16-
}
17-
18-
#[derive(Debug, PartialEq)]
19-
struct ParsedInner {
20-
ast: ast::ModModule,
21-
errors: Vec<ParseError>,
22-
}
23-
24-
impl Parsed {
25-
fn new(ast: ast::ModModule, errors: Vec<ParseError>) -> Self {
26-
Self {
27-
inner: Arc::new(ParsedInner { ast, errors }),
28-
}
29-
}
30-
31-
pub(crate) fn from_text(text: &str) -> Self {
32-
let result = ruff_python_parser::parse(text, Mode::Module);
33-
34-
let (module, errors) = match result {
35-
Ok(parsed) => match parsed.into_syntax() {
36-
ast::Mod::Module(module) => (module, vec![]),
37-
ast::Mod::Expression(expression) => (
38-
ast::ModModule {
39-
range: expression.range(),
40-
body: vec![ast::Stmt::Expr(ast::StmtExpr {
41-
range: expression.range(),
42-
value: expression.body,
43-
})],
44-
},
45-
vec![],
46-
),
47-
},
48-
Err(errors) => (
49-
ast::ModModule {
50-
range: TextRange::default(),
51-
body: Vec::new(),
52-
},
53-
vec![errors],
54-
),
55-
};
56-
57-
Parsed::new(module, errors)
58-
}
59-
60-
pub fn ast(&self) -> &ast::ModModule {
61-
&self.inner.ast
62-
}
63-
64-
pub fn errors(&self) -> &[ParseError] {
65-
&self.inner.errors
66-
}
67-
}
68-
6912
#[tracing::instrument(level = "debug", skip(db))]
70-
pub(crate) fn parse(db: &dyn SourceDb, file_id: FileId) -> QueryResult<Parsed> {
13+
pub(crate) fn parse(db: &dyn SourceDb, file_id: FileId) -> QueryResult<Arc<Parsed<ModModule>>> {
7114
let jar = db.jar()?;
7215

7316
jar.parsed.get(&file_id, |file_id| {
7417
let source = source_text(db, *file_id)?;
7518

76-
Ok(Parsed::from_text(source.text()))
19+
Ok(Arc::new(ruff_python_parser::parse_unchecked_source(
20+
source.text(),
21+
source.kind().into(),
22+
)))
7723
})
7824
}
7925

8026
#[derive(Debug, Default)]
81-
pub struct ParsedStorage(KeyValueCache<FileId, Parsed>);
27+
pub struct ParsedStorage(KeyValueCache<FileId, Arc<Parsed<ModModule>>>);
8228

8329
impl Deref for ParsedStorage {
84-
type Target = KeyValueCache<FileId, Parsed>;
30+
type Target = KeyValueCache<FileId, Arc<Parsed<ModModule>>>;
8531

8632
fn deref(&self) -> &Self::Target {
8733
&self.0

crates/red_knot/src/source.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ pub enum SourceKind {
5353
IpyNotebook(Arc<Notebook>),
5454
}
5555

56+
impl<'a> From<&'a SourceKind> for PySourceType {
57+
fn from(value: &'a SourceKind) -> Self {
58+
match value {
59+
SourceKind::Python(_) => PySourceType::Python,
60+
SourceKind::Stub(_) => PySourceType::Stub,
61+
SourceKind::IpyNotebook(_) => PySourceType::Ipynb,
62+
}
63+
}
64+
}
65+
5666
#[derive(Debug, Clone, PartialEq)]
5767
pub struct Source {
5868
kind: SourceKind,

0 commit comments

Comments
 (0)