Compare commits
2 Commits
dcreager/s
...
alex/newty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
446a1ba2e1 | ||
|
|
1c7f3cd2b8 |
@@ -60,6 +60,7 @@ pub use crate::types::ide_support::{
|
||||
use crate::types::infer::infer_unpack_types;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::newtype::{NewTypeInstance, NewTypePseudoClass};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
|
||||
@@ -89,6 +90,7 @@ mod infer;
|
||||
mod instance;
|
||||
mod mro;
|
||||
mod narrow;
|
||||
mod newtype;
|
||||
mod protocol_class;
|
||||
mod signatures;
|
||||
mod special_form;
|
||||
@@ -617,6 +619,18 @@ pub enum Type<'db> {
|
||||
/// The set of Python objects with the given class in their __class__'s method resolution order.
|
||||
/// Construct this variant using the `Type::instance` constructor function.
|
||||
NominalInstance(NominalInstanceType<'db>),
|
||||
/// The set of Python objects that are considered by ty to inhabit a type created by a
|
||||
/// call to `NewType(...)`.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```py
|
||||
/// from typing import NewType
|
||||
/// UserId = NewType('UserId', int) # The call to `NewType` creates a pseudo-class `UserId`
|
||||
/// x = UserId(42) # `x` inhabits the type `UserId`,
|
||||
/// # which is represented in our model with `Type::NewTypeInstance`
|
||||
/// ```
|
||||
NewTypeInstance(NewTypeInstance<'db>),
|
||||
/// The set of Python objects that conform to the interface described by a given protocol.
|
||||
/// Construct this variant using the `Type::instance` constructor function.
|
||||
ProtocolInstance(ProtocolInstanceType<'db>),
|
||||
@@ -2273,6 +2287,15 @@ impl<'db> Type<'db> {
|
||||
C::from_bool(db, !known_instance.is_instance_of(db, instance.class(db)))
|
||||
}
|
||||
|
||||
(Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => {
|
||||
left.is_disjoint_from(db, right)
|
||||
}
|
||||
|
||||
(Type::NewTypeInstance(new_type_instance), other)
|
||||
| (other, Type::NewTypeInstance(new_type_instance)) => new_type_instance
|
||||
.supertype(db)
|
||||
.is_disjoint_from_impl(db, other, visitor),
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
|
||||
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
|
||||
@@ -4963,6 +4986,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
let special_case = match self {
|
||||
Type::NewTypeInstance(newtype_instance) => newtype_instance.tuple_spec(db),
|
||||
Type::NominalInstance(nominal) => nominal.tuple_spec(db),
|
||||
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
|
||||
Some(Cow::Owned(TupleSpec::homogeneous(todo_type!(
|
||||
@@ -5598,6 +5622,9 @@ impl<'db> Type<'db> {
|
||||
.map(Type::NonInferableTypeVar)
|
||||
.unwrap_or(*self))
|
||||
}
|
||||
KnownInstanceType::NewTypePseudoClass(newtype) => {
|
||||
Ok(Type::NewTypeInstance(newtype.to_instance()))
|
||||
}
|
||||
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Deprecated],
|
||||
fallback_type: Type::unknown(),
|
||||
@@ -6686,6 +6713,9 @@ pub enum KnownInstanceType<'db> {
|
||||
|
||||
/// A single instance of `dataclasses.Field`
|
||||
Field(FieldInstance<'db>),
|
||||
|
||||
/// The object returned by a call to `typing.NewType`.
|
||||
NewTypePseudoClass(NewTypePseudoClass<'db>),
|
||||
}
|
||||
|
||||
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
@@ -6741,6 +6771,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
Self::Deprecated(_) => KnownClass::Deprecated,
|
||||
Self::Field(_) => KnownClass::Field,
|
||||
Self::NewTypePseudoClass(_) => KnownClass::NewType,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6791,6 +6822,13 @@ impl<'db> KnownInstanceType<'db> {
|
||||
field.default_type(self.db).display(self.db).fmt(f)?;
|
||||
f.write_str("]")
|
||||
}
|
||||
KnownInstanceType::NewTypePseudoClass(new_type) => {
|
||||
write!(
|
||||
f,
|
||||
"<Pseudo-class object for NewType `{}`>",
|
||||
new_type.name(self.db)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use crate::types::function::{
|
||||
DataclassTransformerParams, FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral,
|
||||
};
|
||||
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
|
||||
use crate::types::newtype::NewTypePseudoClass;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
use crate::types::tuple::{Tuple, TupleLength, TupleType};
|
||||
use crate::types::{
|
||||
@@ -1055,6 +1056,36 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownClass::NewType) => match overload.parameter_types() {
|
||||
[Some(Type::StringLiteral(name)), Some(supertype)] => {
|
||||
let pseudo_class =
|
||||
if let Some(supertype) = supertype.to_class_type(db) {
|
||||
NewTypePseudoClass::from_class(
|
||||
db,
|
||||
&name.value(db),
|
||||
supertype,
|
||||
definition,
|
||||
)
|
||||
} else if let Type::KnownInstance(
|
||||
KnownInstanceType::NewTypePseudoClass(newtype_class),
|
||||
) = supertype
|
||||
{
|
||||
NewTypePseudoClass::from_new_type(
|
||||
db,
|
||||
&name.value(db),
|
||||
*newtype_class,
|
||||
definition,
|
||||
)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
overload.set_return_type(Type::KnownInstance(
|
||||
KnownInstanceType::NewTypePseudoClass(pseudo_class),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
_ => {}
|
||||
},
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ impl<'db> ClassBase<'db> {
|
||||
KnownInstanceType::TypeAliasType(_)
|
||||
| KnownInstanceType::TypeVar(_)
|
||||
| KnownInstanceType::Deprecated(_)
|
||||
| KnownInstanceType::NewTypePseudoClass(_)
|
||||
| KnownInstanceType::Field(_) => None,
|
||||
},
|
||||
|
||||
|
||||
@@ -124,6 +124,9 @@ impl Display for DisplayRepresentation<'_> {
|
||||
(ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings).fmt(f),
|
||||
}
|
||||
}
|
||||
Type::NewTypeInstance(newtype_instance) => {
|
||||
f.write_str(newtype_instance.name(self.db))
|
||||
}
|
||||
Type::ProtocolInstance(protocol) => match protocol.inner {
|
||||
Protocol::FromClass(ClassType::NonGeneric(class)) => {
|
||||
f.write_str(class.name(self.db))
|
||||
|
||||
@@ -10390,6 +10390,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
self.infer_type_expression(&subscript.slice);
|
||||
todo_type!("Generic PEP-695 type alias")
|
||||
}
|
||||
KnownInstanceType::NewTypePseudoClass(newtype) => {
|
||||
self.infer_type_expression(&subscript.slice);
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"NewType `{}` expected no type arguments",
|
||||
newtype.name(self.db())
|
||||
));
|
||||
}
|
||||
Type::unknown()
|
||||
}
|
||||
},
|
||||
Type::Dynamic(DynamicType::Todo(_)) => {
|
||||
self.infer_type_expression(slice);
|
||||
|
||||
120
crates/ty_python_semantic/src/types/newtype.rs
Normal file
120
crates/ty_python_semantic/src/types/newtype.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::{
|
||||
Db,
|
||||
semantic_index::definition::Definition,
|
||||
types::{ClassType, Type, constraints::Constraints, tuple::TupleSpec},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct NewTypePseudoClass<'db>(NewType<'db>);
|
||||
|
||||
impl<'db> NewTypePseudoClass<'db> {
|
||||
pub(crate) fn from_class(
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
supertype: ClassType<'db>,
|
||||
definition: Definition<'db>,
|
||||
) -> Self {
|
||||
Self(NewType::new(
|
||||
db,
|
||||
Name::from(name),
|
||||
NewTypeBase::Class(supertype),
|
||||
definition,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn from_new_type(
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
supertype: NewTypePseudoClass<'db>,
|
||||
definition: Definition<'db>,
|
||||
) -> Self {
|
||||
Self(NewType::new(
|
||||
db,
|
||||
Name::from(name),
|
||||
NewTypeBase::NewType(supertype.0),
|
||||
definition,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn to_instance(&self) -> NewTypeInstance<'db> {
|
||||
NewTypeInstance(self.0)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self, db: &'db dyn Db) -> &'db str {
|
||||
self.0.name(db)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct NewTypeInstance<'db>(NewType<'db>);
|
||||
|
||||
impl<'db> NewTypeInstance<'db> {
|
||||
pub(crate) fn tuple_spec(self, db: &'db dyn Db) -> Option<Cow<TupleSpec<'db>>> {
|
||||
match self.0.supertype(db) {
|
||||
NewTypeBase::Class(class) => Type::instance(db, class).tuple_instance_spec(db),
|
||||
NewTypeBase::NewType(newtype) => NewTypeInstance(newtype).tuple_spec(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn supertype(self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self.0.supertype(db) {
|
||||
NewTypeBase::Class(class) => Type::instance(db, class),
|
||||
NewTypeBase::NewType(newtype) => Type::NewTypeInstance(NewTypeInstance(newtype)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_relation_to<C: Constraints<'db>>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: NewTypeInstance<'db>,
|
||||
) -> C {
|
||||
if self == other {
|
||||
return C::from_bool(db, true);
|
||||
}
|
||||
match self.0.supertype(db) {
|
||||
NewTypeBase::Class(_) => C::from_bool(db, false),
|
||||
NewTypeBase::NewType(newtype) => NewTypeInstance(newtype).has_relation_to(db, other),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from<C: Constraints<'db>>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: NewTypeInstance<'db>,
|
||||
) -> C {
|
||||
C::from_bool(
|
||||
db,
|
||||
!(self.has_relation_to(db, other) || other.has_relation_to(db, self)),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self, db: &'db dyn Db) -> &'db str {
|
||||
self.0.name(db)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
struct NewType<'db> {
|
||||
#[returns(ref)]
|
||||
name: Name,
|
||||
supertype: NewTypeBase<'db>,
|
||||
definition: Definition<'db>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for NewType<'_> {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
enum NewTypeBase<'db> {
|
||||
Class(ClassType<'db>),
|
||||
NewType(NewType<'db>),
|
||||
}
|
||||
Reference in New Issue
Block a user