diff --git a/crates/ty_module_resolver/src/module.rs b/crates/ty_module_resolver/src/module.rs index 837a3592a57e6..57a34f622d703 100644 --- a/crates/ty_module_resolver/src/module.rs +++ b/crates/ty_module_resolver/src/module.rs @@ -406,6 +406,10 @@ impl KnownModule { pub const fn is_functools(self) -> bool { matches!(self, Self::Functools) } + + pub const fn is_dataclasses(self) -> bool { + matches!(self, Self::Dataclasses) + } } impl std::fmt::Display for KnownModule { diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md index 77478861b083d..fcca32fb3320d 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md @@ -109,7 +109,7 @@ from dataclasses import InitVar, dataclass @dataclass class Wrong: - x: InitVar[int, str] # error: [invalid-type-form] "Type qualifier `InitVar` expected exactly 1 argument, got 2" + x: InitVar[int, str] # error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` expected exactly 1 argument, got 2" ``` A trailing comma in a subscript creates a single-element tuple. We need to handle this gracefully @@ -165,5 +165,29 @@ class D: self.x: InitVar[int] = 1 # error: [invalid-type-form] "`InitVar` annotations are not allowed for non-name targets" ``` +### Use as a class + +`InitVar` is a class at runtime. We do not recognise it as such, but we try to avoid emitting errors +on runtime uses of the symbol. + +```py +from dataclasses import InitVar + +x: type = InitVar + +reveal_type(InitVar[int]) # revealed: Any +reveal_type(InitVar(int)) # revealed: Any +reveal_type(InitVar(type=int)) # revealed: Any + +# error: [missing-argument] "No argument provided for required parameter `type`" +reveal_type(InitVar()) # revealed: Any +# error: [unknown-argument] "Argument `wut` does not match any known parameter" +reveal_type(InitVar(str, wut=56)) # revealed: Any + +def test(x: object): + if isinstance(x, InitVar): + reveal_type(x) # revealed: Any +``` + [type annotation grammar]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions [`dataclasses.initvar`]: https://docs.python.org/3/library/dataclasses.html#dataclasses.InitVar diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8da94b19e605e..c33e82aa3c3c7 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3736,6 +3736,13 @@ impl<'db> Type<'db> { } }, + Type::SpecialForm(SpecialFormType::TypeQualifier(TypeQualifier::InitVar)) => { + let parameter = Parameter::positional_or_keyword(Name::new_static("type")) + .with_annotated_type(Type::any()); + let signature = Signature::new(Parameters::new(db, [parameter]), Type::any()); + Binding::single(self, signature).into() + } + Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => { // Note that for objects that have a (possibly not callable!) `__call__` attribute, // we will get the signature of the `__call__` attribute, but will pass in the type @@ -4957,12 +4964,6 @@ impl<'db> Type<'db> { let ty = match class.known(db) { Some(KnownClass::Complex) => KnownUnion::Complex.to_type(db), Some(KnownClass::Float) => KnownUnion::Float.to_type(db), - Some(KnownClass::InitVar) => { - return Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![InvalidTypeExpression::InitVar], - fallback_type: Type::unknown(), - }); - } _ => Type::instance(db, class.default_specialization(db)), }; Ok(ty) @@ -6678,7 +6679,6 @@ enum InvalidTypeExpression<'db> { /// Type qualifiers that are invalid in type expressions, /// and which would require exactly one argument even if they appeared in an annotation expression TypeQualifierRequiresOneArgument(TypeQualifier), - InitVar, /// `typing.Self` cannot be used in `@staticmethod` definitions. TypingSelfInStaticMethod, /// `typing.Self` cannot be used in metaclass definitions. @@ -6752,10 +6752,6 @@ impl<'db> InvalidTypeExpression<'db> { "Type qualifier `{qualifier}` is not allowed in type expressions \ (only in annotation expressions, and only with exactly one argument)", ), - InvalidTypeExpression::InitVar => f.write_str( - "Type qualifier `dataclasses.InitVar` is not allowed in type expressions \ - (only in annotation expressions, and only with exactly one argument)", - ), InvalidTypeExpression::TypingSelfInStaticMethod => { f.write_str("`Self` cannot be used in a static method") } diff --git a/crates/ty_python_semantic/src/types/class/known.rs b/crates/ty_python_semantic/src/types/class/known.rs index f053b27ffff4c..f2aab0dd9b3c9 100644 --- a/crates/ty_python_semantic/src/types/class/known.rs +++ b/crates/ty_python_semantic/src/types/class/known.rs @@ -125,7 +125,6 @@ pub enum KnownClass { // dataclasses Field, KwOnly, - InitVar, // _typeshed._type_checker_internals NamedTupleFallback, NamedTupleLike, @@ -243,7 +242,6 @@ impl KnownClass { | Self::Deprecated | Self::Field | Self::KwOnly - | Self::InitVar | Self::NamedTupleFallback | Self::NamedTupleLike | Self::ConstraintSet @@ -334,7 +332,6 @@ impl KnownClass { | KnownClass::NotImplementedType | KnownClass::Field | KnownClass::KwOnly - | KnownClass::InitVar | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike | KnownClass::ConstraintSet @@ -425,7 +422,6 @@ impl KnownClass { | KnownClass::NotImplementedType | KnownClass::Field | KnownClass::KwOnly - | KnownClass::InitVar | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike | KnownClass::ConstraintSet @@ -515,7 +511,6 @@ impl KnownClass { | KnownClass::NotImplementedType | KnownClass::Field | KnownClass::KwOnly - | KnownClass::InitVar | KnownClass::TypedDictFallback | KnownClass::NamedTupleLike | KnownClass::NamedTupleFallback @@ -617,7 +612,6 @@ impl KnownClass { | Self::UnionType | Self::Field | Self::KwOnly - | Self::InitVar | Self::NamedTupleFallback | Self::ConstraintSet | Self::GenericContext @@ -720,8 +714,7 @@ impl KnownClass { | KnownClass::Path | KnownClass::ConstraintSet | KnownClass::GenericContext - | KnownClass::Specialization - | KnownClass::InitVar => false, + | KnownClass::Specialization => false, KnownClass::NamedTupleFallback | KnownClass::TypedDictFallback => true, } } @@ -831,7 +824,6 @@ impl KnownClass { } Self::Field => "Field", Self::KwOnly => "KW_ONLY", - Self::InitVar => "InitVar", Self::NamedTupleFallback => "NamedTupleFallback", Self::NamedTupleLike => "NamedTupleLike", Self::ConstraintSet => "ConstraintSet", @@ -1212,7 +1204,7 @@ impl KnownClass { | Self::DefaultDict | Self::Deque | Self::OrderedDict => KnownModule::Collections, - Self::Field | Self::KwOnly | Self::InitVar => KnownModule::Dataclasses, + Self::Field | Self::KwOnly => KnownModule::Dataclasses, Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals, Self::NamedTupleLike | Self::ConstraintSet @@ -1297,7 +1289,6 @@ impl KnownClass { | Self::NewType | Self::Field | Self::KwOnly - | Self::InitVar | Self::Iterable | Self::Iterator | Self::AsyncIterator @@ -1393,7 +1384,6 @@ impl KnownClass { | Self::NewType | Self::Field | Self::KwOnly - | Self::InitVar | Self::Iterable | Self::Iterator | Self::AsyncIterator @@ -1508,7 +1498,6 @@ impl KnownClass { } "Field" => &[Self::Field], "KW_ONLY" => &[Self::KwOnly], - "InitVar" => &[Self::InitVar], "NamedTupleFallback" => &[Self::NamedTupleFallback], "NamedTupleLike" => &[Self::NamedTupleLike], "ConstraintSet" => &[Self::ConstraintSet], @@ -1585,7 +1574,6 @@ impl KnownClass { | Self::BuiltinFunctionType | Self::Field | Self::KwOnly - | Self::InitVar | Self::NamedTupleFallback | Self::TypedDictFallback | Self::TypeVar diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index a1d4ccb10b266..03b1a1aeafd8f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7,6 +7,7 @@ use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity}; use ruff_db::files::File; use ruff_db::parsed::ParsedModuleRef; use ruff_db::source::source_text; +use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::name::Name; use ruff_python_ast::{ self as ast, AnyNodeRef, ArgOrKeyword, ArgumentsSourceOrder, ExprContext, HasNodeIndex, @@ -4258,20 +4259,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; if is_pep_613_type_alias { - let is_valid_special_form = |ty: Type<'db>| match ty { - Type::SpecialForm(SpecialFormType::TypeQualifier(_)) => false, - Type::ClassLiteral(literal) => { - !literal.is_known(self.db(), KnownClass::InitVar) - } - _ => true, - }; - - let is_invalid = match value { - ast::Expr::Subscript(sub) => { - !is_valid_special_form(self.expression_type(&sub.value)) - } - _ => !is_valid_special_form(self.expression_type(value)), - }; + let is_invalid = matches!( + self.expression_type(map_subscript(value)), + Type::SpecialForm(SpecialFormType::TypeQualifier(_)) + ); if is_invalid && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, value) diff --git a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs index 0da1a370cce5c..b200ac885d291 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs @@ -11,8 +11,7 @@ use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, }; use crate::types::{ - KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifier, - TypeQualifiers, todo_type, + SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifier, TypeQualifiers, todo_type, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -100,6 +99,20 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) -> TypeAndQualifiers<'db> { let special_case = match ty { Type::SpecialForm(special_form) => match special_form { + SpecialFormType::TypeQualifier(TypeQualifier::InitVar) => { + if let Some(builder) = + builder.context.report_lint(&INVALID_TYPE_FORM, annotation) + { + builder.into_diagnostic( + "`InitVar` may not be used without a type argument", + ); + } + Some(TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::INIT_VAR, + )) + } SpecialFormType::TypeQualifier(qualifier) => Some(TypeAndQualifiers::new( Type::unknown(), TypeOrigin::Declared, @@ -125,19 +138,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { SpecialFormType::TypeAlias, ))) } - Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => { - if let Some(builder) = - builder.context.report_lint(&INVALID_TYPE_FORM, annotation) - { - builder - .into_diagnostic("`InitVar` may not be used without a type argument"); - } - Some(TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::INIT_VAR, - )) - } _ => None, }; @@ -360,41 +360,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ), ), }, - Type::ClassLiteral(class) if class.is_known(self.db(), KnownClass::InitVar) => { - let arguments = if let ast::Expr::Tuple(tuple) = slice { - &*tuple.elts - } else { - std::slice::from_ref(slice) - }; - let type_and_qualifiers = if let [argument] = arguments { - self.infer_annotation_expression_impl( - argument, - PEP613Policy::Disallowed, - ) - .with_qualifier(TypeQualifiers::INIT_VAR) - } else { - for element in arguments { - self.infer_annotation_expression_impl( - element, - PEP613Policy::Disallowed, - ); - } - if let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - let num_arguments = arguments.len(); - builder.into_diagnostic(format_args!( - "Type qualifier `InitVar` expected exactly 1 argument, \ - got {num_arguments}", - )); - } - TypeAndQualifiers::declared(Type::unknown()) - }; - if slice.is_tuple_expr() { - self.store_expression_type(slice, type_and_qualifiers.inner_type()); - } - type_and_qualifiers - } _ => TypeAndQualifiers::declared( self.infer_subscript_type_expression_no_store(subscript, slice, value_ty), ), diff --git a/crates/ty_python_semantic/src/types/infer/builder/class.rs b/crates/ty_python_semantic/src/types/infer/builder/class.rs index 49932f7d0c748..a5563ddda4a03 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/class.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/class.rs @@ -10,6 +10,7 @@ use crate::{ builder::{DeclaredAndInferredType, DeferredExpressionState}, }, signatures::ParameterForm, + special_form::TypeQualifier, }, }; use ruff_python_ast::{self as ast, helpers::any_over_expr}; @@ -166,9 +167,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let maybe_known_class = KnownClass::try_from_file_and_name(db, self.file(), name); + let known_module = || file_to_module(db, self.file()).and_then(|module| module.known(db)); let in_typing_module = || { matches!( - file_to_module(db, self.file()).and_then(|module| module.known(db)), + known_module(), Some(KnownModule::Typing | KnownModule::TypingExtensions) ) }; @@ -178,6 +180,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::SpecialForm(SpecialFormType::NamedTuple) } (None, "Any") if in_typing_module() => Type::SpecialForm(SpecialFormType::Any), + (None, "InitVar") if known_module() == Some(KnownModule::Dataclasses) => { + Type::SpecialForm(SpecialFormType::TypeQualifier(TypeQualifier::InitVar)) + } _ => Type::from(StaticClassLiteral::new( db, name.id.clone(), diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index af5dbca09ab44..27be5ee2ec0cb 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -5,9 +5,8 @@ use super::{DeferredExpressionState, TypeInferenceBuilder}; use crate::semantic_index::scope::ScopeKind; use crate::types::diagnostic::{ self, INVALID_TYPE_FORM, NOT_SUBSCRIPTABLE, UNBOUND_TYPE_VARIABLE, UNSUPPORTED_OPERATOR, - add_type_expression_reference_link, note_py_version_too_old_for_pep_604, - report_invalid_argument_number_to_special_form, report_invalid_arguments_to_callable, - report_invalid_concatenate_last_arg, + note_py_version_too_old_for_pep_604, report_invalid_argument_number_to_special_form, + report_invalid_arguments_to_callable, report_invalid_concatenate_last_arg, }; use crate::types::infer::InferenceFlags; use crate::types::signatures::{ConcatenateTail, Signature}; @@ -684,17 +683,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::ClassLiteral(class_literal) => match class_literal.known(self.db()) { Some(KnownClass::Tuple) => Type::tuple(self.infer_tuple_type_expression(subscript)), Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice), - Some(KnownClass::InitVar) => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - let diagnostic = builder.into_diagnostic( - "Type qualifier `dataclasses.InitVar` is not allowed in type \ - expressions (only in annotation expressions)", - ); - add_type_expression_reference_link(diagnostic); - } - self.infer_expression(slice, TypeContext::default()); - Type::unknown() - } _ => self.infer_subscript_type_expression(subscript, value_ty), }, _ => self.infer_subscript_type_expression(subscript, value_ty), diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index d73aeee6ba7cc..122a712214425 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -11,6 +11,7 @@ use crate::subscript::PyIndex; use crate::types::enums::{enum_member_literals, enum_metadata}; use crate::types::function::KnownFunction; use crate::types::infer::{ExpressionInference, infer_same_file_expression_type}; +use crate::types::special_form::TypeQualifier; use crate::types::typed_dict::{ TypedDictField, TypedDictFieldBuilder, TypedDictSchema, TypedDictType, }; @@ -274,6 +275,11 @@ impl ClassInfoConstraintFunction { SpecialFormType::Callable => (self == ClassInfoConstraintFunction::IsInstance) .then(|| Type::Callable(CallableType::unknown(db)).top_materialization(db)), + // `InitVar` is a class at runtime, so can be used in `isinstance()`, + // but we can't represent internally the type that we should narrow to after an `isinstance()` check, + // so just intersect with `Any` in those cases. + SpecialFormType::TypeQualifier(TypeQualifier::InitVar) => Some(Type::any()), + _ => None, }, diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index 2e7a6e38bf15a..df71632e1d19d 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -154,8 +154,9 @@ impl SpecialFormType { | Self::RegularCallableTypeOf | Self::Unknown | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::TypeQualifier(_) => KnownClass::SpecialForm, + | Self::AlwaysFalsy => KnownClass::SpecialForm, + + Self::TypeQualifier(qualifier) => qualifier.class(), // Typeshed says it's an instance of `_SpecialForm`, // but then we wouldn't recognise things like `issubclass(`X, Protocol)` @@ -239,6 +240,7 @@ impl SpecialFormType { List, Dict, FrozenSet, + InitVar, Set, ChainMap, Counter, @@ -306,6 +308,7 @@ impl SpecialFormType { TypeQualifier::ReadOnly => Self::ReadOnly, TypeQualifier::Required => Self::Required, TypeQualifier::NotRequired => Self::NotRequired, + TypeQualifier::InitVar => Self::InitVar, }, } } @@ -371,6 +374,7 @@ impl SpecialFormType { SpecialFormTypeBuilder::NotRequired => { Self::TypeQualifier(TypeQualifier::NotRequired) } + SpecialFormTypeBuilder::InitVar => Self::TypeQualifier(TypeQualifier::InitVar), }) } @@ -380,8 +384,8 @@ impl SpecialFormType { /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. pub(super) fn check_module(self, module: KnownModule) -> bool { match self { - Self::TypeQualifier(TypeQualifier::ClassVar) - | Self::LegacyStdlibAlias(_) + Self::TypeQualifier(qualifier) => qualifier.check_module(module), + Self::LegacyStdlibAlias(_) | Self::Optional | Self::Union | Self::NoReturn @@ -394,12 +398,6 @@ impl SpecialFormType { | Self::Literal | Self::LiteralString | Self::Never - | Self::TypeQualifier( - TypeQualifier::Final - | TypeQualifier::Required - | TypeQualifier::NotRequired - | TypeQualifier::ReadOnly, - ) | Self::Concatenate | Self::Unpack | Self::TypeAlias @@ -460,6 +458,8 @@ impl SpecialFormType { | Self::Tuple | Self::Type => false, + Self::TypeQualifier(qualifier) => qualifier.is_callable(), + // All other special forms are also not callable Self::Annotated | Self::Literal @@ -480,7 +480,6 @@ impl SpecialFormType { | Self::RegularCallableTypeOf | Self::Callable | Self::TypingSelf - | Self::TypeQualifier(_) | Self::Concatenate | Self::Unpack | Self::TypeAlias @@ -496,6 +495,8 @@ impl SpecialFormType { /// to `issubclass()` and `isinstance()` calls. pub(super) const fn is_valid_isinstance_target(self) -> bool { match self { + Self::TypeQualifier(qualifier) => qualifier.is_valid_isinstance_target(), + Self::Callable | Self::LegacyStdlibAlias(_) | Self::Tuple @@ -509,7 +510,6 @@ impl SpecialFormType { | Self::Bottom | Self::CallableTypeOf | Self::RegularCallableTypeOf - | Self::TypeQualifier(_) | Self::Concatenate | Self::Intersection | Self::Literal @@ -536,6 +536,7 @@ impl SpecialFormType { /// Return the name of the symbol at runtime pub(super) const fn name(self) -> &'static str { match self { + SpecialFormType::TypeQualifier(qualifier) => qualifier.name(), SpecialFormType::Any => "Any", SpecialFormType::Annotated => "Annotated", SpecialFormType::Literal => "Literal", @@ -547,13 +548,9 @@ impl SpecialFormType { SpecialFormType::Tuple => "Tuple", SpecialFormType::Type => "Type", SpecialFormType::TypingSelf => "Self", - SpecialFormType::TypeQualifier(TypeQualifier::Final) => "Final", - SpecialFormType::TypeQualifier(TypeQualifier::ClassVar) => "ClassVar", SpecialFormType::Callable => "Callable", SpecialFormType::Concatenate => "Concatenate", SpecialFormType::Unpack => "Unpack", - SpecialFormType::TypeQualifier(TypeQualifier::Required) => "Required", - SpecialFormType::TypeQualifier(TypeQualifier::NotRequired) => "NotRequired", SpecialFormType::TypeAlias => "TypeAlias", SpecialFormType::TypeGuard => "TypeGuard", SpecialFormType::TypedDict => "TypedDict", @@ -567,7 +564,6 @@ impl SpecialFormType { SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias::Deque) => "Deque", SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias::ChainMap) => "ChainMap", SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias::OrderedDict) => "OrderedDict", - SpecialFormType::TypeQualifier(TypeQualifier::ReadOnly) => "ReadOnly", SpecialFormType::Unknown => "Unknown", SpecialFormType::AlwaysTruthy => "AlwaysTruthy", SpecialFormType::AlwaysFalsy => "AlwaysFalsy", @@ -598,7 +594,6 @@ impl SpecialFormType { | SpecialFormType::Tuple | SpecialFormType::Type | SpecialFormType::TypingSelf - | SpecialFormType::TypeQualifier(_) | SpecialFormType::Callable | SpecialFormType::Concatenate | SpecialFormType::Unpack @@ -613,6 +608,8 @@ impl SpecialFormType { &[KnownModule::Typing, KnownModule::TypingExtensions] } + SpecialFormType::TypeQualifier(qualifier) => qualifier.definition_modules(), + SpecialFormType::Unknown | SpecialFormType::AlwaysTruthy | SpecialFormType::AlwaysFalsy @@ -791,22 +788,10 @@ impl SpecialFormType { SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())), SpecialFormType::Callable => Ok(Type::Callable(CallableType::unknown(db))), SpecialFormType::LegacyStdlibAlias(alias) => Ok(alias.aliased_class().to_instance(db)), - SpecialFormType::TypeQualifier(qualifier) => { - let err = match qualifier { - TypeQualifier::Final | TypeQualifier::ClassVar => { - InvalidTypeExpression::TypeQualifier(qualifier) - } - TypeQualifier::ReadOnly - | TypeQualifier::NotRequired - | TypeQualifier::Required => { - InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) - } - }; - Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec_inline![err], - fallback_type: Type::unknown(), - }) - } + SpecialFormType::TypeQualifier(qualifier) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![qualifier.in_type_expression()], + fallback_type: Type::unknown(), + }), } } } @@ -879,6 +864,85 @@ pub enum TypeQualifier { ClassVar, Required, NotRequired, + /// The symbol `dataclasses.InitVar`. + /// + /// Typeshed defines this symbol as a class, which is accurate, but we represent it as a + /// special form internally as it's more similar semantically to `ClassVar`/`Final` etc. + /// than to a regular generic class. + InitVar, +} + +impl TypeQualifier { + const fn is_callable(self) -> bool { + match self { + Self::InitVar => true, + Self::ReadOnly | Self::Final | Self::ClassVar | Self::Required | Self::NotRequired => { + false + } + } + } + + const fn check_module(self, module: KnownModule) -> bool { + match self { + Self::InitVar => module.is_dataclasses(), + Self::ClassVar => module.is_typing(), + Self::ReadOnly | Self::Final | Self::Required | Self::NotRequired => { + matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) + } + } + } + + const fn is_valid_isinstance_target(self) -> bool { + match self { + Self::InitVar => true, + Self::ReadOnly | Self::Final | Self::ClassVar | Self::Required | Self::NotRequired => { + false + } + } + } + + const fn name(self) -> &'static str { + match self { + Self::ReadOnly => "ReadOnly", + Self::Final => "Final", + Self::ClassVar => "ClassVar", + Self::Required => "Required", + Self::NotRequired => "NotRequired", + Self::InitVar => "InitVar", + } + } + + const fn definition_modules(self) -> &'static [KnownModule] { + match self { + Self::InitVar => &[KnownModule::Dataclasses], + Self::ClassVar | Self::ReadOnly | Self::Final | Self::Required | Self::NotRequired => { + &[KnownModule::Typing, KnownModule::TypingExtensions] + } + } + } + + const fn class(self) -> KnownClass { + match self { + Self::ReadOnly | Self::Final | Self::ClassVar | Self::Required | Self::NotRequired => { + KnownClass::SpecialForm + } + Self::InitVar => KnownClass::Type, + } + } + + const fn in_type_expression(self) -> InvalidTypeExpression<'static> { + match self { + TypeQualifier::Final | TypeQualifier::ClassVar => { + InvalidTypeExpression::TypeQualifier(self) + } + TypeQualifier::ReadOnly + | TypeQualifier::NotRequired + | TypeQualifier::InitVar + | TypeQualifier::Required => { + InvalidTypeExpression::TypeQualifierRequiresOneArgument(self) + } + } + } } impl From for SpecialFormType { @@ -895,6 +959,7 @@ impl From for TypeQualifiers { TypeQualifier::ClassVar => TypeQualifiers::CLASS_VAR, TypeQualifier::Required => TypeQualifiers::REQUIRED, TypeQualifier::NotRequired => TypeQualifiers::NOT_REQUIRED, + TypeQualifier::InitVar => TypeQualifiers::INIT_VAR, } } } diff --git a/crates/ty_python_semantic/src/types/subscript.rs b/crates/ty_python_semantic/src/types/subscript.rs index 733802a73fd7f..4ebe11e044ae5 100644 --- a/crates/ty_python_semantic/src/types/subscript.rs +++ b/crates/ty_python_semantic/src/types/subscript.rs @@ -7,6 +7,7 @@ use ruff_python_ast as ast; use crate::Db; use crate::subscript::{PyIndex, PySlice}; +use crate::types::special_form::TypeQualifier; use super::call::{Bindings, CallArguments, CallDunderError, CallErrorKind}; use super::class::KnownClass; @@ -694,6 +695,13 @@ impl<'db> Type<'db> { Some(Ok(Type::Dynamic(DynamicType::TodoUnpack))) } + (Type::SpecialForm(SpecialFormType::TypeQualifier(TypeQualifier::InitVar)), _) => { + // Subscripting `InitVar` gives you (bizarrely) an instance of `InitVar`, + // which isn't representable in our model because we don't recognise there as being + // an `InitVar` class at all. This doesn't really matter that much, so just infer `Any` here. + Some(Ok(Type::any())) + } + (Type::SpecialForm(special_form), _) if special_form.class().is_special_form() => { Some(Ok(todo_type!("Inference of subscript on special form"))) }