Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/pyrefly_types/src/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ mod tests {
assert_eq!(
Annotation {
qualifiers: Vec::new(),
ty: Some(Type::None)
ty: Some(Type::None),
}
.to_string(),
"None"
);
assert_eq!(
Annotation {
qualifiers: vec![Qualifier::Required, Qualifier::ReadOnly],
ty: None
ty: None,
}
.to_string(),
"Required[ReadOnly]"
Expand Down
3 changes: 3 additions & 0 deletions crates/pyrefly_types/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use starlark_map::smallmap;

use crate::callable::Function;
use crate::class::Class;
#[cfg(test)]
use crate::keywords::RangeConstraints;
use crate::literal::Lit;
use crate::tuple::Tuple;
use crate::types::AnyStyle;
Expand Down Expand Up @@ -1113,6 +1115,7 @@ pub mod tests {
Type::None,
TypeAliasStyle::LegacyImplicit,
Vec::new(),
RangeConstraints::default(),
)));
let wrapped = Type::tuple(vec![alias.clone()]);
let type_of = Type::type_form(alias.clone());
Expand Down
31 changes: 31 additions & 0 deletions crates/pyrefly_types/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,35 @@ impl KwCall {
}
}

/// Numeric range constraints extracted from metadata such as `annotated_types.Gt`.
///
/// These bounds are intentionally conservative:
/// * Only constraints that can be recovered statically (e.g. literal arguments to
/// `annotated_types.Gt/Ge/Lt/Le` or equivalent Field keywords) are recorded here.
/// * The information is used primarily to vet class-body defaults; call sites that
/// pass dynamic values are still enforced at runtime by Pydantic.
/// * When the metadata cannot be evaluated to a literal (for example, it depends on
/// a value computed at runtime), the corresponding entry remains `None` and the
/// solver defers to runtime validation.
///
/// In other words, `RangeConstraints` lets us catch the obvious mismatches during
/// static analysis without attempting to replicate every bit of Pydantic’s runtime
/// behaviour.
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Visit, VisitMut, TypeEq)]
pub struct RangeConstraints {
pub lt: Option<Type>,
pub gt: Option<Type>,
pub ge: Option<Type>,
pub le: Option<Type>,
}

impl RangeConstraints {
pub fn is_empty(&self) -> bool {
self.lt.is_none() && self.gt.is_none() && self.ge.is_none() && self.le.is_none()
}
}

/// Parameters to `typing.dataclass_transform`.
/// See https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-transform-parameters.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -132,6 +161,7 @@ pub struct DataclassFieldKeywords {
pub lt: Option<Type>,
pub gt: Option<Type>,
pub ge: Option<Type>,
pub le: Option<Type>,
/// Whether we should strictly evaluate the type of the field
pub strict: Option<bool>,
/// If a converter callable is passed in, its first positional parameter
Expand Down Expand Up @@ -160,6 +190,7 @@ impl DataclassFieldKeywords {
lt: None,
gt: None,
ge: None,
le: None,
converter_param: None,
strict: None,
}
Expand Down
15 changes: 14 additions & 1 deletion crates/pyrefly_types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::class::ClassKind;
use crate::class::ClassType;
use crate::keywords::DataclassTransformKeywords;
use crate::keywords::KwCall;
use crate::keywords::RangeConstraints;
use crate::literal::Lit;
use crate::module::ModuleType;
use crate::param_spec::ParamSpec;
Expand Down Expand Up @@ -325,22 +326,34 @@ pub struct TypeAlias {
ty: Box<Type>,
pub style: TypeAliasStyle,
annotated_metadata: Box<[Type]>,
range_constraints: RangeConstraints,
}

impl TypeAlias {
pub fn new(name: Name, ty: Type, style: TypeAliasStyle, annotated_metadata: Vec<Type>) -> Self {
pub fn new(
name: Name,
ty: Type,
style: TypeAliasStyle,
annotated_metadata: Vec<Type>,
range_constraints: RangeConstraints,
) -> Self {
Self {
name: Box::new(name),
ty: Box::new(ty),
style,
annotated_metadata: annotated_metadata.into_boxed_slice(),
range_constraints,
}
}

pub fn annotated_metadata(&self) -> &[Type] {
&self.annotated_metadata
}

pub fn range_constraints(&self) -> &RangeConstraints {
&self.range_constraints
}

/// Gets the type contained within the type alias for use in a value
/// position - for example, for a function call or attribute access.
pub fn as_value(&self, stdlib: &Stdlib) -> Type {
Expand Down
Loading