Skip to content

Commit 669aa21

Browse files
committed
Merge remote-tracking branch 'origin/main' into dcreager/special-class
* origin/main: [red-knot] Add `Type::TypeVar` variant (#17102) [red-knot] update to latest Salsa with fixpoint caching fix (#17179) Upgrade to Rust 1.86 and bump MSRV to 1.84 (#17171) [red-knot] Avoid unresolved-reference in unreachable code (#17169) Fix relative import resolution in `site-packages` packages when the `site-packages` search path is a subdirectory of the first-party search path (#17178) [DO NOT LAND] bump Salsa version (#17176) [syntax-errors] Detect duplicate keys in `match` mapping patterns (#17129)
2 parents 6b27947 + 64e7e1a commit 669aa21

File tree

148 files changed

+1742
-303
lines changed

Some content is hidden

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

148 files changed

+1742
-303
lines changed

Cargo.lock

+12-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+7-2
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.83"
7+
rust-version = "1.84"
88
homepage = "https://docs.astral.sh/ruff"
99
documentation = "https://docs.astral.sh/ruff"
1010
repository = "https://github.com/astral-sh/ruff"
@@ -124,7 +124,7 @@ rayon = { version = "1.10.0" }
124124
regex = { version = "1.10.2" }
125125
rustc-hash = { version = "2.0.0" }
126126
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
127-
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "d758691ba17ee1a60c5356ea90888d529e1782ad" }
127+
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "296a8c78da1b54c76ff5795eb4c1e3fe2467e9fc"}
128128
schemars = { version = "0.8.16" }
129129
seahash = { version = "4.1.0" }
130130
serde = { version = "1.0.197", features = ["derive"] }
@@ -209,6 +209,7 @@ must_use_candidate = "allow"
209209
similar_names = "allow"
210210
single_match_else = "allow"
211211
too_many_lines = "allow"
212+
needless_continue = "allow" # An explicit continue can be more readable, especially if the alternative is an empty block.
212213
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
213214
needless_raw_string_hashes = "allow"
214215
# Disallowed restriction lints
@@ -227,6 +228,10 @@ redundant_clone = "warn"
227228
debug_assert_with_mut_call = "warn"
228229
unused_peekable = "warn"
229230

231+
# Has false positives
232+
# https://github.com/rust-lang/rust-clippy/issues/14275
233+
doc_overindented_list_items = "allow"
234+
230235
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
231236
large_stack_arrays = "allow"
232237

crates/red_knot_project/src/db.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ impl salsa::Database for ProjectDatabase {
159159
}
160160

