Fix problem with fallback classes (again)

This commit is contained in:
David Peter 2025-09-24 13:31:26 +02:00
parent 3c097bfd59
commit e1b548eaa2
5 changed files with 124 additions and 27 deletions

View file

@ -277,7 +277,6 @@ reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
person = Person("Alice", 42)
# error: [invalid-argument-type] "Argument to bound method `_asdict` is incorrect: Expected `NamedTupleFallback`, found `Person`"
reveal_type(person._asdict()) # revealed: dict[str, Any]
reveal_type(person._replace(name="Bob")) # revealed: Person
```

View file

@ -5356,12 +5356,10 @@ impl<'db> Type<'db> {
Some(generic_context) => (
Some(class),
Some(generic_context),
Type::from(class.apply_specialization(db, |_| {
// It is important that identity_specialization specializes the class with
// _inferable_ typevars, so that our specialization inference logic will
// try to find a specialization for them.
generic_context.identity_specialization(db)
})),
// It is important that identity_specialization specializes the class with
// _inferable_ typevars, so that our specialization inference logic will
// try to find a specialization for them.
Type::from(class.identity_specialization(db, Type::TypeVar)),
),
_ => (None, None, self),
},

View file

@ -1527,6 +1527,18 @@ impl<'db> ClassLiteral<'db> {
})
}
/// Returns a specialization of this class where each typevar is mapped to itself. The second
/// parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on the use case.
pub(crate) fn identity_specialization(
self,
db: &'db dyn Db,
typevar_to_type: impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
) -> ClassType<'db> {
self.apply_specialization(db, |generic_context| {
generic_context.identity_specialization(db, typevar_to_type)
})
}
/// Return an iterator over the inferred types of this class's *explicit* bases.
///
/// Note that any class (except for `object`) that has no explicit
@ -2625,7 +2637,14 @@ impl<'db> ClassLiteral<'db> {
.map_type(|ty|
ty.apply_type_mapping(
db,
&TypeMapping::ReplaceSelf {new_upper_bound: determine_upper_bound(db, self, specialization, ClassBase::is_typed_dict) }
&TypeMapping::ReplaceSelf {
new_upper_bound: determine_upper_bound(
db,
self,
specialization,
ClassBase::is_typed_dict
)
}
)
)
}
@ -4174,6 +4193,90 @@ impl KnownClass {
}
}
/// Return `true` if this class is a typeshed fallback class which is used to provide attributes and
/// methods for another type (e.g. `NamedTupleFallback` for actual `NamedTuple`s). These fallback
/// classes need special treatment in some places. For example, implicit usages of `Self` should not
/// be eagerly replaced with the fallback class itself. Instead, `Self` should eventually be treated
/// as referring to the destination type (e.g. the actual `NamedTuple`).
pub(crate) const fn is_fallback_class(self) -> bool {
match self {
KnownClass::Bool
| KnownClass::Object
| KnownClass::Bytes
| KnownClass::Bytearray
| KnownClass::Type
| KnownClass::Int
| KnownClass::Float
| KnownClass::Complex
| KnownClass::Str
| KnownClass::List
| KnownClass::Tuple
| KnownClass::Set
| KnownClass::FrozenSet
| KnownClass::Dict
| KnownClass::Slice
| KnownClass::Property
| KnownClass::BaseException
| KnownClass::Exception
| KnownClass::BaseExceptionGroup
| KnownClass::ExceptionGroup
| KnownClass::Staticmethod
| KnownClass::Classmethod
| KnownClass::Super
| KnownClass::Enum
| KnownClass::EnumType
| KnownClass::Auto
| KnownClass::Member
| KnownClass::Nonmember
| KnownClass::StrEnum
| KnownClass::ABCMeta
| KnownClass::GenericAlias
| KnownClass::ModuleType
| KnownClass::FunctionType
| KnownClass::MethodType
| KnownClass::MethodWrapperType
| KnownClass::WrapperDescriptorType
| KnownClass::UnionType
| KnownClass::GeneratorType
| KnownClass::AsyncGeneratorType
| KnownClass::CoroutineType
| KnownClass::NotImplementedType
| KnownClass::BuiltinFunctionType
| KnownClass::EllipsisType
| KnownClass::NoneType
| KnownClass::Awaitable
| KnownClass::Generator
| KnownClass::Deprecated
| KnownClass::StdlibAlias
| KnownClass::SpecialForm
| KnownClass::TypeVar
| KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs
| KnownClass::ProtocolMeta
| KnownClass::TypeVarTuple
| KnownClass::TypeAliasType
| KnownClass::NoDefaultType
| KnownClass::NewType
| KnownClass::SupportsIndex
| KnownClass::Iterable
| KnownClass::Iterator
| KnownClass::ChainMap
| KnownClass::Counter
| KnownClass::DefaultDict
| KnownClass::Deque
| KnownClass::OrderedDict
| KnownClass::VersionInfo
| KnownClass::Field
| KnownClass::KwOnly
| KnownClass::NamedTupleLike
| KnownClass::Template
| KnownClass::ConstraintSet
| KnownClass::InitVar => false,
KnownClass::NamedTupleFallback | KnownClass::TypedDictFallback => true,
}
}
pub(crate) fn name(self, db: &dyn Db) -> &'static str {
match self {
Self::Bool => "bool",

View file

@ -107,28 +107,21 @@ pub(crate) fn get_self_type<'db>(
typevar_binding_context: Option<Definition<'db>>,
class: ClassLiteral<'db>,
) -> Option<BoundTypeVarInstance<'db>> {
// TODO: remove duplication with Type::in_type_expression
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db));
let upper_bound = Type::instance(
db,
class.apply_specialization(db, |generic_context| {
let types = generic_context
.variables(db)
.iter()
.map(|typevar| Type::NonInferableTypeVar(*typevar));
generic_context.specialize(db, types.collect())
}),
);
let class_definition = class.definition(db);
let typevar = TypeVarInstance::new(
db,
ast::name::Name::new_static("Self"),
Some(class_definition),
Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
Some(
TypeVarBoundOrConstraints::UpperBound(Type::instance(
db,
class.identity_specialization(db, Type::NonInferableTypeVar),
))
.into(),
),
// According to the [spec], we can consider `Self`
// equivalent to an invariant type variable
// [spec]: https://typing.python.org/en/latest/spec/generics.html#self
@ -334,14 +327,17 @@ impl<'db> GenericContext<'db> {
}
/// Returns a specialization of this generic context where each typevar is mapped to itself.
/// (And in particular, to an _inferable_ version of itself, since this will be used in our
/// class constructor invocation machinery to infer a specialization for the class from the
/// arguments passed to its constructor.)
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
/// The second parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on
/// the use case.
pub(crate) fn identity_specialization(
self,
db: &'db dyn Db,
typevar_to_type: impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
) -> Specialization<'db> {
let types = self
.variables(db)
.iter()
.map(|typevar| Type::TypeVar(*typevar))
.map(|typevar| typevar_to_type(*typevar))
.collect();
self.specialize(db, types)
}

View file

@ -1285,6 +1285,7 @@ impl<'db> Parameters<'db> {
});
let implicit_annotation = if !method_has_self_in_generic_context
&& class.is_not_generic()
&& !class.known(db).is_some_and(KnownClass::is_fallback_class)
{
Type::instance(db, class)
} else {