Skip to content

Commit

Permalink
[red-knot] Move, rename and make public the PyVersion type (#12782)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Aug 9, 2024
1 parent b595346 commit c4e6519
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 128 deletions.
4 changes: 3 additions & 1 deletion crates/red_knot_python_semantic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use rustc_hash::FxHasher;
pub use db::Db;
pub use module_name::ModuleName;
pub use module_resolver::{resolve_module, system_module_search_paths, vendored_typeshed_stubs};
pub use program::{Program, ProgramSettings, SearchPathSettings, TargetVersion};
pub use program::{Program, ProgramSettings, SearchPathSettings};
pub use python_version::{PythonVersion, TargetVersion, UnsupportedPythonVersion};
pub use semantic_model::{HasTy, SemanticModel};

pub mod ast_node_ref;
Expand All @@ -15,6 +16,7 @@ mod module_name;
mod module_resolver;
mod node_key;
mod program;
mod python_version;
pub mod semantic_index;
mod semantic_model;
pub mod types;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,9 +499,8 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, File, Mod
let resolver_settings = module_resolution_settings(db);
let target_version = resolver_settings.target_version();
let resolver_state = ResolverState::new(db, target_version);
let (_, minor_version) = target_version.as_tuple();
let is_builtin_module =
ruff_python_stdlib::sys::is_builtin_module(minor_version, name.as_str());
ruff_python_stdlib::sys::is_builtin_module(target_version.minor_version(), name.as_str());

for search_path in resolver_settings.search_paths(db) {
// When a builtin module is imported, standard module resolution is bypassed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
use ruff_db::vendored::VendoredPathBuf;

use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings, TargetVersion};
use crate::program::{Program, SearchPathSettings};
use crate::python_version::TargetVersion;

/// A test case for the module resolver.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ruff_db::files::{system_path_to_file, File};
use super::vendored::vendored_typeshed_stubs;
use crate::db::Db;
use crate::module_name::ModuleName;
use crate::TargetVersion;
use crate::python_version::{PythonVersion, TargetVersion};

#[derive(Debug)]
pub(crate) struct LazyTypeshedVersions<'db>(OnceCell<&'db TypeshedVersions>);
Expand Down Expand Up @@ -63,7 +63,7 @@ impl<'db> LazyTypeshedVersions<'db> {
// Unwrapping here is not correct...
parse_typeshed_versions(db, versions_file).as_ref().unwrap()
});
versions.query_module(module, PyVersion::from(target_version))
versions.query_module(module, PythonVersion::from(target_version))
}
}