161161
let event = event();
162-
if matches!(event.kind, salsa::EventKind::WillCheckCancellation { .. }) {
162+
if matches!(event.kind, salsa::EventKind::WillCheckCancellation) {
163163
return;
164164
}
165165

crates/red_knot_project/src/db/changes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ impl ProjectDatabase {
187187
let program = Program::get(self);
188188
if let Err(error) = program.update_from_settings(self, program_settings) {
189189
tracing::error!("Failed to update the program settings, keeping the old program settings: {error}");
190-
};
190+
}
191191

192192
if metadata.root() == project.root(self) {
193193
tracing::debug!("Reloading project after structural change");

crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,19 @@ class Unrelated: ...
9797
def unbounded_unconstrained[T, U](t: list[T], u: list[U]) -> None:
9898
static_assert(is_assignable_to(T, T))
9999
static_assert(is_assignable_to(T, object))
100+
static_assert(not is_assignable_to(T, Super))
100101
static_assert(is_assignable_to(U, U))
101102
static_assert(is_assignable_to(U, object))
103+
static_assert(not is_assignable_to(U, Super))
102104
static_assert(not is_assignable_to(T, U))
103105
static_assert(not is_assignable_to(U, T))
104106

105107
static_assert(is_subtype_of(T, T))
106108
static_assert(is_subtype_of(T, object))
109+
static_assert(not is_subtype_of(T, Super))
107110
static_assert(is_subtype_of(U, U))
108111
static_assert(is_subtype_of(U, object))
112+
static_assert(not is_subtype_of(U, Super))
109113
static_assert(not is_subtype_of(T, U))
110114
static_assert(not is_subtype_of(U, T))
111115
```
@@ -180,8 +184,7 @@ def two_final_bounded[T: FinalClass, U: FinalClass](t: list[T], u: list[U]) -> N
180184

181185
A constrained fully static typevar is assignable to the union of its constraints, but not to any of
182186
the constraints individually. None of the constraints are subtypes of the typevar, though the
183-
intersection of all of its contraints is a subtype of the typevar. (Though that intersection is
184-
likely to be `Never` in practice.)
187+
intersection of all of its constraints is a subtype of the typevar.
185188

186189
```py
187190
from knot_extensions import Intersection
@@ -287,10 +290,11 @@ def unbounded_unconstrained[T](t: list[T]) -> None:
287290
static_assert(not is_single_valued(T))
288291
```
289292

290-
A bounded typevar is not a singleton, since it can still be specialized to `Never`.
293+
A bounded typevar is not a singleton, even if its bound is a singleton, since it can still be
294+
specialized to `Never`.
291295

292296
```py
293-
def bounded[T: int](t: list[T]) -> None:
297+
def bounded[T: None](t: list[T]) -> None:
294298
static_assert(not is_singleton(T))
295299
static_assert(not is_single_valued(T))
296300
```
@@ -448,7 +452,7 @@ since that would allow the typevar to take on values from _multiple_ constraints
448452
`OneOf` connector would not be a “type” according to a strict reading of the typing spec, since it
449453
would not represent a single set of runtime objects; it would instead represent a _set of_ sets of
450454
runtime objects. This is one reason we have not actually added this connector to our data model yet.
451-
Nevertheless, describing constrained typevars this ways helps explain how we simplify intersections
455+
Nevertheless, describing constrained typevars this way helps explain how we simplify intersections
452456
involving them.
453457

454458
This means that when intersecting a constrained typevar with a type `T`, constraints that are
@@ -487,6 +491,7 @@ def remove_constraint[T: (int, str, bool)](t: T) -> None:
487491
reveal_type(x) # revealed: str
488492

489493
def _(x: Intersection[T, Not[str]]) -> None:
494+
# With OneOf this would be OneOf[int, bool]
490495
reveal_type(x) # revealed: T & ~str
491496

492497
def _(x: Intersection[T, Not[bool]]) -> None:

crates/red_knot_python_semantic/resources/mdtest/terminal_statements.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,7 @@ def f(cond: bool) -> str:
654654
reveal_type(x) # revealed: Literal["before"]
655655
return "a"
656656
x = "after-return"
657-
# TODO: no unresolved-reference error
658-
# error: [unresolved-reference]
659-
reveal_type(x) # revealed: Unknown
657+
reveal_type(x) # revealed: Never
660658
else:
661659
x = "else"
662660
return reveal_type(x) # revealed: Literal["else"]

crates/red_knot_python_semantic/resources/mdtest/unreachable.md

-5
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,6 @@ def f():
211211

212212
print("unreachable")
213213

214-
# TODO: we should not emit an error here; we currently do, since there is no control flow path from this
215-
# use of 'x' to any definition of 'x'.
216-
# error: [unresolved-reference]
217214
print(x)
218215
```
219216

@@ -228,8 +225,6 @@ def outer():
228225
x = 1
229226

230227
def inner():
231-
# TODO: we should not emit an error here
232-
# error: [unresolved-reference]
233228
return x # Name `x` used when not defined
234229
while True:
235230
pass

crates/red_knot_python_semantic/src/db.rs

+20-8
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ pub(crate) mod tests {
1919
use std::sync::Arc;
2020

2121
use crate::program::{Program, SearchPathSettings};
22-
use crate::{default_lint_registry, ProgramSettings, PythonPlatform};
22+
use crate::{default_lint_registry, ProgramSettings, PythonPath, PythonPlatform};
2323

2424
use super::Db;
2525
use crate::lint::{LintRegistry, RuleSelection};
2626
use anyhow::Context;
2727
use ruff_db::files::{File, Files};
2828
use ruff_db::system::{
29-
DbWithTestSystem, DbWithWritableSystem as _, System, SystemPathBuf, TestSystem,
29+
DbWithTestSystem, DbWithWritableSystem as _, System, SystemPath, SystemPathBuf, TestSystem,
3030
};
3131
use ruff_db::vendored::VendoredFileSystem;
3232
use ruff_db::{Db as SourceDb, Upcast};
@@ -139,8 +139,8 @@ pub(crate) mod tests {
139139
python_version: PythonVersion,
140140
/// Target Python platform
141141
python_platform: PythonPlatform,
142-
/// Path to a custom typeshed directory
143-
custom_typeshed: Option<SystemPathBuf>,
142+
/// Paths to the directory to use for `site-packages`
143+
site_packages: Vec<SystemPathBuf>,
144144
/// Path and content pairs for files that should be present
145145
files: Vec<(&'a str, &'a str)>,
146146
}
@@ -150,7 +150,7 @@ pub(crate) mod tests {
150150
Self {
151151
python_version: PythonVersion::default(),
152152
python_platform: PythonPlatform::default(),
153-
custom_typeshed: None,
153+
site_packages: vec![],
154154
files: vec![],
155155
}
156156
}
@@ -160,8 +160,20 @@ pub(crate) mod tests {
160160
self
161161
}
162162

163-
pub(crate) fn with_file(mut self, path: &'a str, content: &'a str) -> Self {
164-
self.files.push((path, content));
163+
pub(crate) fn with_file(
164+
mut self,
165+
path: &'a (impl AsRef<SystemPath> + ?Sized),
166+
content: &'a str,
167+
) -> Self {
168+
self.files.push((path.as_ref().as_str(), content));
169+
self
170+
}
171+
172+
pub(crate) fn with_site_packages_search_path(
173+
mut self,
174+
path: &(impl AsRef<SystemPath> + ?Sized),
175+
) -> Self {
176+
self.site_packages.push(path.as_ref().to_path_buf());
165177
self
166178
}
167179

@@ -175,7 +187,7 @@ pub(crate) mod tests {
175187
.context("Failed to write test files")?;
176188

177189
let mut search_paths = SearchPathSettings::new(vec![src_root]);
178-
search_paths.custom_typeshed = self.custom_typeshed;
190+
search_paths.python_path = PythonPath::KnownSitePackages(self.site_packages);
179191

180192
Program::from_settings(
181193
&db,

crates/red_knot_python_semantic/src/module_resolver/resolver.rs

+34-10
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,21 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
9696
FilePath::SystemVirtual(_) => return None,
9797
};
9898

99-
let mut search_paths = search_paths(db);
100-
101-
let module_name = loop {
102-
let candidate = search_paths.next()?;
99+
let module_name = search_paths(db).find_map(|candidate| {
103100
let relative_path = match path {
104101
SystemOrVendoredPathRef::System(path) => candidate.relativize_system_path(path),
105102
SystemOrVendoredPathRef::Vendored(path) => candidate.relativize_vendored_path(path),
106-
};
107-
if let Some(relative_path) = relative_path {
108-
break relative_path.to_module_name()?;
109-
}
110-
};
103+
}?;
104+
relative_path.to_module_name()
105+
})?;
111106

112107
// Resolve the module name to see if Python would resolve the name to the same path.
113108
// If it doesn't, then that means that multiple modules have the same name in different
114109
// root paths, but that the module corresponding to `path` is in a lower priority search path,
115110
// in which case we ignore it.
116111
let module = resolve_module(db, &module_name)?;
117112

118-
if file == module.file() {
113+
if file.path(db) == module.file().path(db) {
119114
Some(module)
120115
} else {
121116
// This path is for a module with the same name but with a different precedence. For example:
@@ -1969,4 +1964,33 @@ not_a_directory
19691964

19701965
Ok(())
19711966
}
1967+
1968+
#[test]
1969+
fn file_to_module_where_one_search_path_is_subdirectory_of_other() {
1970+
let project_directory = SystemPathBuf::from("/project");
1971+
let site_packages = project_directory.join(".venv/lib/python3.13/site-packages");
1972+
let installed_foo_module = site_packages.join("foo/__init__.py");
1973+
1974+
let mut db = TestDb::new();
1975+
db.write_file(&installed_foo_module, "").unwrap();
1976+
1977+
Program::from_settings(
1978+
&db,
1979+
ProgramSettings {
1980+
python_version: PythonVersion::default(),
1981+
python_platform: PythonPlatform::default(),
1982+
search_paths: SearchPathSettings {
1983+
extra_paths: vec![],
1984+
src_roots: vec![project_directory],
1985+
custom_typeshed: None,
1986+
python_path: PythonPath::KnownSitePackages(vec![site_packages.clone()]),
1987+
},
1988+
},
1989+
)
1990+
.unwrap();
1991+
1992+
let foo_module_file = File::new(&db, FilePath::System(installed_foo_module));
1993+
let module = file_to_module(&db, foo_module_file).unwrap();
1994+
assert_eq!(module.search_path(), &site_packages);
1995+
}
19721996
}

crates/red_knot_python_semantic/src/module_resolver/typeshed.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ fn python_version_from_versions_file_string(
322322

323323
#[cfg(test)]
324324
mod tests {
325+
use std::fmt::Write as _;
325326
use std::num::{IntErrorKind, NonZeroU16};
326327
use std::path::Path;
327328

@@ -333,8 +334,7 @@ mod tests {
333334

334335
const TYPESHED_STDLIB_DIR: &str = "stdlib";
335336

336-
#[allow(unsafe_code)]
337-
const ONE: Option<NonZeroU16> = Some(unsafe { NonZeroU16::new_unchecked(1) });
337+
const ONE: Option<NonZeroU16> = Some(NonZeroU16::new(1).unwrap());
338338

339339
impl TypeshedVersions {
340340
#[must_use]
@@ -571,7 +571,7 @@ foo: 3.8- # trailing comment
571571

572572
let mut massive_versions_file = String::new();
573573
for i in 0..too_many {
574-
massive_versions_file.push_str(&format!("x{i}: 3.8-\n"));
574+
let _ = writeln!(&mut massive_versions_file, "x{i}: 3.8-");
575575
}
576576

577577
assert_eq!(

crates/red_knot_python_semantic/src/semantic_index/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,7 @@ where
14481448
self.visit_expr(subject);
14491449
if cases.is_empty() {
14501450
return;
1451-
};
1451+
}
14521452

14531453
let after_subject = self.flow_snapshot();
14541454
let mut vis_constraints = vec![];

crates/red_knot_python_semantic/src/suppression.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub(crate) fn check_suppressions(db: &dyn Db, file: File, diagnostics: &mut Type
145145
fn check_unknown_rule(context: &mut CheckSuppressionsContext) {
146146
if context.is_lint_disabled(&UNKNOWN_RULE) {
147147
return;
148-
};
148+
}
149149

150150
for unknown in &context.suppressions.unknown {
151151
match &unknown.reason {
@@ -174,7 +174,7 @@ fn check_unknown_rule(context: &mut CheckSuppressionsContext) {
174174
format_args!("Unknown rule `{prefixed}`. Did you mean `{suggestion}`?"),
175175
);
176176
}
177-
};
177+
}
178178
}
179179
}
180180

@@ -267,7 +267,7 @@ fn check_unused_suppressions(context: &mut CheckSuppressionsContext) {
267267
suppression.range,
268268
format_args!("Unused `{kind}` without a code", kind = suppression.kind),
269269
),
270-
};
270+
}
271271
}
272272
}
273273

0 commit comments

Comments
 (0)