Skip to content

Commit 01bac61

Browse files
committed
Merge branch 'main' into dcreager/generate-ast
* main: [red-knot] Inline `SubclassOfType::as_instance_type_of_metaclass()` (#15556) [`flake8-comprehensions`] strip parentheses around generators in `unnecessary-generator-set` (`C401`) (#15553) [`pylint`] Implement `redefined-slots-in-subclass` (`W0244`) (#9640) [`flake8-bugbear`] Do not raise error if keyword argument is present and target-python version is less or equals than 3.9 (`B903`) (#15549) [red-knot] `type[T]` is disjoint from `type[S]` if the metaclass of `T` is disjoint from the metaclass of `S` (#15547) [red-knot] Pure instance variables declared in class body (#15515) Update snapshots of #15507 with new annotated snipetts rendering (#15546) [`pylint`] Do not report methods with only one `EM101`-compatible `raise` (`PLR6301`) (#15507) Fix unstable f-string formatting for expressions containing a trailing comma (#15545) Support `knot.toml` files in project discovery (#15505) Add support for configuring knot in `pyproject.toml` files (#15493) Fix bracket spacing for single-element tuples in f-string expressions (#15537) [`flake8-simplify`] Do not emit diagnostics for expressions inside string type annotations (`SIM222`, `SIM223`) (#15405) [`flake8-pytest-style`] Do not emit diagnostics for empty `for` loops (`PT012`, `PT031`) (#15542)
2 parents d0aaa58 + 4351d85 commit 01bac61

84 files changed

Lines changed: 2465 additions & 540 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

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

crates/red_knot/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ tracing-flame = { workspace = true }
3232
tracing-tree = { workspace = true }
3333

3434
[dev-dependencies]
35+
ruff_db = { workspace = true, features = ["testing"] }
36+
37+
insta = { workspace = true, features = ["filters"] }
38+
insta-cmd = { workspace = true }
3539
filetime = { workspace = true }
40+
regex = { workspace = true }
3641
tempfile = { workspace = true }
37-
ruff_db = { workspace = true, features = ["testing"] }
42+
toml = { workspace = true }
3843

3944
[lints]
4045
workspace = true

crates/red_knot/src/main.rs

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use python_version::PythonVersion;
99
use red_knot_python_semantic::SitePackages;
1010
use red_knot_server::run_server;
1111
use red_knot_workspace::db::ProjectDatabase;
12-
use red_knot_workspace::project::settings::Configuration;
12+
use red_knot_workspace::project::options::{EnvironmentOptions, Options};
1313
use red_knot_workspace::project::ProjectMetadata;
1414
use red_knot_workspace::watch;
1515
use red_knot_workspace::watch::ProjectWatcher;
@@ -71,31 +71,30 @@ struct Args {
7171
}
7272

7373
impl Args {
74-
fn to_configuration(&self, cli_cwd: &SystemPath) -> Configuration {
75-
let mut configuration = Configuration::default();
76-
77-
if let Some(python_version) = self.python_version {
78-
configuration.python_version = Some(python_version.into());
79-
}
80-
81-
if let Some(venv_path) = &self.venv_path {
82-
configuration.search_paths.site_packages = Some(SitePackages::Derived {
83-
venv_path: SystemPath::absolute(venv_path, cli_cwd),
84-
});
85-
}
86-
87-
if let Some(typeshed) = &self.typeshed {
88-
configuration.search_paths.typeshed = Some(SystemPath::absolute(typeshed, cli_cwd));
74+
fn to_options(&self, cli_cwd: &SystemPath) -> Options {
75+
Options {
76+
environment: Some(EnvironmentOptions {
77+
python_version: self.python_version.map(Into::into),
78+
venv_path: self
79+
.venv_path
80+
.as_ref()
81+
.map(|venv_path| SitePackages::Derived {
82+
venv_path: SystemPath::absolute(venv_path, cli_cwd),
83+
}),
84+
typeshed: self
85+
.typeshed
86+
.as_ref()
87+
.map(|typeshed| SystemPath::absolute(typeshed, cli_cwd)),
88+
extra_paths: self.extra_search_path.as_ref().map(|extra_search_paths| {
89+
extra_search_paths
90+
.iter()
91+
.map(|path| SystemPath::absolute(path, cli_cwd))
92+
.collect()
93+
}),
94+
..EnvironmentOptions::default()
95+
}),
96+
..Default::default()
8997
}
90-
91-
if let Some(extra_search_paths) = &self.extra_search_path {
92-
configuration.search_paths.extra_paths = extra_search_paths
93-
.iter()
94-
.map(|path| Some(SystemPath::absolute(path, cli_cwd)))
95-
.collect();
96-
}
97-
98-
configuration
9998
}
10099
}
101100

@@ -164,18 +163,13 @@ fn run() -> anyhow::Result<ExitStatus> {
164163
.unwrap_or_else(|| cli_base_path.clone());
165164

166165
let system = OsSystem::new(cwd.clone());
167-
let cli_configuration = args.to_configuration(&cwd);
168-
let workspace_metadata = ProjectMetadata::discover(
169-
system.current_directory(),
170-
&system,
171-
Some(&cli_configuration),
172-
)?;
173-
174-
// TODO: Use the `program_settings` to compute the key for the database's persistent
175-
// cache and load the cache if it exists.
166+
let cli_options = args.to_options(&cwd);
167+
let mut workspace_metadata = ProjectMetadata::discover(system.current_directory(), &system)?;
168+
workspace_metadata.apply_cli_options(cli_options.clone());
169+
176170
let mut db = ProjectDatabase::new(workspace_metadata, system)?;
177171

178-
let (main_loop, main_loop_cancellation_token) = MainLoop::new(cli_configuration);
172+
let (main_loop, main_loop_cancellation_token) = MainLoop::new(cli_options);
179173

180174
// Listen to Ctrl+C and abort the watch mode.
181175
let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token));
@@ -228,19 +222,19 @@ struct MainLoop {
228222
/// The file system watcher, if running in watch mode.
229223
watcher: Option<ProjectWatcher>,
230224

231-
cli_configuration: Configuration,
225+
cli_options: Options,
232226
}
233227

234228
impl MainLoop {
235-
fn new(cli_configuration: Configuration) -> (Self, MainLoopCancellationToken) {
229+
fn new(cli_options: Options) -> (Self, MainLoopCancellationToken) {
236230
let (sender, receiver) = crossbeam_channel::bounded(10);
237231

238232
(
239233
Self {
240234
sender: sender.clone(),
241235
receiver,
242236
watcher: None,
243-
cli_configuration,
237+
cli_options,
244238
},
245239
MainLoopCancellationToken { sender },
246240
)
@@ -324,7 +318,7 @@ impl MainLoop {
324318
MainLoopMessage::ApplyChanges(changes) => {
325319
revision += 1;
326320
// Automatically cancels any pending queries and waits for them to complete.
327-
db.apply_changes(changes, Some(&self.cli_configuration));
321+
db.apply_changes(changes, Some(&self.cli_options));
328322
if let Some(watcher) = self.watcher.as_mut() {
329323
watcher.update(db);
330324
}

crates/red_knot/tests/cli.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use anyhow::Context;
2+
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
3+
use std::process::Command;
4+
use tempfile::TempDir;
5+
6+
/// Specifying an option on the CLI should take precedence over the same setting in the
7+
/// project's configuration.
8+
#[test]
9+
fn test_config_override() -> anyhow::Result<()> {
10+
let tempdir = TempDir::new()?;
11+
12+
std::fs::write(
13+
tempdir.path().join("pyproject.toml"),
14+
r#"
15+
[tool.knot.environment]
16+
python-version = "3.11"
17+
"#,
18+
)
19+
.context("Failed to write settings")?;
20+
21+
std::fs::write(
22+
tempdir.path().join("test.py"),
23+
r#"
24+
import sys
25+
26+
# Access `sys.last_exc` that was only added in Python 3.12
27+
print(sys.last_exc)
28+
"#,
29+
)
30+
.context("Failed to write test.py")?;
31+
32+
insta::with_settings!({filters => vec![(&*tempdir_filter(&tempdir), "<temp_dir>/")]}, {
33+
assert_cmd_snapshot!(knot().arg("--project").arg(tempdir.path()), @r"
34+
success: false
35+
exit_code: 1
36+
----- stdout -----
37+
error[lint:unresolved-attribute] <temp_dir>/test.py:5:7 Type `<module 'sys'>` has no attribute `last_exc`
38+
39+
----- stderr -----
40+
");
41+
});
42+
43+
assert_cmd_snapshot!(knot().arg("--project").arg(tempdir.path()).arg("--python-version").arg("3.12"), @r"
44+
success: true
45+
exit_code: 0
46+
----- stdout -----
47+
48+
----- stderr -----
49+
");
50+
51+
Ok(())
52+
}
53+
54+
fn knot() -> Command {
55+
Command::new(get_cargo_bin("red_knot"))
56+
}
57+
58+
fn tempdir_filter(tempdir: &TempDir) -> String {
59+
format!(r"{}\\?/?", regex::escape(tempdir.path().to_str().unwrap()))
60+
}

0 commit comments

Comments
 (0)