From 3d85c7d09c7ffc19f3933fef97bbe6e455ec19df Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 13:25:01 +0100 Subject: [PATCH] Add support for sys.platform --- .../resources/mdtest/sys_platform.md | 43 +++++++++++++++++++ crates/red_knot_python_semantic/src/db.rs | 6 ++- crates/red_knot_python_semantic/src/lib.rs | 2 + .../src/module_resolver/resolver.rs | 6 ++- .../src/module_resolver/testing.rs | 12 +++++- .../red_knot_python_semantic/src/program.rs | 13 ++++-- .../src/python_platform.rs | 13 ++++++ crates/red_knot_python_semantic/src/types.rs | 15 +++++++ crates/red_knot_test/src/config.rs | 13 +++++- crates/red_knot_test/src/db.rs | 5 ++- crates/red_knot_test/src/lib.rs | 3 ++ .../src/workspace/settings.rs | 1 + crates/ruff_graph/src/db.rs | 1 + 13 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/sys_platform.md create mode 100644 crates/red_knot_python_semantic/src/python_platform.rs diff --git a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md new file mode 100644 index 0000000000..40cf1e2b67 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md @@ -0,0 +1,43 @@ +# `sys.platform` + +## Default value + +When no target platform is specified, we fall back to the type of `sys.platform` declared in +typeshed: + +```toml +[environment] +# No python-platform entry +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: str +``` + +## Explicit selection of `all` platforms + +```toml +[environment] +python-platform = "all" +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: str +``` + +## Explicit selection of a specific platform + +```toml +[environment] +python-platform = "linux" +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: Literal["linux"] +``` diff --git a/crates/red_knot_python_semantic/src/db.rs b/crates/red_knot_python_semantic/src/db.rs index 1e40d7ad13..e3677f816c 100644 --- a/crates/red_knot_python_semantic/src/db.rs +++ b/crates/red_knot_python_semantic/src/db.rs @@ -16,7 +16,7 @@ pub(crate) mod tests { use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; - use crate::{default_lint_registry, ProgramSettings}; + use crate::{default_lint_registry, ProgramSettings, PythonPlatform}; use super::Db; use crate::lint::RuleSelection; @@ -127,6 +127,8 @@ pub(crate) mod tests { pub(crate) struct TestDbBuilder<'a> { /// Target Python version python_version: PythonVersion, + /// Target Python platform + python_platform: PythonPlatform, /// Path to a custom typeshed directory custom_typeshed: Option, /// Path and content pairs for files that should be present @@ -137,6 +139,7 @@ pub(crate) mod tests { pub(crate) fn new() -> Self { Self { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), custom_typeshed: None, files: vec![], } @@ -173,6 +176,7 @@ pub(crate) mod tests { &db, &ProgramSettings { python_version: self.python_version, + python_platform: self.python_platform, search_paths, }, ) diff --git a/crates/red_knot_python_semantic/src/lib.rs b/crates/red_knot_python_semantic/src/lib.rs index 324d2756b8..b05a41a0ef 100644 --- a/crates/red_knot_python_semantic/src/lib.rs +++ b/crates/red_knot_python_semantic/src/lib.rs @@ -7,6 +7,7 @@ pub use db::Db; pub use module_name::ModuleName; pub use module_resolver::{resolve_module, system_module_search_paths, KnownModule, Module}; pub use program::{Program, ProgramSettings, SearchPathSettings, SitePackages}; +pub use python_platform::PythonPlatform; pub use python_version::PythonVersion; pub use semantic_model::{HasTy, SemanticModel}; @@ -17,6 +18,7 @@ mod module_name; mod module_resolver; mod node_key; mod program; +mod python_platform; mod python_version; pub mod semantic_index; mod semantic_model; diff --git a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs index 990fc01a51..954ded4262 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs @@ -721,8 +721,8 @@ mod tests { use crate::module_name::ModuleName; use crate::module_resolver::module::ModuleKind; use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; - use crate::ProgramSettings; use crate::PythonVersion; + use crate::{ProgramSettings, PythonPlatform}; use super::*; @@ -1262,7 +1262,7 @@ mod tests { fn symlink() -> anyhow::Result<()> { use anyhow::Context; - use crate::program::Program; + use crate::{program::Program, PythonPlatform}; use ruff_db::system::{OsSystem, SystemPath}; use crate::db::tests::TestDb; @@ -1296,6 +1296,7 @@ mod tests { &db, &ProgramSettings { python_version: PythonVersion::PY38, + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], src_root: src.clone(), @@ -1801,6 +1802,7 @@ not_a_directory &db, &ProgramSettings { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], src_root: SystemPathBuf::from("/src"), diff --git a/crates/red_knot_python_semantic/src/module_resolver/testing.rs b/crates/red_knot_python_semantic/src/module_resolver/testing.rs index 75d66835f5..365dec2893 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/testing.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/testing.rs @@ -4,7 +4,7 @@ use ruff_db::vendored::VendoredPathBuf; use crate::db::tests::TestDb; use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; -use crate::{ProgramSettings, SitePackages}; +use crate::{ProgramSettings, PythonPlatform, SitePackages}; /// A test case for the module resolver. /// @@ -101,6 +101,7 @@ pub(crate) struct UnspecifiedTypeshed; pub(crate) struct TestCaseBuilder { typeshed_option: T, python_version: PythonVersion, + python_platform: PythonPlatform, first_party_files: Vec, site_packages_files: Vec, } @@ -147,6 +148,7 @@ impl TestCaseBuilder { Self { typeshed_option: UnspecifiedTypeshed, python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), first_party_files: vec![], site_packages_files: vec![], } @@ -157,12 +159,14 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: _, python_version, + python_platform, first_party_files, site_packages_files, } = self; TestCaseBuilder { typeshed_option: VendoredTypeshed, python_version, + python_platform, first_party_files, site_packages_files, } @@ -176,6 +180,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: _, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -183,6 +188,7 @@ impl TestCaseBuilder { TestCaseBuilder { typeshed_option: typeshed, python_version, + python_platform, first_party_files, site_packages_files, } @@ -212,6 +218,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -227,6 +234,7 @@ impl TestCaseBuilder { &db, &ProgramSettings { python_version, + python_platform, search_paths: SearchPathSettings { extra_paths: vec![], src_root: src.clone(), @@ -269,6 +277,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: VendoredTypeshed, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -283,6 +292,7 @@ impl TestCaseBuilder { &db, &ProgramSettings { python_version, + python_platform, search_paths: SearchPathSettings { site_packages: SitePackages::Known(vec![site_packages.clone()]), ..SearchPathSettings::new(src.clone()) diff --git a/crates/red_knot_python_semantic/src/program.rs b/crates/red_knot_python_semantic/src/program.rs index cf85fd7cf4..54147cfccf 100644 --- a/crates/red_knot_python_semantic/src/program.rs +++ b/crates/red_knot_python_semantic/src/program.rs @@ -1,3 +1,4 @@ +use crate::python_platform::PythonPlatform; use crate::python_version::PythonVersion; use anyhow::Context; use salsa::Durability; @@ -12,6 +13,8 @@ use crate::Db; pub struct Program { pub python_version: PythonVersion, + pub python_platform: PythonPlatform, + #[return_ref] pub(crate) search_paths: SearchPaths, } @@ -20,6 +23,7 @@ impl Program { pub fn from_settings(db: &dyn Db, settings: &ProgramSettings) -> anyhow::Result { let ProgramSettings { python_version, + python_platform, search_paths, } = settings; @@ -28,9 +32,11 @@ impl Program { let search_paths = SearchPaths::from_settings(db, search_paths) .with_context(|| "Invalid search path settings")?; - Ok(Program::builder(settings.python_version, search_paths) - .durability(Durability::HIGH) - .new(db)) + Ok( + Program::builder(*python_version, python_platform.clone(), search_paths) + .durability(Durability::HIGH) + .new(db), + ) } pub fn update_search_paths( @@ -57,6 +63,7 @@ impl Program { #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ProgramSettings { pub python_version: PythonVersion, + pub python_platform: PythonPlatform, pub search_paths: SearchPathSettings, } diff --git a/crates/red_knot_python_semantic/src/python_platform.rs b/crates/red_knot_python_semantic/src/python_platform.rs new file mode 100644 index 0000000000..8a1fed6f20 --- /dev/null +++ b/crates/red_knot_python_semantic/src/python_platform.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +/// The target platform to assume when resolving types. +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum PythonPlatform { + /// Do not make any assumptions about the target platform. + #[default] + All, + /// Assume a target platform like `linux`, `darwin`, `win32`, etc. + #[serde(untagged)] + Individual(String), +} diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index ee5d2c418c..5055e4b21c 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -137,6 +137,21 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> { return Symbol::Type(Type::BooleanLiteral(true), Boundness::Bound); } + if name == "platform" + && file_to_module(db, scope.file(db)).is_some_and(|module| module.name() == "sys") + { + match Program::get(db).python_platform(db) { + crate::PythonPlatform::Individual(platform) => { + return Symbol::Type( + Type::StringLiteral(StringLiteralType::new(db, platform.as_str())), + Boundness::Bound, + ); + } + crate::PythonPlatform::All => { + // Fall through to the looked up type + } + } + } let table = symbol_table(db, scope); table diff --git a/crates/red_knot_test/src/config.rs b/crates/red_knot_test/src/config.rs index cf677c485c..a2fe51226e 100644 --- a/crates/red_knot_test/src/config.rs +++ b/crates/red_knot_test/src/config.rs @@ -9,7 +9,7 @@ //! ``` use anyhow::Context; -use red_knot_python_semantic::PythonVersion; +use red_knot_python_semantic::{PythonPlatform, PythonVersion}; use serde::Deserialize; #[derive(Deserialize, Debug, Default, Clone)] @@ -28,13 +28,22 @@ impl MarkdownTestConfig { pub(crate) fn python_version(&self) -> Option { self.environment.as_ref().and_then(|env| env.python_version) } + + pub(crate) fn python_platform(&self) -> Option { + self.environment + .as_ref() + .and_then(|env| env.python_platform.clone()) + } } #[derive(Deserialize, Debug, Default, Clone)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct Environment { - /// Python version to assume when resolving types. + /// Target Python version to assume when resolving types. pub(crate) python_version: Option, + + /// Target platform to assume when resolving types. + pub(crate) python_platform: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/red_knot_test/src/db.rs b/crates/red_knot_test/src/db.rs index df1cfc503e..d6e3d82090 100644 --- a/crates/red_knot_test/src/db.rs +++ b/crates/red_knot_test/src/db.rs @@ -1,7 +1,7 @@ use red_knot_python_semantic::lint::RuleSelection; use red_knot_python_semantic::{ - default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonVersion, - SearchPathSettings, + default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonPlatform, + PythonVersion, SearchPathSettings, }; use ruff_db::files::{File, Files}; use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem}; @@ -40,6 +40,7 @@ impl Db { &db, &ProgramSettings { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(db.workspace_root.clone()), }, ) diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index f2a7639e29..9b39048a57 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -52,6 +52,9 @@ pub fn run(path: &Utf8Path, long_title: &str, short_title: &str, test_name: &str Program::get(&db) .set_python_version(&mut db) .to(test.configuration().python_version().unwrap_or_default()); + Program::get(&db) + .set_python_platform(&mut db) + .to(test.configuration().python_platform().unwrap_or_default()); // Remove all files so that the db is in a "fresh" state. db.memory_file_system().remove_all(); diff --git a/crates/red_knot_workspace/src/workspace/settings.rs b/crates/red_knot_workspace/src/workspace/settings.rs index 66493f441d..525357e184 100644 --- a/crates/red_knot_workspace/src/workspace/settings.rs +++ b/crates/red_knot_workspace/src/workspace/settings.rs @@ -40,6 +40,7 @@ impl Configuration { WorkspaceSettings { program: ProgramSettings { python_version: self.python_version.unwrap_or_default(), + python_platform: Default::default(), search_paths: self.search_paths.to_settings(workspace_root), }, } diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index a70ae6d91e..c21b27715e 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -49,6 +49,7 @@ impl ModuleDb { &db, &ProgramSettings { python_version, + python_platform: Default::default(), search_paths, }, )?;