Skip to content

Commit

Permalink
Create Unknown rule diagnostics with a source range (#15648)
Browse files Browse the repository at this point in the history
MichaReiser authored Jan 23, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 1e790d3 commit 05ea77b
Showing 10 changed files with 341 additions and 115 deletions.
6 changes: 4 additions & 2 deletions crates/red_knot/src/main.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use python_version::PythonVersion;
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::value::RelativePathBuf;
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
use red_knot_project::watch;
use red_knot_project::watch::ProjectWatcher;
use red_knot_project::{ProjectDatabase, ProjectMetadata};
@@ -73,7 +73,9 @@ impl Args {
fn to_options(&self) -> Options {
Options {
environment: Some(EnvironmentOptions {
python_version: self.python_version.map(Into::into),
python_version: self
.python_version
.map(|version| RangedValue::cli(version.into())),
venv_path: self.venv_path.as_ref().map(RelativePathBuf::cli),
typeshed: self.typeshed.as_ref().map(RelativePathBuf::cli),
extra_paths: self.extra_search_path.as_ref().map(|extra_search_paths| {
2 changes: 1 addition & 1 deletion crates/red_knot/tests/cli.rs
Original file line number Diff line number Diff line change
@@ -287,7 +287,7 @@ division-by-zer = "warn" # incorrect rule name
success: false
exit_code: 1
----- stdout -----
warning[unknown-rule] Unknown lint rule `division-by-zer`
warning[unknown-rule] <temp_dir>/pyproject.toml:3:1 Unknown lint rule `division-by-zer`
----- stderr -----
");
16 changes: 10 additions & 6 deletions crates/red_knot/tests/file_watching.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use std::time::{Duration, Instant};
use anyhow::{anyhow, Context};
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::pyproject::{PyProject, Tool};
use red_knot_project::metadata::value::RelativePathBuf;
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
use red_knot_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher};
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
use red_knot_python_semantic::{resolve_module, ModuleName, PythonPlatform, PythonVersion};
@@ -897,8 +897,10 @@ print(sys.last_exc, os.getegid())
|_root_path, _project_path| {
Some(Options {
environment: Some(EnvironmentOptions {
python_version: Some(PythonVersion::PY311),
python_platform: Some(PythonPlatform::Identifier("win32".to_string())),
python_version: Some(RangedValue::cli(PythonVersion::PY311)),
python_platform: Some(RangedValue::cli(PythonPlatform::Identifier(
"win32".to_string(),
))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -921,8 +923,10 @@ print(sys.last_exc, os.getegid())
// Change the python version
case.update_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(PythonVersion::PY312),
python_platform: Some(PythonPlatform::Identifier("linux".to_string())),
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
python_platform: Some(RangedValue::cli(PythonPlatform::Identifier(
"linux".to_string(),
))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -1382,7 +1386,7 @@ mod unix {
extra_paths: Some(vec![RelativePathBuf::cli(
".venv/lib/python3.12/site-packages",
)]),
python_version: Some(PythonVersion::PY312),
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
..EnvironmentOptions::default()
}),
..Options::default()
2 changes: 1 addition & 1 deletion crates/red_knot_project/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ impl ProjectMetadata {
) -> Self {
let name = project
.and_then(|project| project.name.as_ref())
.map(|name| Name::new(&**name))
.map(|name| Name::new(&***name))
.unwrap_or_else(|| Name::new(root.file_name().unwrap_or("root")));

// TODO(https://github.com/astral-sh/ruff/issues/15491): Respect requires-python
84 changes: 60 additions & 24 deletions crates/red_knot_project/src/metadata/options.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::metadata::value::{RelativePathBuf, ValueSource, ValueSourceGuard};
use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSourceGuard};
use crate::Db;
use red_knot_python_semantic::lint::{GetLintError, Level, RuleSelection};
use red_knot_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
use red_knot_python_semantic::{
ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings, SitePackages,
};
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::system::{System, SystemPath};
use ruff_macros::Combine;
use ruff_text_size::TextRange;
@@ -44,7 +44,12 @@ impl Options {
let (python_version, python_platform) = self
.environment
.as_ref()
.map(|env| (env.python_version, env.python_platform.as_ref()))
.map(|env| {
(
env.python_version.as_deref().copied(),
env.python_platform.as_deref(),
)
})
.unwrap_or_default();

ProgramSettings {
@@ -116,27 +121,42 @@ impl Options {
.flat_map(|rules| rules.inner.iter());

for (rule_name, level) in rules {
let source = rule_name.source();
match registry.get(rule_name) {
Ok(lint) => {
if let Ok(severity) = Severity::try_from(*level) {
selection.enable(lint, severity);
let lint_source = match source {
ValueSource::File(_) => LintSource::File,
ValueSource::Cli => LintSource::Cli,
};
if let Ok(severity) = Severity::try_from(**level) {
selection.enable(lint, severity, lint_source);
} else {
selection.disable(lint);
}
}
Err(GetLintError::Unknown(_)) => {
diagnostics.push(OptionDiagnostic::new(
DiagnosticId::UnknownRule,
format!("Unknown lint rule `{rule_name}`"),
Severity::Warning,
));
}
Err(GetLintError::Removed(_)) => {
diagnostics.push(OptionDiagnostic::new(
DiagnosticId::UnknownRule,
format!("The lint rule `{rule_name}` has been removed and is no longer supported"),
Severity::Warning,
));
Err(error) => {
// `system_path_to_file` can return `Err` if the file was deleted since the configuration
// was read. This should be rare and it should be okay to default to not showing a configuration
// file in that case.
let file = source
.file()
.and_then(|path| system_path_to_file(db.upcast(), path).ok());

// TODO: Add a note if the value was configured on the CLI
let diagnostic = match error {
GetLintError::Unknown(_) => OptionDiagnostic::new(
DiagnosticId::UnknownRule,
format!("Unknown lint rule `{rule_name}`"),
Severity::Warning,
),
GetLintError::Removed(_) => OptionDiagnostic::new(
DiagnosticId::UnknownRule,
format!("Unknown lint rule `{rule_name}`"),
Severity::Warning,
),
};

diagnostics.push(diagnostic.with_file(file).with_range(rule_name.range()));
}
}
}
@@ -149,10 +169,10 @@ impl Options {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct EnvironmentOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub python_version: Option<PythonVersion>,
pub python_version: Option<RangedValue<PythonVersion>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub python_platform: Option<PythonPlatform>,
pub python_platform: Option<RangedValue<PythonPlatform>>,

/// 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,
@@ -183,7 +203,7 @@ pub struct SrcOptions {
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", transparent)]
pub struct Rules {
inner: FxHashMap<String, Level>,
inner: FxHashMap<RangedValue<String>, RangedValue<Level>>,
}

#[derive(Error, Debug)]
@@ -197,6 +217,8 @@ pub struct OptionDiagnostic {
id: DiagnosticId,
message: String,
severity: Severity,
file: Option<File>,
range: Option<TextRange>,
}

impl OptionDiagnostic {
@@ -205,8 +227,22 @@ impl OptionDiagnostic {
id,
message,
severity,
file: None,
range: None,
}
}

#[must_use]
fn with_file(mut self, file: Option<File>) -> Self {
self.file = file;
self
}

#[must_use]
fn with_range(mut self, range: Option<TextRange>) -> Self {
self.range = range;
self
}
}

impl Diagnostic for OptionDiagnostic {
@@ -219,11 +255,11 @@ impl Diagnostic for OptionDiagnostic {
}

fn file(&self) -> Option<File> {
None
self.file
}

fn range(&self) -> Option<TextRange> {
None
self.range
}

fn severity(&self) -> Severity {
8 changes: 4 additions & 4 deletions crates/red_knot_project/src/metadata/pyproject.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use std::ops::Deref;
use thiserror::Error;

use crate::metadata::options::Options;
use crate::metadata::value::{ValueSource, ValueSourceGuard};
use crate::metadata::value::{RangedValue, ValueSource, ValueSourceGuard};

/// A `pyproject.toml` as specified in PEP 517.
#[derive(Deserialize, Serialize, Debug, Default, Clone)]
@@ -48,11 +48,11 @@ pub struct Project {
///
/// Note: Intentionally option to be more permissive during deserialization.
/// `PackageMetadata::from_pyproject` reports missing names.
pub name: Option<PackageName>,
pub name: Option<RangedValue<PackageName>>,
/// The version of the project
pub version: Option<Version>,
pub version: Option<RangedValue<Version>>,
/// The Python versions this project is compatible with.
pub requires_python: Option<VersionSpecifiers>,
pub requires_python: Option<RangedValue<VersionSpecifiers>>,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
300 changes: 237 additions & 63 deletions crates/red_knot_project/src/metadata/value.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use crate::combine::Combine;
use crate::Db;
use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_text_size::{TextRange, TextSize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cell::RefCell;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use crate::combine::Combine;
use crate::Db;
use toml::Spanned;

#[derive(Clone, Debug)]
pub enum ValueSource {
@@ -19,6 +23,15 @@ pub enum ValueSource {
Cli,
}

impl ValueSource {
pub fn file(&self) -> Option<&SystemPath> {
match self {
ValueSource::File(path) => Some(&**path),
ValueSource::Cli => None,
}
}
}

thread_local! {
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
/// but we want to associate each deserialized [`RelativePath`] with the source from
@@ -49,115 +62,276 @@ impl Drop for ValueSourceGuard {
}
}

/// A possibly relative path in a configuration file.
/// A value that "remembers" where it comes from (source) and its range in source.
///
/// Relative paths in configuration files or from CLI options
/// require different anchoring:
/// ## Equality, Hash, and Ordering
/// The equality, hash, and ordering are solely based on the value. They disregard the value's range
/// or source.
///
/// * CLI: The path is relative to the current working directory
/// * Configuration file: The path is relative to the project's root.
#[derive(Debug, Clone)]
pub struct RelativePathBuf {
path: SystemPathBuf,
/// This ensures that two resolved configurations are identical even if the position of a value has changed
/// or if the values were loaded from different sources.
#[derive(Clone)]
pub struct RangedValue<T> {
value: T,
source: ValueSource,

/// The byte range of `value` in `source`.
///
/// Can be `None` because not all sources support a range.
/// For example, arguments provided on the CLI won't have a range attached.
range: Option<TextRange>,
}

impl RelativePathBuf {
pub fn new(path: impl AsRef<SystemPath>, source: ValueSource) -> Self {
impl<T> RangedValue<T> {
pub fn new(value: T, source: ValueSource) -> Self {
Self::with_range(value, source, TextRange::default())
}

pub fn cli(value: T) -> Self {
Self::with_range(value, ValueSource::Cli, TextRange::default())
}

pub fn with_range(value: T, source: ValueSource, range: TextRange) -> Self {
Self {
path: path.as_ref().to_path_buf(),
value,
range: Some(range),
source,
}
}

pub fn cli(path: impl AsRef<SystemPath>) -> Self {
Self::new(path, ValueSource::Cli)
pub fn range(&self) -> Option<TextRange> {
self.range
}

/// Returns the relative path as specified by the user.
pub fn path(&self) -> &SystemPath {
&self.path
pub fn source(&self) -> &ValueSource {
&self.source
}

/// Returns the owned relative path.
pub fn into_path_buf(self) -> SystemPathBuf {
self.path
#[must_use]
pub fn with_source(mut self, source: ValueSource) -> Self {
self.source = source;
self
}

/// Resolves the absolute path for `self` based on its origin.
pub fn absolute_with_db(&self, db: &dyn Db) -> SystemPathBuf {
self.absolute(db.project().root(db), db.system())
pub fn into_inner(self) -> T {
self.value
}
}

/// Resolves the absolute path for `self` based on its origin.
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
let relative_to = match &self.source {
ValueSource::File(_) => project_root,
ValueSource::Cli => system.current_directory(),
};
impl<T> Combine for RangedValue<T> {
fn combine(self, _other: Self) -> Self
where
Self: Sized,
{
self
}
fn combine_with(&mut self, _other: Self) {}
}

SystemPath::absolute(&self.path, relative_to)
impl<T> IntoIterator for RangedValue<T>
where
T: IntoIterator,
{
type Item = T::Item;
type IntoIter = T::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}

// TODO(micha): Derive most of those implementations once `RelativePath` uses `Value`.
// and use `serde(transparent, deny_unknown_fields)`
impl Combine for RelativePathBuf {
fn combine(self, _other: Self) -> Self {
self
// The type already has an `iter` method thanks to `Deref`.
#[allow(clippy::into_iter_without_iter)]
impl<'a, T> IntoIterator for &'a RangedValue<T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}

#[inline(always)]
fn combine_with(&mut self, _other: Self) {}
// The type already has a `into_iter_mut` method thanks to `DerefMut`.
#[allow(clippy::into_iter_without_iter)]
impl<'a, T> IntoIterator for &'a mut RangedValue<T>
where
&'a mut T: IntoIterator,
{
type Item = <&'a mut T as IntoIterator>::Item;
type IntoIter = <&'a mut T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}

impl Hash for RelativePathBuf {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state);
impl<T> fmt::Debug for RangedValue<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(f)
}
}

impl<T> fmt::Display for RangedValue<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(f)
}
}

impl<T> Deref for RangedValue<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}

impl PartialEq for RelativePathBuf {
impl<T> DerefMut for RangedValue<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.value
}
}

impl<T, U: ?Sized> AsRef<U> for RangedValue<T>
where
T: AsRef<U>,
{
fn as_ref(&self) -> &U {
self.value.as_ref()
}
}

impl<T: PartialEq> PartialEq for RangedValue<T> {
fn eq(&self, other: &Self) -> bool {
self.path.eq(&other.path)
self.value.eq(&other.value)
}
}

impl Eq for RelativePathBuf {}
impl<T: PartialEq<T>> PartialEq<T> for RangedValue<T> {
fn eq(&self, other: &T) -> bool {
self.value.eq(other)
}
}

impl<T: Eq> Eq for RangedValue<T> {}

impl PartialOrd for RelativePathBuf {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
impl<T: Hash> Hash for RangedValue<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}

impl Ord for RelativePathBuf {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path.cmp(&other.path)
impl<T: PartialOrd> PartialOrd for RangedValue<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value)
}
}

impl Serialize for RelativePathBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.path.serialize(serializer)
impl<T: PartialOrd<T>> PartialOrd<T> for RangedValue<T> {
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
self.value.partial_cmp(other)
}
}

impl<T: Ord> Ord for RangedValue<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}

impl<'de> Deserialize<'de> for RelativePathBuf {
impl<'de, T> Deserialize<'de> for RangedValue<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let path = SystemPathBuf::deserialize(deserializer)?;
let spanned: Spanned<T> = Spanned::deserialize(deserializer)?;
let span = spanned.span();
let range = TextRange::new(
TextSize::try_from(span.start).expect("Configuration file to be smaller than 4GB"),
TextSize::try_from(span.end).expect("Configuration file to be smaller than 4GB"),
);

Ok(VALUE_SOURCE.with_borrow(|source| {
let source = source
.clone()
.expect("Thread local `VALUE_SOURCE` to be set. Use `ValueSourceGuard` to set the value source before calling serde/toml `from_str`.");
let source = source.clone().unwrap();

Self { path, source }
Self::with_range(spanned.into_inner(), source, range)
}))
}
}

impl<T> Serialize for RangedValue<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value.serialize(serializer)
}
}

/// A possibly relative path in a configuration file.
///
/// Relative paths in configuration files or from CLI options
/// require different anchoring:
///
/// * CLI: The path is relative to the current working directory
/// * Configuration file: The path is relative to the project's root.
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct RelativePathBuf(RangedValue<SystemPathBuf>);

impl RelativePathBuf {
pub fn new(path: impl AsRef<SystemPath>, source: ValueSource) -> Self {
Self(RangedValue::new(path.as_ref().to_path_buf(), source))
}

pub fn cli(path: impl AsRef<SystemPath>) -> Self {
Self::new(path, ValueSource::Cli)
}

/// Returns the relative path as specified by the user.
pub fn path(&self) -> &SystemPath {
&self.0
}

/// Returns the owned relative path.
pub fn into_path_buf(self) -> SystemPathBuf {
self.0.into_inner()
}

/// Resolves the absolute path for `self` based on its origin.
pub fn absolute_with_db(&self, db: &dyn Db) -> SystemPathBuf {
self.absolute(db.project().root(db), db.system())
}

/// Resolves the absolute path for `self` based on its origin.
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
let relative_to = match &self.0.source {
ValueSource::File(_) => project_root,
ValueSource::Cli => system.current_directory(),
};

SystemPath::absolute(&self.0, relative_to)
}
}

impl Combine for RelativePathBuf {
fn combine(self, other: Self) -> Self {
Self(self.0.combine(other.0))
}

fn combine_with(&mut self, other: Self) {
self.0.combine_with(other.0);
}
}
32 changes: 20 additions & 12 deletions crates/red_knot_python_semantic/src/lint.rs
Original file line number Diff line number Diff line change
@@ -414,7 +414,7 @@ pub struct RuleSelection {
/// Map with the severity for each enabled lint rule.
///
/// If a rule isn't present in this map, then it should be considered disabled.
lints: FxHashMap<LintId, Severity>,
lints: FxHashMap<LintId, (Severity, LintSource)>,
}

impl RuleSelection {
@@ -427,7 +427,7 @@ impl RuleSelection {
.filter_map(|lint| {
Severity::try_from(lint.default_level())
.ok()
.map(|severity| (*lint, severity))
.map(|severity| (*lint, (severity, LintSource::Default)))
})
.collect();

@@ -441,12 +441,14 @@ impl RuleSelection {

/// Returns an iterator over all enabled lints and their severity.
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LintId, Severity)> + '_ {
self.lints.iter().map(|(&lint, &severity)| (lint, severity))
self.lints
.iter()
.map(|(&lint, &(severity, _))| (lint, severity))
}

/// Returns the configured severity for the lint with the given id or `None` if the lint is disabled.
pub fn severity(&self, lint: LintId) -> Option<Severity> {
self.lints.get(&lint).copied()
self.lints.get(&lint).map(|(severity, _)| *severity)
}

/// Returns `true` if the `lint` is enabled.
@@ -457,19 +459,25 @@ impl RuleSelection {
/// Enables `lint` and configures with the given `severity`.
///
/// Overrides any previous configuration for the lint.
pub fn enable(&mut self, lint: LintId, severity: Severity) {
self.lints.insert(lint, severity);
pub fn enable(&mut self, lint: LintId, severity: Severity, source: LintSource) {
self.lints.insert(lint, (severity, source));
}

/// Disables `lint` if it was previously enabled.
pub fn disable(&mut self, lint: LintId) {
self.lints.remove(&lint);
}
}

/// Merges the enabled lints from `other` into this selection.
///
/// Lints from `other` will override any existing configuration.
pub fn merge(&mut self, other: &RuleSelection) {
self.lints.extend(other.iter());
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub enum LintSource {
/// The user didn't enable the rule explicitly, instead it's enabled by default.
#[default]
Default,

/// The rule was enabled by using a CLI argument
Cli,

/// The rule was enabled in a configuration file.
File,
}
3 changes: 2 additions & 1 deletion crates/red_knot_wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use js_sys::Error;
use wasm_bindgen::prelude::*;

use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::value::RangedValue;
use red_knot_project::ProjectMetadata;
use red_knot_project::{Db, ProjectDatabase};
use ruff_db::diagnostic::Diagnostic;
@@ -48,7 +49,7 @@ impl Workspace {

workspace.apply_cli_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(settings.python_version.into()),
python_version: Some(RangedValue::cli(settings.python_version.into())),
..EnvironmentOptions::default()
}),
..Options::default()
3 changes: 2 additions & 1 deletion crates/ruff_benchmark/benches/red_knot.rs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

use rayon::ThreadPoolBuilder;
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
use red_knot_project::metadata::value::RangedValue;
use red_knot_project::watch::{ChangeEvent, ChangedKind};
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
use red_knot_python_semantic::PythonVersion;
@@ -76,7 +77,7 @@ fn setup_case() -> Case {
let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap();
metadata.apply_cli_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(PythonVersion::PY312),
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
..EnvironmentOptions::default()
}),
..Options::default()

0 comments on commit 05ea77b

Please sign in to comment.