Compare commits

..

9 Commits

Author SHA1 Message Date
dylwil3
8bda1837d9 update tests and snapshots 2025-06-06 09:28:47 -05:00
dylwil3
e667d53828 update docs 2025-06-06 09:28:47 -05:00
dylwil3
a071114638 remove preview check for rule 2025-06-06 09:28:47 -05:00
Brent Westbrook
86ae9fbc08 Ruff 0.12
Summary
--

Release branch for Ruff 0.12.0

TODOs
--

- [ ] Drop empty first commit
- [ ] Merge with rebase-merge (**don't squash merge!!!!**)
2025-06-06 09:28:47 -05:00
Brent Westbrook
11966beeec Ruff 0.12
Summary
--

Release branch for Ruff 0.12.0

TODOs
--

- [ ] Drop empty first commit
- [ ] Merge with rebase-merge (**don't squash merge!!!!**)
2025-06-06 10:14:43 -04:00
Alex Waygood
1274521f9f [ty] Track the origin of the environment.python setting for better error messages (#18483) 2025-06-06 13:36:41 +01:00
shimies
8d24760643 Fix doc for Neovim setting examples (#18491)
## Summary
This PR fixes an error in the example Neovim configuration on [this
documentation
page](https://docs.astral.sh/ruff/editors/settings/#configuration).
The `configuration` block should be nested under `settings`, consistent
with other properties and as outlined
[here](https://docs.astral.sh/ruff/editors/setup/#neovim).

I encountered this issue when copying the example to configure ruff
integration in my neovim - the config didn’t work until I corrected the
nesting.

## Test Plan
- [x] Confirmed that the corrected configuration works in a real Neovim
+ Ruff setup
- [x] Verified that the updated configuration renders correctly in
MkDocs
<img width="382" alt="image"
src="https://github.com/user-attachments/assets/0722fb35-8ffa-4b10-90ba-c6e8417e40bf"
/>
2025-06-06 15:19:16 +05:30
Carl Meyer
db8db536f8 [ty] clarify requirements for scope_id argument to in_type_expression (#18488) 2025-06-05 22:46:26 -07:00
Carl Meyer
cb8246bc5f [ty] remove unnecessary Either (#18489)
Just a quick review-comment follow-up.
2025-06-05 18:39:22 -07:00
19 changed files with 369 additions and 137 deletions

2
Cargo.lock generated
View File

@@ -3965,6 +3965,7 @@ dependencies = [
"anyhow",
"bitflags 2.9.1",
"camino",
"colored 3.0.0",
"compact_str",
"countme",
"dir-test",
@@ -3977,6 +3978,7 @@ dependencies = [
"ordermap",
"quickcheck",
"quickcheck_macros",
"ruff_annotate_snippets",
"ruff_db",
"ruff_index",
"ruff_macros",

View File

@@ -566,7 +566,7 @@ fn venv() -> Result<()> {
----- stderr -----
ruff failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` does not point to a Python executable or a directory on disk
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
");
});

View File

@@ -65,7 +65,7 @@ use crate::docstrings::extraction::ExtractionTarget;
use crate::importer::{ImportRequest, Importer, ResolutionError};
use crate::noqa::NoqaMapping;
use crate::package::PackageRoot;
use crate::preview::{is_semantic_errors_enabled, is_undefined_export_in_dunder_init_enabled};
use crate::preview::is_semantic_errors_enabled;
use crate::registry::{AsRule, Rule};
use crate::rules::pyflakes::rules::{
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
@@ -2900,17 +2900,13 @@ impl<'a> Checker<'a> {
}
} else {
if self.enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings)
|| !self.path.ends_with("__init__.py")
{
self.report_diagnostic(
pyflakes::rules::UndefinedExport {
name: name.to_string(),
},
range,
)
.set_parent(definition.start());
}
self.report_diagnostic(
pyflakes::rules::UndefinedExport {
name: name.to_string(),
},
range,
)
.set_parent(definition.start());
}
}
}

View File

@@ -111,11 +111,6 @@ pub(crate) const fn is_support_slices_in_literal_concatenation_enabled(
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11370
pub(crate) const fn is_undefined_export_in_dunder_init_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/14236
pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()

View File

@@ -233,7 +233,6 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))]
#[test_case(Rule::UndefinedExport, Path::new("__init__.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -14,7 +14,7 @@ use crate::Violation;
/// Including an undefined name in `__all__` is likely to raise `NameError` at
/// runtime, when the module is imported.
///
/// In [preview], this rule will flag undefined names in `__init__.py` file,
/// This rule will flag undefined names in `__init__.py` file
/// even if those names implicitly refer to other modules in the package. Users
/// that rely on implicit exports should disable this rule in `__init__.py`
/// files via [`lint.per-file-ignores`].
@@ -37,8 +37,6 @@ use crate::Violation;
///
/// ## References
/// - [Python documentation: `__all__`](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[derive(ViolationMetadata)]
pub(crate) struct UndefinedExport {
pub name: String,

View File

@@ -9,3 +9,27 @@ __init__.py:1:8: F401 `os` imported but unused
3 | print(__path__)
|
= help: Remove unused import: `os`
__init__.py:5:12: F822 Undefined name `a` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:17: F822 Undefined name `b` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:22: F822 Undefined name `c` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|

View File

@@ -1,26 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
__init__.py:5:12: F822 Undefined name `a` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:17: F822 Undefined name `b` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:22: F822 Undefined name `c` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|

View File

@@ -7,6 +7,7 @@ use ruff_python_semantic::analyze::function_type::is_stub;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::rules::fastapi::rules::is_fastapi_route;
/// ## What it does

View File

@@ -974,7 +974,7 @@ fn python_cli_argument_virtual_environment() -> anyhow::Result<()> {
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
ty failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `<temp_dir>/my-venv/foo/some_other_file.txt` does not point to a Python executable or a directory on disk
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `<temp_dir>/my-venv/foo/some_other_file.txt`: does not point to a Python executable or a directory on disk
");
// And so are paths that do not exist on disk
@@ -987,7 +987,7 @@ fn python_cli_argument_virtual_environment() -> anyhow::Result<()> {
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
ty failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `<temp_dir>/not-a-directory-or-executable` does not point to a Python executable or a directory on disk
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `<temp_dir>/not-a-directory-or-executable`: does not point to a Python executable or a directory on disk
");
Ok(())
@@ -1045,6 +1045,14 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> {
(
"pyproject.toml",
r#"
[project]
name = "test"
version = "0.1.0"
description = "Some description"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
[tool.ty.environment]
python = "not-a-directory-or-executable"
"#,
@@ -1052,8 +1060,7 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> {
("test.py", ""),
])?;
// TODO: this error message should say "invalid `python` configuration setting" rather than "invalid `--python` argument"
assert_cmd_snapshot!(case.command(), @r"
assert_cmd_snapshot!(case.command(), @r#"
success: false
exit_code: 2
----- stdout -----
@@ -1062,8 +1069,92 @@ fn config_file_broken_python_setting() -> anyhow::Result<()> {
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
ty failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `<temp_dir>/not-a-directory-or-executable` does not point to a Python executable or a directory on disk
");
Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting
--> Invalid setting in configuration file `<temp_dir>/pyproject.toml`
|
9 |
10 | [tool.ty.environment]
11 | python = "not-a-directory-or-executable"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not point to a Python executable or a directory on disk
|
"#);
Ok(())
}
#[test]
fn config_file_python_setting_directory_with_no_site_packages() -> anyhow::Result<()> {
let case = TestCase::with_files([
(
"pyproject.toml",
r#"
[tool.ty.environment]
python = "directory-but-no-site-packages"
"#,
),
("directory-but-no-site-packages/lib/foo.py", ""),
("test.py", ""),
])?;
assert_cmd_snapshot!(case.command(), @r#"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
ty failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `environment.python` setting
--> Invalid setting in configuration file `<temp_dir>/pyproject.toml`
|
1 |
2 | [tool.ty.environment]
3 | python = "directory-but-no-site-packages"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Could not find a `site-packages` directory for this Python installation/executable
|
"#);
Ok(())
}
// This error message is never emitted on Windows, because Windows installations have simpler layouts
#[cfg(not(windows))]
#[test]
fn unix_system_installation_with_no_lib_directory() -> anyhow::Result<()> {
let case = TestCase::with_files([
(
"pyproject.toml",
r#"
[tool.ty.environment]
python = "directory-but-no-site-packages"
"#,
),
("directory-but-no-site-packages/foo.py", ""),
("test.py", ""),
])?;
assert_cmd_snapshot!(case.command(), @r#"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
ty failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Failed to iterate over the contents of the `lib` directory of the Python installation
--> Invalid setting in configuration file `<temp_dir>/pyproject.toml`
|
1 |
2 | [tool.ty.environment]
3 | python = "directory-but-no-site-packages"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
"#);
Ok(())
}

View File

@@ -182,10 +182,14 @@ impl Options {
custom_typeshed: typeshed.map(|path| path.absolute(project_root, system)),
python_path: python
.map(|python_path| {
PythonPath::sys_prefix(
python_path.absolute(project_root, system),
SysPrefixPathOrigin::PythonCliFlag,
)
let origin = match python_path.source() {
ValueSource::Cli => SysPrefixPathOrigin::PythonCliFlag,
ValueSource::File(path) => SysPrefixPathOrigin::ConfigFileSetting(
path.clone(),
python_path.range(),
),
};
PythonPath::sys_prefix(python_path.absolute(project_root, system), origin)
})
.or_else(|| {
std::env::var("VIRTUAL_ENV").ok().map(|virtual_env| {

View File

@@ -32,6 +32,10 @@ impl ValueSource {
ValueSource::Cli => None,
}
}
pub const fn is_cli(&self) -> bool {
matches!(self, ValueSource::Cli)
}
}
thread_local! {
@@ -324,6 +328,14 @@ impl RelativePathBuf {
&self.0
}
pub fn source(&self) -> &ValueSource {
self.0.source()
}
pub fn range(&self) -> Option<TextRange> {
self.0.range()
}
/// Returns the owned relative path.
pub fn into_path_buf(self) -> SystemPathBuf {
self.0.into_inner()

View File

@@ -12,6 +12,7 @@ license = { workspace = true }
[dependencies]
ruff_db = { workspace = true }
ruff_annotate_snippets = { workspace = true }
ruff_index = { workspace = true, features = ["salsa"] }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["salsa"] }
@@ -25,6 +26,7 @@ ruff_python_trivia = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
compact_str = { workspace = true }
countme = { workspace = true }
drop_bomb = { workspace = true }

View File

@@ -235,7 +235,7 @@ impl SearchPaths {
let (site_packages_paths, python_version) = match python_path {
PythonPath::IntoSysPrefix(path, origin) => {
if *origin == SysPrefixPathOrigin::LocalVenv {
if origin == &SysPrefixPathOrigin::LocalVenv {
tracing::debug!("Discovering virtual environment in `{path}`");
let virtual_env_directory = path.join(".venv");
@@ -260,7 +260,7 @@ impl SearchPaths {
})
} else {
tracing::debug!("Resolving {origin}: {path}");
PythonEnvironment::new(path, *origin, system)?.into_settings(system)?
PythonEnvironment::new(path, origin.clone(), system)?.into_settings(system)?
}
}

View File

@@ -228,7 +228,6 @@ impl Default for PythonVersionWithSource {
/// Configures the search paths for module resolution.
#[derive(Eq, PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SearchPathSettings {
/// List of user-provided paths that should take first priority in the module resolution.
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
@@ -260,7 +259,6 @@ impl SearchPathSettings {
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PythonPath {
/// A path that either represents the value of [`sys.prefix`] at runtime in Python
/// for a given Python executable, or which represents a path relative to `sys.prefix`

View File

@@ -8,16 +8,17 @@
//! reasonably ask us to type-check code assuming that the code runs
//! on Linux.)
use std::fmt::Display;
use std::io;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::{fmt, sync::Arc};
use indexmap::IndexSet;
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_python_ast::PythonVersion;
use ruff_python_trivia::Cursor;
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextLen, TextRange};
use crate::{PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource};
@@ -102,7 +103,7 @@ impl PythonEnvironment {
Ok(venv) => Ok(Self::Virtual(venv)),
// If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment
Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, _))
if !origin.must_be_virtual_env() =>
if !path.origin.must_be_virtual_env() =>
{
Ok(Self::System(SystemEnvironment::new(path)))
}
@@ -530,33 +531,21 @@ impl SystemEnvironment {
}
/// Enumeration of ways in which `site-packages` discovery can fail.
#[derive(Debug, thiserror::Error)]
#[derive(Debug)]
pub(crate) enum SitePackagesDiscoveryError {
/// `site-packages` discovery failed because the provided path couldn't be canonicalized.
#[error("Invalid {1}: `{0}` could not be canonicalized")]
CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error),
CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, io::Error),
/// `site-packages` discovery failed because the provided path doesn't appear to point to
/// a Python executable or a `sys.prefix` directory.
#[error(
"Invalid {1}: `{0}` does not point to a {thing}",
thing = if .1.must_point_directly_to_sys_prefix() {
"directory on disk"
} else {
"Python executable or a directory on disk"
}
)]
PathNotExecutableOrDirectory(SystemPathBuf, SysPrefixPathOrigin),
PathNotExecutableOrDirectory(SystemPathBuf, SysPrefixPathOrigin, Option<io::Error>),
/// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that
/// the provided path should point to the `sys.prefix` of a virtual environment,
/// but there was no file at `<sys.prefix>/pyvenv.cfg`.
#[error("{} points to a broken venv with no pyvenv.cfg file", .0.origin)]
NoPyvenvCfgFile(SysPrefixPath, #[source] io::Error),
NoPyvenvCfgFile(SysPrefixPath, io::Error),
/// `site-packages` discovery failed because the `pyvenv.cfg` file could not be parsed.
#[error("Failed to parse the pyvenv.cfg file at {0} because {1}")]
PyvenvCfgParseError(SystemPathBuf, PyvenvCfgParseErrorKind),
/// `site-packages` discovery failed because we're on a Unix system,
@@ -564,17 +553,149 @@ pub(crate) enum SitePackagesDiscoveryError {
/// would be relative to the `sys.prefix` path, and we tried to fallback to iterating
/// through the `<sys.prefix>/lib` directory looking for a `site-packages` directory,
/// but we came across some I/O error while trying to do so.
#[error(
"Failed to iterate over the contents of the `lib` directory of the Python installation at {1}"
)]
CouldNotReadLibDirectory(#[source] io::Error, SysPrefixPath),
CouldNotReadLibDirectory(SysPrefixPath, io::Error),
/// We looked everywhere we could think of for the `site-packages` directory,
/// but none could be found despite our best endeavours.
#[error("Could not find the `site-packages` directory for the Python installation at {0}")]
NoSitePackagesDirFound(SysPrefixPath),
}
impl std::error::Error for SitePackagesDiscoveryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CanonicalizationError(_, _, io_err) => Some(io_err),
Self::PathNotExecutableOrDirectory(_, _, io_err) => {
io_err.as_ref().map(|e| e as &dyn std::error::Error)
}
Self::NoPyvenvCfgFile(_, io_err) => Some(io_err),
Self::PyvenvCfgParseError(_, _) => None,
Self::CouldNotReadLibDirectory(_, io_err) => Some(io_err),
Self::NoSitePackagesDirFound(_) => None,
}
}
}
impl std::fmt::Display for SitePackagesDiscoveryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CanonicalizationError(given_path, origin, _) => {
display_error(f, origin, given_path, "Failed to canonicalize", None)
}
Self::PathNotExecutableOrDirectory(path, origin, _) => {
let thing = if origin.must_point_directly_to_sys_prefix() {
"directory on disk"
} else {
"Python executable or a directory on disk"
};
display_error(
f,
origin,
path,
&format!("Invalid {origin}"),
Some(&format!("does not point to a {thing}")),
)
}
Self::NoPyvenvCfgFile(SysPrefixPath { inner, origin }, _) => display_error(
f,
origin,
inner,
&format!("Invalid {origin}"),
Some("points to a broken venv with no pyvenv.cfg file"),
),
Self::PyvenvCfgParseError(path, kind) => {
write!(
f,
"Failed to parse the `pyvenv.cfg` file at `{path}` because {kind}"
)
}
Self::CouldNotReadLibDirectory(SysPrefixPath { inner, origin }, _) => display_error(
f,
origin,
inner,
"Failed to iterate over the contents of the `lib` directory of the Python installation",
None,
),
Self::NoSitePackagesDirFound(SysPrefixPath { inner, origin }) => display_error(
f,
origin,
inner,
&format!("Invalid {origin}"),
Some(
"Could not find a `site-packages` directory for this Python installation/executable",
),
),
}
}
}
fn display_error(
f: &mut std::fmt::Formatter<'_>,
sys_prefix_origin: &SysPrefixPathOrigin,
given_path: &SystemPath,
primary_message: &str,
secondary_message: Option<&str>,
) -> std::fmt::Result {
let fallback: &mut dyn FnMut() -> std::fmt::Result = &mut || {
f.write_str(primary_message)?;
write!(f, " `{given_path}`")?;
if let Some(secondary_message) = secondary_message {
f.write_str(": ")?;
f.write_str(secondary_message)?;
}
Ok(())
};
let SysPrefixPathOrigin::ConfigFileSetting(config_file_path, Some(setting_range)) =
sys_prefix_origin
else {
return fallback();
};
let Ok(config_file_source) = std::fs::read_to_string((**config_file_path).as_ref()) else {
return fallback();
};
let index = LineIndex::from_source_text(&config_file_source);
let source = SourceCode::new(&config_file_source, &index);
let primary_message = format!(
"{primary_message}
--> Invalid setting in configuration file `{config_file_path}`"
);
let start_index = source.line_index(setting_range.start()).saturating_sub(2);
let end_index = source
.line_index(setting_range.end())
.saturating_add(2)
.min(OneIndexed::from_zero_indexed(source.line_count()));
let start_offset = source.line_start(start_index);
let end_offset = source.line_end(end_index);
let mut annotation = Level::Error.span((setting_range - start_offset).into());
if let Some(secondary_message) = secondary_message {
annotation = annotation.label(secondary_message);
}
let snippet = Snippet::source(&config_file_source[TextRange::new(start_offset, end_offset)])
.annotation(annotation)
.line_start(start_index.get())
.fold(false);
let message = Level::None.title(&primary_message).snippet(snippet);
let renderer = if colored::control::SHOULD_COLORIZE.should_colorize() {
Renderer::styled()
} else {
Renderer::plain()
};
let renderer = renderer.cut_indicator("");
writeln!(f, "{}", renderer.render(message))
}
/// The various ways in which parsing a `pyvenv.cfg` file could fail
#[derive(Debug)]
pub(crate) enum PyvenvCfgParseErrorKind {
@@ -615,7 +736,10 @@ fn site_packages_directory_from_sys_prefix(
implementation: PythonImplementation,
system: &dyn System,
) -> SitePackagesDiscoveryResult<SystemPathBuf> {
tracing::debug!("Searching for site-packages directory in {sys_prefix_path}");
tracing::debug!(
"Searching for site-packages directory in sys.prefix {}",
sys_prefix_path.inner
);
if cfg!(target_os = "windows") {
let site_packages = sys_prefix_path.join(r"Lib\site-packages");
@@ -684,7 +808,7 @@ fn site_packages_directory_from_sys_prefix(
for entry_result in system
.read_directory(&sys_prefix_path.join("lib"))
.map_err(|io_err| {
SitePackagesDiscoveryError::CouldNotReadLibDirectory(io_err, sys_prefix_path.to_owned())
SitePackagesDiscoveryError::CouldNotReadLibDirectory(sys_prefix_path.to_owned(), io_err)
})?
{
let Ok(entry) = entry_result else {
@@ -743,14 +867,15 @@ impl SysPrefixPath {
// It's important to resolve symlinks here rather than simply making the path absolute,
// since system Python installations often only put symlinks in the "expected"
// locations for `home` and `site-packages`
let canonicalized = system
.canonicalize_path(unvalidated_path)
.map_err(|io_err| {
let canonicalized = match system.canonicalize_path(unvalidated_path) {
Ok(path) => path,
Err(io_err) => {
let unvalidated_path = unvalidated_path.to_path_buf();
if io_err.kind() == io::ErrorKind::NotFound {
let err = if io_err.kind() == io::ErrorKind::NotFound {
SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
unvalidated_path,
origin,
Some(io_err),
)
} else {
SitePackagesDiscoveryError::CanonicalizationError(
@@ -758,22 +883,24 @@ impl SysPrefixPath {
origin,
io_err,
)
}
})?;
};
return Err(err);
}
};
if origin.must_point_directly_to_sys_prefix() {
return system
.is_directory(&canonicalized)
.then_some(Self {
return if system.is_directory(&canonicalized) {
Ok(Self {
inner: canonicalized,
origin,
})
.ok_or_else(|| {
SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
unvalidated_path.to_path_buf(),
origin,
)
});
} else {
Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
unvalidated_path.to_path_buf(),
origin,
None,
))
};
}
let sys_prefix = if system.is_file(&canonicalized)
@@ -800,18 +927,21 @@ impl SysPrefixPath {
// regardless of whether it's a virtual environment or a system installation.
canonicalized.ancestors().nth(2)
};
sys_prefix.map(SystemPath::to_path_buf).ok_or_else(|| {
SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
let Some(sys_prefix) = sys_prefix else {
return Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
unvalidated_path.to_path_buf(),
origin,
)
})?
None,
));
};
sys_prefix.to_path_buf()
} else if system.is_directory(&canonicalized) {
canonicalized
} else {
return Err(SitePackagesDiscoveryError::PathNotExecutableOrDirectory(
unvalidated_path.to_path_buf(),
origin,
None,
));
};
@@ -847,16 +977,11 @@ impl Deref for SysPrefixPath {
}
}
impl fmt::Display for SysPrefixPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "`sys.prefix` path `{}`", self.inner)
}
}
/// Enumeration of sources a `sys.prefix` path can come from.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SysPrefixPathOrigin {
/// The `sys.prefix` path came from a configuration file setting: `pyproject.toml` or `ty.toml`
ConfigFileSetting(Arc<SystemPathBuf>, Option<TextRange>),
/// The `sys.prefix` path came from a `--python` CLI flag
PythonCliFlag,
/// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable
@@ -875,10 +1000,13 @@ pub enum SysPrefixPathOrigin {
impl SysPrefixPathOrigin {
/// Whether the given `sys.prefix` path must be a virtual environment (rather than a system
/// Python environment).
pub(crate) const fn must_be_virtual_env(self) -> bool {
pub(crate) const fn must_be_virtual_env(&self) -> bool {
match self {
Self::LocalVenv | Self::VirtualEnvVar => true,
Self::PythonCliFlag | Self::DerivedFromPyvenvCfg | Self::CondaPrefixVar => false,
Self::ConfigFileSetting(..)
| Self::PythonCliFlag
| Self::DerivedFromPyvenvCfg
| Self::CondaPrefixVar => false,
}
}
@@ -886,9 +1014,9 @@ impl SysPrefixPathOrigin {
///
/// Some variants can point either directly to `sys.prefix` or to a Python executable inside
/// the `sys.prefix` directory, e.g. the `--python` CLI flag.
pub(crate) const fn must_point_directly_to_sys_prefix(self) -> bool {
pub(crate) const fn must_point_directly_to_sys_prefix(&self) -> bool {
match self {
Self::PythonCliFlag => false,
Self::PythonCliFlag | Self::ConfigFileSetting(..) => false,
Self::VirtualEnvVar
| Self::CondaPrefixVar
| Self::DerivedFromPyvenvCfg
@@ -897,10 +1025,11 @@ impl SysPrefixPathOrigin {
}
}
impl Display for SysPrefixPathOrigin {
impl std::fmt::Display for SysPrefixPathOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::PythonCliFlag => f.write_str("`--python` argument"),
Self::ConfigFileSetting(_, _) => f.write_str("`environment.python` setting"),
Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"),
Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"),
Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"),
@@ -1107,7 +1236,7 @@ mod tests {
#[track_caller]
fn run(self) -> PythonEnvironment {
let env_path = self.build();
let env = PythonEnvironment::new(env_path.clone(), self.origin, &self.system)
let env = PythonEnvironment::new(env_path.clone(), self.origin.clone(), &self.system)
.expect("Expected environment construction to succeed");
let expect_virtual_env = self.virtual_env.is_some();
@@ -1144,7 +1273,7 @@ mod tests {
venv.root_path,
SysPrefixPath {
inner: self.system.canonicalize_path(expected_env_path).unwrap(),
origin: self.origin,
origin: self.origin.clone(),
}
);
assert_eq!(
@@ -1216,7 +1345,7 @@ mod tests {
env.root_path,
SysPrefixPath {
inner: self.system.canonicalize_path(expected_env_path).unwrap(),
origin: self.origin,
origin: self.origin.clone(),
}
);

View File

@@ -4967,7 +4967,10 @@ impl<'db> Type<'db> {
/// `Type::ClassLiteral(builtins.int)`, that is, it is the `int` class itself. As a type
/// expression, it names the type `Type::NominalInstance(builtins.int)`, that is, all objects whose
/// `__class__` is `int`.
pub fn in_type_expression(
///
/// The `scope_id` argument must always be a scope from the file we are currently inferring, so
/// as to avoid cross-module AST dependency.
pub(crate) fn in_type_expression(
&self,
db: &'db dyn Db,
scope_id: ScopeId<'db>,

View File

@@ -9021,9 +9021,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
) -> Type<'db> {
let arguments = &*subscript_node.slice;
let (args, args_number) = if let ast::Expr::Tuple(t) = arguments {
(Either::Left(t), t.len())
(t.iter(), t.len())
} else {
(Either::Right([arguments]), 1)
(std::slice::from_ref(arguments).iter(), 1)
};
if args_number != expected_arg_count {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) {

View File

@@ -66,7 +66,9 @@ _Using configuration file path:_
```lua
require('lspconfig').ruff.setup {
init_options = {
configuration = "~/path/to/ruff.toml"
settings = {
configuration = "~/path/to/ruff.toml"
}
}
}
```
@@ -117,20 +119,22 @@ _Using inline configuration:_
```lua
require('lspconfig').ruff.setup {
init_options = {
configuration = {
lint = {
unfixable = {"F401"},
["extend-select"] = {"TID251"},
["flake8-tidy-imports"] = {
["banned-api"] = {
["typing.TypedDict"] = {
msg = "Use `typing_extensions.TypedDict` instead"
settings = {
configuration = {
lint = {
unfixable = {"F401"},
["extend-select"] = {"TID251"},
["flake8-tidy-imports"] = {
["banned-api"] = {
["typing.TypedDict"] = {
msg = "Use `typing_extensions.TypedDict` instead"
}
}
}
},
format = {
["quote-style"] = "single"
}
},
format = {
["quote-style"] = "single"
}
}
}