[red-knot] Add --python-platform CLI option (#17284)

## Summary

Add a new `--python-platform` command-line option, in analogy to
`--python-version`.

## Test Plan

Added new integration test.
This commit is contained in:
David Peter 2025-04-07 21:04:44 +02:00 committed by GitHub
parent 4a4a376f02
commit 3657f798c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 87 additions and 7 deletions

View file

@ -71,6 +71,14 @@ pub(crate) struct CheckCommand {
#[arg(long, value_name = "VERSION", alias = "target-version")] #[arg(long, value_name = "VERSION", alias = "target-version")]
pub(crate) python_version: Option<PythonVersion>, pub(crate) python_version: Option<PythonVersion>,
/// 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<String>,
#[clap(flatten)] #[clap(flatten)]
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
@ -116,6 +124,9 @@ impl CheckCommand {
python_version: self python_version: self
.python_version .python_version
.map(|version| RangedValue::cli(version.into())), .map(|version| RangedValue::cli(version.into())),
python_platform: self
.python_platform
.map(|platform| RangedValue::cli(platform.into())),
python: self.python.map(RelativePathBuf::cli), python: self.python.map(RelativePathBuf::cli),
typeshed: self.typeshed.map(RelativePathBuf::cli), typeshed: self.typeshed.map(RelativePathBuf::cli),
extra_paths: self.extra_search_path.map(|extra_search_paths| { extra_paths: self.extra_search_path.map(|extra_search_paths| {
@ -124,7 +135,6 @@ impl CheckCommand {
.map(RelativePathBuf::cli) .map(RelativePathBuf::cli)
.collect() .collect()
}), }),
..EnvironmentOptions::default()
}), }),
terminal: Some(TerminalOptions { terminal: Some(TerminalOptions {
output_format: self output_format: self

View file

@ -6,9 +6,9 @@ use std::process::Command;
use tempfile::TempDir; use tempfile::TempDir;
/// Specifying an option on the CLI should take precedence over the same setting in the /// 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] #[test]
fn config_override() -> anyhow::Result<()> { fn config_override_python_version() -> anyhow::Result<()> {
let case = TestCase::with_files([ let case = TestCase::with_files([
( (
"pyproject.toml", "pyproject.toml",
@ -57,6 +57,67 @@ fn config_override() -> anyhow::Result<()> {
Ok(()) 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
--> <temp_dir>/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
--> <temp_dir>/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. /// 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 /// We test this by adding an extra search path from the CLI to the libs directory when

View file

@ -224,7 +224,7 @@ impl Options {
#[serde(rename_all = "kebab-case", deny_unknown_fields)] #[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct EnvironmentOptions { 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 /// 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"). /// 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 /// 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")] #[serde(skip_serializing_if = "Option::is_none")]
pub python_version: Option<RangedValue<PythonVersion>>, pub python_version: Option<RangedValue<PythonVersion>>,
/// 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, /// If specified, Red Knot will tailor its use of type stub files,
/// which conditionalize type definitions based on the platform. /// which conditionalize type definitions based on the platform.
/// ///

View file

@ -21,6 +21,15 @@ pub enum PythonPlatform {
Identifier(String), Identifier(String),
} }
impl From<String> for PythonPlatform {
fn from(platform: String) -> Self {
match platform.as_str() {
"all" => PythonPlatform::All,
_ => PythonPlatform::Identifier(platform.to_string()),
}
}
}
impl Display for PythonPlatform { impl Display for PythonPlatform {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {

View file

@ -89,7 +89,7 @@
] ]
}, },
"python-platform": { "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": [ "anyOf": [
{ {
"$ref": "#/definitions/PythonPlatform" "$ref": "#/definitions/PythonPlatform"
@ -100,7 +100,7 @@
] ]
}, },
"python-version": { "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": [ "anyOf": [
{ {
"$ref": "#/definitions/PythonVersion" "$ref": "#/definitions/PythonVersion"