diff --git a/crates/red_knot/src/main.rs b/crates/red_knot/src/main.rs index e898d37618d28..ec05dcd6635b0 100644 --- a/crates/red_knot/src/main.rs +++ b/crates/red_knot/src/main.rs @@ -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| { diff --git a/crates/red_knot/tests/cli.rs b/crates/red_knot/tests/cli.rs index 64dae7bdc64bf..7bea404922406 100644 --- a/crates/red_knot/tests/cli.rs +++ b/crates/red_knot/tests/cli.rs @@ -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] /pyproject.toml:3:1 Unknown lint rule `division-by-zer` ----- stderr ----- "); diff --git a/crates/red_knot/tests/file_watching.rs b/crates/red_knot/tests/file_watching.rs index 9ac391026ec79..4a86e24cca6b8 100644 --- a/crates/red_knot/tests/file_watching.rs +++ b/crates/red_knot/tests/file_watching.rs @@ -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() diff --git a/crates/red_knot_project/src/metadata.rs b/crates/red_knot_project/src/metadata.rs index c249fc291bcde..6fcd5f7a02dcc 100644 --- a/crates/red_knot_project/src/metadata.rs +++ b/crates/red_knot_project/src/metadata.rs @@ -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 diff --git a/crates/red_knot_project/src/metadata/options.rs b/crates/red_knot_project/src/metadata/options.rs index 765424ce4f38f..f44dbb90c5034 100644 --- a/crates/red_knot_project/src/metadata/options.rs +++ b/crates/red_knot_project/src/metadata/options.rs @@ -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, + pub python_version: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub python_platform: Option, + pub python_platform: Option>, /// 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, + inner: FxHashMap, RangedValue>, } #[derive(Error, Debug)] @@ -197,6 +217,8 @@ pub struct OptionDiagnostic { id: DiagnosticId, message: String, severity: Severity, + file: Option, + range: Option, } impl OptionDiagnostic { @@ -205,8 +227,22 @@ impl OptionDiagnostic { id, message, severity, + file: None, + range: None, } } + + #[must_use] + fn with_file(mut self, file: Option) -> Self { + self.file = file; + self + } + + #[must_use] + fn with_range(mut self, range: Option) -> Self { + self.range = range; + self + } } impl Diagnostic for OptionDiagnostic { @@ -219,11 +255,11 @@ impl Diagnostic for OptionDiagnostic { } fn file(&self) -> Option { - None + self.file } fn range(&self) -> Option { - None + self.range } fn severity(&self) -> Severity { diff --git a/crates/red_knot_project/src/metadata/pyproject.rs b/crates/red_knot_project/src/metadata/pyproject.rs index b2924399b659c..4ad5f3c5b8a36 100644 --- a/crates/red_knot_project/src/metadata/pyproject.rs +++ b/crates/red_knot_project/src/metadata/pyproject.rs @@ -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, + pub name: Option>, /// The version of the project - pub version: Option, + pub version: Option>, /// The Python versions this project is compatible with. - pub requires_python: Option, + pub requires_python: Option>, } #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] diff --git a/crates/red_knot_project/src/metadata/value.rs b/crates/red_knot_project/src/metadata/value.rs index 4d2a64336347a..c4a663ad8fe9f 100644 --- a/crates/red_knot_project/src/metadata/value.rs +++ b/crates/red_knot_project/src/metadata/value.rs @@ -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 { + 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, } -impl RelativePathBuf { - pub fn new(path: impl AsRef, source: ValueSource) -> Self { +impl RangedValue { + 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) -> Self { - Self::new(path, ValueSource::Cli) + pub fn range(&self) -> Option { + 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 Combine for RangedValue { + fn combine(self, _other: Self) -> Self + where + Self: Sized, + { + self + } + fn combine_with(&mut self, _other: Self) {} +} - SystemPath::absolute(&self.path, relative_to) +impl IntoIterator for RangedValue +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 +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 +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(&self, state: &mut H) { - self.path.hash(state); +impl fmt::Debug for RangedValue +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.value.fmt(f) + } +} + +impl fmt::Display for RangedValue +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.value.fmt(f) + } +} + +impl Deref for RangedValue { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.value } } -impl PartialEq for RelativePathBuf { +impl DerefMut for RangedValue { + fn deref_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl AsRef for RangedValue +where + T: AsRef, +{ + fn as_ref(&self) -> &U { + self.value.as_ref() + } +} + +impl PartialEq for RangedValue { fn eq(&self, other: &Self) -> bool { - self.path.eq(&other.path) + self.value.eq(&other.value) } } -impl Eq for RelativePathBuf {} +impl> PartialEq for RangedValue { + fn eq(&self, other: &T) -> bool { + self.value.eq(other) + } +} + +impl Eq for RangedValue {} -impl PartialOrd for RelativePathBuf { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl Hash for RangedValue { + fn hash(&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 PartialOrd for RangedValue { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) } } -impl Serialize for RelativePathBuf { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.path.serialize(serializer) +impl> PartialOrd for RangedValue { + fn partial_cmp(&self, other: &T) -> Option { + self.value.partial_cmp(other) + } +} + +impl Ord for RangedValue { + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) } } -impl<'de> Deserialize<'de> for RelativePathBuf { +impl<'de, T> Deserialize<'de> for RangedValue +where + T: Deserialize<'de>, +{ fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let path = SystemPathBuf::deserialize(deserializer)?; + let spanned: Spanned = 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 Serialize for RangedValue +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + 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); + +impl RelativePathBuf { + pub fn new(path: impl AsRef, source: ValueSource) -> Self { + Self(RangedValue::new(path.as_ref().to_path_buf(), source)) + } + + pub fn cli(path: impl AsRef) -> 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); + } +} diff --git a/crates/red_knot_python_semantic/src/lint.rs b/crates/red_knot_python_semantic/src/lint.rs index 92450858cf334..56280363c4be7 100644 --- a/crates/red_knot_python_semantic/src/lint.rs +++ b/crates/red_knot_python_semantic/src/lint.rs @@ -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, + lints: FxHashMap, } 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 + '_ { - 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 { - 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, } diff --git a/crates/red_knot_wasm/src/lib.rs b/crates/red_knot_wasm/src/lib.rs index 27b2a580e5c47..ad9d422566b38 100644 --- a/crates/red_knot_wasm/src/lib.rs +++ b/crates/red_knot_wasm/src/lib.rs @@ -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() diff --git a/crates/ruff_benchmark/benches/red_knot.rs b/crates/ruff_benchmark/benches/red_knot.rs index 4c6822ed9aa10..255e89d92af60 100644 --- a/crates/ruff_benchmark/benches/red_knot.rs +++ b/crates/ruff_benchmark/benches/red_knot.rs @@ -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()