Expand Down Expand Up @@ -177,7 +177,7 @@ impl TypeshedVersions {
fn query_module(
&self,
module: &ModuleName,
target_version: PyVersion,
target_version: PythonVersion,
) -> TypeshedVersionsQueryResult {
if let Some(range) = self.exact(module) {
if range.contains(target_version) {
Expand Down Expand Up @@ -322,13 +322,13 @@ impl fmt::Display for TypeshedVersions {

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum PyVersionRange {
AvailableFrom(RangeFrom<PyVersion>),
AvailableWithin(RangeInclusive<PyVersion>),
AvailableFrom(RangeFrom<PythonVersion>),
AvailableWithin(RangeInclusive<PythonVersion>),
}

impl PyVersionRange {
#[must_use]
fn contains(&self, version: PyVersion) -> bool {
fn contains(&self, version: PythonVersion) -> bool {
match self {
Self::AvailableFrom(inner) => inner.contains(&version),
Self::AvailableWithin(inner) => inner.contains(&version),
Expand All @@ -342,9 +342,14 @@ impl FromStr for PyVersionRange {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('-').map(str::trim);
match (parts.next(), parts.next(), parts.next()) {
(Some(lower), Some(""), None) => Ok(Self::AvailableFrom((lower.parse()?)..)),
(Some(lower), Some(""), None) => {
let lower = PythonVersion::from_versions_file_string(lower)?;
Ok(Self::AvailableFrom(lower..))
}
(Some(lower), Some(upper), None) => {
Ok(Self::AvailableWithin((lower.parse()?)..=(upper.parse()?)))
let lower = PythonVersion::from_versions_file_string(lower)?;
let upper = PythonVersion::from_versions_file_string(upper)?;
Ok(Self::AvailableWithin(lower..=upper))
}
_ => Err(TypeshedVersionsParseErrorKind::UnexpectedNumberOfHyphens),
}
Expand All @@ -362,74 +367,20 @@ impl fmt::Display for PyVersionRange {
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct PyVersion {
major: u8,
minor: u8,
}

impl FromStr for PyVersion {
type Err = TypeshedVersionsParseErrorKind;

fn from_str(s: &str) -> Result<Self, Self::Err> {
impl PythonVersion {
fn from_versions_file_string(s: &str) -> Result<Self, TypeshedVersionsParseErrorKind> {
let mut parts = s.split('.').map(str::trim);
let (Some(major), Some(minor), None) = (parts.next(), parts.next(), parts.next()) else {
return Err(TypeshedVersionsParseErrorKind::UnexpectedNumberOfPeriods(
s.to_string(),
));
};
let major = match u8::from_str(major) {
Ok(major) => major,
Err(err) => {
return Err(TypeshedVersionsParseErrorKind::IntegerParsingFailure {
version: s.to_string(),
err,
})
}
};
let minor = match u8::from_str(minor) {
Ok(minor) => minor,
Err(err) => {
return Err(TypeshedVersionsParseErrorKind::IntegerParsingFailure {
version: s.to_string(),
err,
})
PythonVersion::try_from((major, minor)).map_err(|int_parse_error| {
TypeshedVersionsParseErrorKind::IntegerParsingFailure {
version: s.to_string(),
err: int_parse_error,
}
};
Ok(Self { major, minor })
}
}

impl fmt::Display for PyVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let PyVersion { major, minor } = self;
write!(f, "{major}.{minor}")
}
}

impl From<TargetVersion> for PyVersion {
fn from(value: TargetVersion) -> Self {
match value {
TargetVersion::Py37 => PyVersion { major: 3, minor: 7 },
TargetVersion::Py38 => PyVersion { major: 3, minor: 8 },
TargetVersion::Py39 => PyVersion { major: 3, minor: 9 },
TargetVersion::Py310 => PyVersion {
major: 3,
minor: 10,
},
TargetVersion::Py311 => PyVersion {
major: 3,
minor: 11,
},
TargetVersion::Py312 => PyVersion {
major: 3,
minor: 12,
},
TargetVersion::Py313 => PyVersion {
major: 3,
minor: 13,
},
}
})
}
}

Expand Down
54 changes: 1 addition & 53 deletions crates/red_knot_python_semantic/src/program.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::python_version::TargetVersion;
use crate::Db;
use ruff_db::system::SystemPathBuf;
use salsa::Durability;
Expand All @@ -24,59 +25,6 @@ pub struct ProgramSettings {
pub search_paths: SearchPathSettings,
}

/// Enumeration of all supported Python versions
///
/// TODO: unify with the `PythonVersion` enum in the linter/formatter crates?
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum TargetVersion {
Py37,
#[default]
Py38,
Py39,
Py310,
Py311,
Py312,
Py313,
}

impl TargetVersion {
pub const fn as_tuple(self) -> (u8, u8) {
match self {
Self::Py37 => (3, 7),
Self::Py38 => (3, 8),
Self::Py39 => (3, 9),
Self::Py310 => (3, 10),
Self::Py311 => (3, 11),
Self::Py312 => (3, 12),
Self::Py313 => (3, 13),
}
}

const fn as_str(self) -> &'static str {
match self {
Self::Py37 => "py37",
Self::Py38 => "py38",
Self::Py39 => "py39",
Self::Py310 => "py310",
Self::Py311 => "py311",
Self::Py312 => "py312",
Self::Py313 => "py313",
}
}
}

impl std::fmt::Display for TargetVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}

impl std::fmt::Debug for TargetVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}

/// Configures the search paths for module resolution.
#[derive(Eq, PartialEq, Debug, Clone, Default)]
pub struct SearchPathSettings {
Expand Down
136 changes: 136 additions & 0 deletions crates/red_knot_python_semantic/src/python_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::fmt;

/// Enumeration of all supported Python versions
///
/// TODO: unify with the `PythonVersion` enum in the linter/formatter crates?
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum TargetVersion {
Py37,
#[default]
Py38,
Py39,
Py310,
Py311,
Py312,
Py313,
}

impl TargetVersion {
pub fn major_version(self) -> u8 {
PythonVersion::from(self).major
}

pub fn minor_version(self) -> u8 {
PythonVersion::from(self).minor
}

const fn as_display_str(self) -> &'static str {
match self {
Self::Py37 => "py37",
Self::Py38 => "py38",
Self::Py39 => "py39",
Self::Py310 => "py310",
Self::Py311 => "py311",
Self::Py312 => "py312",
Self::Py313 => "py313",
}
}
}

impl fmt::Display for TargetVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_display_str())
}
}

impl fmt::Debug for TargetVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

/// Generic representation for a Python version.
///
/// Unlike [`TargetVersion`], this does not necessarily represent
/// a Python version that we actually support.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PythonVersion {
pub major: u8,
pub minor: u8,
}

impl TryFrom<(&str, &str)> for PythonVersion {
type Error = std::num::ParseIntError;

fn try_from(value: (&str, &str)) -> Result<Self, Self::Error> {
let (major, minor) = value;
Ok(Self {
major: major.parse()?,
minor: minor.parse()?,
})
}
}

impl fmt::Display for PythonVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let PythonVersion { major, minor } = self;
write!(f, "{major}.{minor}")
}
}

impl From<TargetVersion> for PythonVersion {
fn from(value: TargetVersion) -> Self {
match value {
TargetVersion::Py37 => PythonVersion { major: 3, minor: 7 },
TargetVersion::Py38 => PythonVersion { major: 3, minor: 8 },
TargetVersion::Py39 => PythonVersion { major: 3, minor: 9 },
TargetVersion::Py310 => PythonVersion {
major: 3,
minor: 10,
},
TargetVersion::Py311 => PythonVersion {
major: 3,
minor: 11,
},
TargetVersion::Py312 => PythonVersion {
major: 3,
minor: 12,
},
TargetVersion::Py313 => PythonVersion {
major: 3,
minor: 13,
},
}
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct UnsupportedPythonVersion(PythonVersion);

impl fmt::Display for UnsupportedPythonVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Python version {} is unsupported", self.0)
}
}

impl std::error::Error for UnsupportedPythonVersion {}

impl TryFrom<PythonVersion> for TargetVersion {
type Error = UnsupportedPythonVersion;

fn try_from(value: PythonVersion) -> Result<Self, Self::Error> {
let PythonVersion { major: 3, minor } = value else {
return Err(UnsupportedPythonVersion(value));
};
match minor {
7 => Ok(TargetVersion::Py37),
8 => Ok(TargetVersion::Py38),
9 => Ok(TargetVersion::Py39),
10 => Ok(TargetVersion::Py310),
11 => Ok(TargetVersion::Py311),
12 => Ok(TargetVersion::Py312),
13 => Ok(TargetVersion::Py313),
_ => Err(UnsupportedPythonVersion(value)),
}
}
}
3 changes: 2 additions & 1 deletion crates/red_knot_python_semantic/src/semantic_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ mod tests {
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};

use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings, TargetVersion};
use crate::program::{Program, SearchPathSettings};
use crate::python_version::TargetVersion;
use crate::types::Type;
use crate::{HasTy, SemanticModel};

Expand Down
Loading

0 comments on commit c4e6519

Please sign in to comment.