diff --git a/crates/red_knot/src/args.rs b/crates/red_knot/src/args.rs index f7863ab53e..75b21f4cf2 100644 --- a/crates/red_knot/src/args.rs +++ b/crates/red_knot/src/args.rs @@ -71,6 +71,14 @@ pub(crate) struct CheckCommand { #[arg(long, value_name = "VERSION", alias = "target-version")] pub(crate) python_version: Option, + /// Target platform to assume when resolving types. + /// + /// This is used to specialize the type of `sys.platform` and will affect the visibility + /// of platform-specific functions and attributes. If the value is set to `all`, no + /// assumptions are made about the target platform. + #[arg(long, value_name = "PLATFORM", alias = "platform")] + pub(crate) python_platform: Option, + #[clap(flatten)] pub(crate) verbosity: Verbosity, @@ -116,6 +124,9 @@ impl CheckCommand { python_version: self .python_version .map(|version| RangedValue::cli(version.into())), + python_platform: self + .python_platform + .map(|platform| RangedValue::cli(platform.into())), python: self.python.map(RelativePathBuf::cli), typeshed: self.typeshed.map(RelativePathBuf::cli), extra_paths: self.extra_search_path.map(|extra_search_paths| { @@ -124,7 +135,6 @@ impl CheckCommand { .map(RelativePathBuf::cli) .collect() }), - ..EnvironmentOptions::default() }), terminal: Some(TerminalOptions { output_format: self diff --git a/crates/red_knot/tests/cli.rs b/crates/red_knot/tests/cli.rs index 4482ff17b8..80e7ede3cc 100644 --- a/crates/red_knot/tests/cli.rs +++ b/crates/red_knot/tests/cli.rs @@ -6,9 +6,9 @@ use std::process::Command; use tempfile::TempDir; /// Specifying an option on the CLI should take precedence over the same setting in the -/// project's configuration. +/// project's configuration. Here, this is tested for the Python version. #[test] -fn config_override() -> anyhow::Result<()> { +fn config_override_python_version() -> anyhow::Result<()> { let case = TestCase::with_files([ ( "pyproject.toml", @@ -57,6 +57,67 @@ fn config_override() -> anyhow::Result<()> { Ok(()) } +/// Same as above, but for the Python platform. +#[test] +fn config_override_python_platform() -> anyhow::Result<()> { + let case = TestCase::with_files([ + ( + "pyproject.toml", + r#" + [tool.knot.environment] + python-platform = "linux" + "#, + ), + ( + "test.py", + r#" + import sys + from typing_extensions import reveal_type + + reveal_type(sys.platform) + "#, + ), + ])?; + + assert_cmd_snapshot!(case.command(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + info: revealed-type + --> /test.py:5:1 + | + 3 | from typing_extensions import reveal_type + 4 | + 5 | reveal_type(sys.platform) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Revealed type is `Literal["linux"]` + | + + Found 1 diagnostic + + ----- stderr ----- + "#); + + assert_cmd_snapshot!(case.command().arg("--python-platform").arg("all"), @r" + success: true + exit_code: 0 + ----- stdout ----- + info: revealed-type + --> /test.py:5:1 + | + 3 | from typing_extensions import reveal_type + 4 | + 5 | reveal_type(sys.platform) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Revealed type is `LiteralString` + | + + Found 1 diagnostic + + ----- stderr ----- + "); + + Ok(()) +} + /// Paths specified on the CLI are relative to the current working directory and not the project root. /// /// We test this by adding an extra search path from the CLI to the libs directory when diff --git a/crates/red_knot_project/src/metadata/options.rs b/crates/red_knot_project/src/metadata/options.rs index 5506dceab1..22b3d43f43 100644 --- a/crates/red_knot_project/src/metadata/options.rs +++ b/crates/red_knot_project/src/metadata/options.rs @@ -224,7 +224,7 @@ impl Options { #[serde(rename_all = "kebab-case", deny_unknown_fields)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct EnvironmentOptions { - /// Specifies the version of Python that will be used to execute the source code. + /// Specifies the version of Python that will be used to analyze the source code. /// The version should be specified as a string in the format `M.m` where `M` is the major version /// and `m` is the minor (e.g. "3.0" or "3.6"). /// If a version is provided, knot will generate errors if the source code makes use of language features @@ -233,7 +233,7 @@ pub struct EnvironmentOptions { #[serde(skip_serializing_if = "Option::is_none")] pub python_version: Option>, - /// Specifies the target platform that will be used to execute the source code. + /// Specifies the target platform that will be used to analyze the source code. /// If specified, Red Knot will tailor its use of type stub files, /// which conditionalize type definitions based on the platform. /// diff --git a/crates/red_knot_python_semantic/src/python_platform.rs b/crates/red_knot_python_semantic/src/python_platform.rs index 5a8a8bd3da..e7650724ba 100644 --- a/crates/red_knot_python_semantic/src/python_platform.rs +++ b/crates/red_knot_python_semantic/src/python_platform.rs @@ -21,6 +21,15 @@ pub enum PythonPlatform { Identifier(String), } +impl From for PythonPlatform { + fn from(platform: String) -> Self { + match platform.as_str() { + "all" => PythonPlatform::All, + _ => PythonPlatform::Identifier(platform.to_string()), + } + } +} + impl Display for PythonPlatform { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { diff --git a/knot.schema.json b/knot.schema.json index 7f96fb4fe1..07dca02b11 100644 --- a/knot.schema.json +++ b/knot.schema.json @@ -89,7 +89,7 @@ ] }, "python-platform": { - "description": "Specifies the target platform that will be used to execute the source code. If specified, Red Knot will tailor its use of type stub files, which conditionalize type definitions based on the platform.\n\nIf no platform is specified, knot will use `all` or the current platform in the LSP use case.", + "description": "Specifies the target platform that will be used to analyze the source code. If specified, Red Knot will tailor its use of type stub files, which conditionalize type definitions based on the platform.\n\nIf no platform is specified, knot will use `all` or the current platform in the LSP use case.", "anyOf": [ { "$ref": "#/definitions/PythonPlatform" @@ -100,7 +100,7 @@ ] }, "python-version": { - "description": "Specifies the version of Python that will be used to execute the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. \"3.0\" or \"3.6\"). If a version is provided, knot will generate errors if the source code makes use of language features that are not supported in that version. It will also tailor its use of type stub files, which conditionalizes type definitions based on the version.", + "description": "Specifies the version of Python that will be used to analyze the source code. The version should be specified as a string in the format `M.m` where `M` is the major version and `m` is the minor (e.g. \"3.0\" or \"3.6\"). If a version is provided, knot will generate errors if the source code makes use of language features that are not supported in that version. It will also tailor its use of type stub files, which conditionalizes type definitions based on the version.", "anyOf": [ { "$ref": "#/definitions/PythonVersion"