diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 292b15ec32ace..66fc452d8ac79 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -1443,7 +1443,7 @@ from typing import List, Dict # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" InvalidList = List[1] -# error: [invalid-type-form] "`typing.List` requires exactly one argument" +# error: [invalid-type-form] "`typing.List` requires exactly 1 argument, got 2" ListTooManyArgs = List[int, str] # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" @@ -1452,10 +1452,10 @@ InvalidDict1 = Dict[1, str] # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" InvalidDict2 = Dict[str, 2] -# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 1" +# error: [invalid-type-form] "`typing.Dict` requires exactly 2 arguments, got 1" DictTooFewArgs = Dict[str] -# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 3" +# error: [invalid-type-form] "`typing.Dict` requires exactly 2 arguments, got 3" DictTooManyArgs = Dict[str, int, float] def _( @@ -1470,7 +1470,7 @@ def _( reveal_type(list_too_many_args) # revealed: list[Unknown] reveal_type(invalid_dict1) # revealed: dict[Unknown, str] reveal_type(invalid_dict2) # revealed: dict[str, Unknown] - reveal_type(dict_too_few_args) # revealed: dict[str, Unknown] + reveal_type(dict_too_few_args) # revealed: dict[Unknown, Unknown] reveal_type(dict_too_many_args) # revealed: dict[Unknown, Unknown] ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a4d17e190a163..e737d36469b83 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1,5 +1,4 @@ use compact_str::ToCompactString; -use infer::nearest_enclosing_class; use itertools::{Either, Itertools}; use ruff_diagnostics::{Edit, Fix}; use rustc_hash::FxHashMap; @@ -63,8 +62,7 @@ use crate::types::function::{ FunctionType, KnownFunction, }; use crate::types::generics::{ - ApplySpecialization, InferableTypeVars, Specialization, bind_typevar, typing_self, - walk_generic_context, + ApplySpecialization, InferableTypeVars, Specialization, bind_typevar, walk_generic_context, }; pub(crate) use crate::types::generics::{GenericContext, SpecializationBuilder}; use crate::types::mro::{Mro, MroIterator, StaticMroError}; @@ -75,6 +73,7 @@ pub(crate) use crate::types::narrow::{ use crate::types::newtype::NewType; pub(crate) use crate::types::signatures::{Parameter, Parameters}; use crate::types::signatures::{ParameterForm, walk_signature}; +use crate::types::special_form::{SpecialFormCategory, TypeQualifier}; use crate::types::tuple::{Tuple, TupleSpec, TupleSpecBuilder}; use crate::types::typed_dict::TypedDictField; pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type}; @@ -5991,133 +5990,33 @@ impl<'db> Type<'db> { KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)), }, - Type::SpecialForm(special_form) => match special_form { - SpecialFormType::Never | SpecialFormType::NoReturn => Ok(Type::Never), - SpecialFormType::LiteralString => Ok(Type::literal_string()), - SpecialFormType::Any => Ok(Type::any()), - SpecialFormType::Unknown => Ok(Type::unknown()), - SpecialFormType::AlwaysTruthy => Ok(Type::AlwaysTruthy), - SpecialFormType::AlwaysFalsy => Ok(Type::AlwaysFalsy), - + Type::SpecialForm(special_form) => match special_form.kind() { // We treat `typing.Type` exactly the same as `builtins.type`: - SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)), - SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())), - - // Legacy `typing` aliases - SpecialFormType::List => Ok(KnownClass::List.to_instance(db)), - SpecialFormType::Dict => Ok(KnownClass::Dict.to_instance(db)), - SpecialFormType::Set => Ok(KnownClass::Set.to_instance(db)), - SpecialFormType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)), - SpecialFormType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)), - SpecialFormType::Counter => Ok(KnownClass::Counter.to_instance(db)), - SpecialFormType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)), - SpecialFormType::Deque => Ok(KnownClass::Deque.to_instance(db)), - SpecialFormType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)), - - // TODO: Use an opt-in rule for a bare `Callable` - SpecialFormType::Callable => Ok(Type::Callable(CallableType::unknown(db))), - - // Special case: `NamedTuple` in a type expression is understood to describe the type - // `tuple[object, ...] & `. - // This isn't very principled (since at runtime, `NamedTuple` is just a function), - // but it appears to be what users often expect, and it improves compatibility with - // other type checkers such as mypy. - // See conversation in https://github.com/astral-sh/ruff/pull/19915. - SpecialFormType::NamedTuple => Ok(IntersectionType::from_elements( - db, - [ - Type::homogeneous_tuple(db, Type::object()), - KnownClass::NamedTupleLike.to_instance(db), - ], - )), - SpecialFormType::TypingSelf => { - let index = semantic_index(db, scope_id.file(db)); - let Some(class) = nearest_enclosing_class(db, index, scope_id) else { - return Err(InvalidTypeExpressionError { - fallback_type: Type::unknown(), - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::InvalidType(*self, scope_id) - ], - }); + SpecialFormCategory::Type => Ok(KnownClass::Type.to_instance(db)), + SpecialFormCategory::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())), + SpecialFormCategory::Callable => Ok(Type::Callable(CallableType::unknown(db))), + SpecialFormCategory::LegacyStdlibAlias(alias) => { + Ok(alias.aliased_class().to_instance(db)) + } + SpecialFormCategory::Other(form) => { + form.in_type_expression(db, scope_id, typevar_binding_context) + } + SpecialFormCategory::TypeQualifier(qualifier) => { + let err = match qualifier { + TypeQualifier::Final | TypeQualifier::ClassVar => { + InvalidTypeExpression::TypeQualifier(qualifier) + } + TypeQualifier::ReadOnly + | TypeQualifier::NotRequired + | TypeQualifier::Required => { + InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) + } }; - - Ok( - typing_self(db, scope_id, typevar_binding_context, class.into()) - .map(Type::TypeVar) - .unwrap_or(*self), - ) - } - // We ensure that `typing.TypeAlias` used in the expected position (annotating an - // annotated assignment statement) doesn't reach here. Using it in any other type - // expression is an error. - SpecialFormType::TypeAlias => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![InvalidTypeExpression::TypeAlias], - fallback_type: Type::unknown(), - }), - SpecialFormType::TypedDict => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![InvalidTypeExpression::TypedDict], - fallback_type: Type::unknown(), - }), - - SpecialFormType::Literal - | SpecialFormType::Union - | SpecialFormType::Intersection => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::RequiresArguments(*special_form) - ], - fallback_type: Type::unknown(), - }), - - SpecialFormType::Protocol => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![InvalidTypeExpression::Protocol], - fallback_type: Type::unknown(), - }), - SpecialFormType::Generic => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![InvalidTypeExpression::Generic], - fallback_type: Type::unknown(), - }), - - SpecialFormType::Optional - | SpecialFormType::Not - | SpecialFormType::Top - | SpecialFormType::Bottom - | SpecialFormType::TypeOf - | SpecialFormType::TypeIs - | SpecialFormType::TypeGuard - | SpecialFormType::Unpack - | SpecialFormType::CallableTypeOf => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::RequiresOneArgument(*special_form) - ], - fallback_type: Type::unknown(), - }), - - SpecialFormType::Annotated | SpecialFormType::Concatenate => { - Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::RequiresTwoArguments(*special_form) - ], - fallback_type: Type::unknown(), - }) - } - - SpecialFormType::ClassVar | SpecialFormType::Final => { Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::TypeQualifier(*special_form) - ], + invalid_expressions: smallvec::smallvec_inline![err], fallback_type: Type::unknown(), }) } - - SpecialFormType::ReadOnly - | SpecialFormType::NotRequired - | SpecialFormType::Required => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec_inline![ - InvalidTypeExpression::TypeQualifierRequiresOneArgument(*special_form) - ], - fallback_type: Type::unknown(), - }), }, Type::Union(union) => { @@ -7956,9 +7855,10 @@ impl<'db> TypeAndQualifiers<'db> { self.origin } - /// Insert/add an additional type qualifier. - pub(crate) fn add_qualifier(&mut self, qualifier: TypeQualifiers) { + /// Return `self` with an additional qualifier added to the set of qualifiers. + pub(crate) fn with_qualifier(mut self, qualifier: TypeQualifiers) -> Self { self.qualifiers |= qualifier; + self } /// Return the set of type qualifiers. @@ -8043,10 +7943,10 @@ enum InvalidTypeExpression<'db> { TypeAlias, /// Type qualifiers are always invalid in *type expressions*, /// but these ones are okay with 0 arguments in *annotation expressions* - TypeQualifier(SpecialFormType), + TypeQualifier(TypeQualifier), /// Type qualifiers that are invalid in type expressions, /// and which would require exactly one argument even if they appeared in an annotation expression - TypeQualifierRequiresOneArgument(SpecialFormType), + TypeQualifierRequiresOneArgument(TypeQualifier), /// Some types are always invalid in type expressions InvalidType(Type<'db>, ScopeId<'db>), } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 5e12ec91c9e4f..7d0c99796e680 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,14 +1,14 @@ use crate::types::class::CodeGeneratorKind; use crate::types::generics::{ApplySpecialization, Specialization}; use crate::types::mro::MroIterator; -use crate::{Db, DisplaySettings}; - +use crate::types::special_form::{self, SpecialFormCategory}; use crate::types::tuple::TupleType; use crate::types::{ ApplyTypeMappingVisitor, ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, SpecialFormType, StaticMroError, Type, TypeContext, TypeMapping, todo_type, }; +use crate::{Db, DisplaySettings}; /// Enumeration of the possible kinds of types we allow in class bases. /// @@ -212,95 +212,72 @@ impl<'db> ClassBase<'db> { } }, - Type::SpecialForm(special_form) => match special_form { - SpecialFormType::Annotated - | SpecialFormType::Literal - | SpecialFormType::LiteralString - | SpecialFormType::Union - | SpecialFormType::NoReturn - | SpecialFormType::Never - | SpecialFormType::Final - | SpecialFormType::NotRequired - | SpecialFormType::TypeGuard - | SpecialFormType::TypeIs - | SpecialFormType::TypingSelf - | SpecialFormType::Unpack - | SpecialFormType::ClassVar - | SpecialFormType::Concatenate - | SpecialFormType::Required - | SpecialFormType::TypeAlias - | SpecialFormType::ReadOnly - | SpecialFormType::Optional - | SpecialFormType::Not - | SpecialFormType::Top - | SpecialFormType::Bottom - | SpecialFormType::Intersection - | SpecialFormType::TypeOf - | SpecialFormType::CallableTypeOf - | SpecialFormType::AlwaysTruthy - | SpecialFormType::AlwaysFalsy => None, - - SpecialFormType::Any => Some(Self::Dynamic(DynamicType::Any)), - SpecialFormType::Unknown => Some(Self::unknown()), - - SpecialFormType::Protocol => Some(Self::Protocol), - SpecialFormType::Generic => Some(Self::Generic), - - SpecialFormType::NamedTuple => { - let class = subclass?.as_static()?; - let fields = class.own_fields(db, None, CodeGeneratorKind::NamedTuple); - Self::try_from_type( - db, - TupleType::heterogeneous( - db, - fields.values().map(|field| field.declared_ty), - )? - .to_class_type(db) - .into(), - subclass, - ) + Type::SpecialForm(special_form) => match special_form.kind() { + SpecialFormCategory::LegacyStdlibAlias(alias) => { + Self::try_from_type(db, alias.aliased_class().to_class_literal(db), subclass) } - // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO - SpecialFormType::Dict => { - Self::try_from_type(db, KnownClass::Dict.to_class_literal(db), subclass) - } - SpecialFormType::List => { - Self::try_from_type(db, KnownClass::List.to_class_literal(db), subclass) - } - SpecialFormType::Type => { - Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass) - } - SpecialFormType::Tuple => { - Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db), subclass) - } - SpecialFormType::Set => { - Self::try_from_type(db, KnownClass::Set.to_class_literal(db), subclass) - } - SpecialFormType::FrozenSet => { - Self::try_from_type(db, KnownClass::FrozenSet.to_class_literal(db), subclass) - } - SpecialFormType::ChainMap => { - Self::try_from_type(db, KnownClass::ChainMap.to_class_literal(db), subclass) - } - SpecialFormType::Counter => { - Self::try_from_type(db, KnownClass::Counter.to_class_literal(db), subclass) - } - SpecialFormType::DefaultDict => { - Self::try_from_type(db, KnownClass::DefaultDict.to_class_literal(db), subclass) - } - SpecialFormType::Deque => { - Self::try_from_type(db, KnownClass::Deque.to_class_literal(db), subclass) - } - SpecialFormType::OrderedDict => { - Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db), subclass) - } - SpecialFormType::TypedDict => Some(Self::TypedDict), - SpecialFormType::Callable => Self::try_from_type( + SpecialFormCategory::Callable => Self::try_from_type( db, todo_type!("Support for Callable as a base class"), subclass, ), + + SpecialFormCategory::Tuple => { + Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db), subclass) + } + + // TODO: Classes inheriting from `typing.Type` also have `Generic` in their MRO + SpecialFormCategory::Type => { + Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass) + } + + SpecialFormCategory::TypeQualifier(_) => None, + + SpecialFormCategory::Other(form) => match form { + special_form::MiscSpecialForm::Any => Some(Self::Dynamic(DynamicType::Any)), + special_form::MiscSpecialForm::Unknown => Some(Self::unknown()), + special_form::MiscSpecialForm::Protocol => Some(Self::Protocol), + special_form::MiscSpecialForm::Generic => Some(Self::Generic), + special_form::MiscSpecialForm::TypedDict => Some(Self::TypedDict), + + special_form::MiscSpecialForm::NamedTuple => { + let class = subclass?.as_static()?; + let fields = class.own_fields(db, None, CodeGeneratorKind::NamedTuple); + Self::try_from_type( + db, + TupleType::heterogeneous( + db, + fields.values().map(|field| field.declared_ty), + )? + .to_class_type(db) + .into(), + subclass, + ) + } + + special_form::MiscSpecialForm::AlwaysFalsy + | special_form::MiscSpecialForm::AlwaysTruthy + | special_form::MiscSpecialForm::TypeOf + | special_form::MiscSpecialForm::CallableTypeOf + | special_form::MiscSpecialForm::TypeIs + | special_form::MiscSpecialForm::TypingSelf + | special_form::MiscSpecialForm::Not + | special_form::MiscSpecialForm::Top + | special_form::MiscSpecialForm::Bottom + | special_form::MiscSpecialForm::Intersection + | special_form::MiscSpecialForm::Literal + | special_form::MiscSpecialForm::LiteralString + | special_form::MiscSpecialForm::Annotated + | special_form::MiscSpecialForm::TypeAlias + | special_form::MiscSpecialForm::Optional + | special_form::MiscSpecialForm::Unpack + | special_form::MiscSpecialForm::Concatenate + | special_form::MiscSpecialForm::Never + | special_form::MiscSpecialForm::NoReturn + | special_form::MiscSpecialForm::Union + | special_form::MiscSpecialForm::TypeGuard => None, + }, }, } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 6a828626ef47e..f708fd62e7ff9 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -3926,7 +3926,7 @@ pub(crate) fn report_invalid_arguments_to_annotated( pub(crate) fn report_invalid_argument_number_to_special_form( context: &InferContext, subscript: &ast::ExprSubscript, - special_form: SpecialFormType, + special_form: impl Into, received_arguments: usize, expected_arguments: u8, ) { @@ -3939,6 +3939,7 @@ pub(crate) fn report_invalid_argument_number_to_special_form( builder.into_diagnostic(format_args!( "Special form `{special_form}` expected exactly {expected_arguments} {noun}, \ got {received_arguments}", + special_form = special_form.into(), )); } } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index f05127b4dc1f4..b72c4690af96e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -119,6 +119,7 @@ use crate::types::generics::{ use crate::types::infer::nearest_enclosing_function; use crate::types::mro::{DynamicMroErrorKind, StaticMroErrorKind}; use crate::types::newtype::NewType; +use crate::types::special_form::{AliasSpec, MiscSpecialForm}; use crate::types::subclass_of::SubclassOfInner; use crate::types::subscript::{LegacyGenericOrigin, SubscriptError, SubscriptErrorKind}; use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleSpecBuilder, TupleType}; @@ -132,12 +133,13 @@ use crate::types::{ IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, KnownUnion, LintDiagnosticGuard, LiteralValueType, LiteralValueTypeKind, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, ParamSpecAttrKind, Parameter, ParameterForm, - Parameters, Signature, SpecialFormType, StaticClassLiteral, SubclassOfType, Truthiness, Type, - TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers, TypeVarBoundOrConstraints, - TypeVarBoundOrConstraintsEvaluation, TypeVarConstraints, TypeVarDefaultEvaluation, - TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, - UnionType, UnionTypeInstance, any_over_type, binding_type, definition_expression_type, - infer_complete_scope_types, infer_scope_types, todo_type, + Parameters, Signature, SpecialFormCategory, SpecialFormType, StaticClassLiteral, + SubclassOfType, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, + TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation, + TypeVarConstraints, TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, + TypeVarVariance, TypedDictType, UnionBuilder, UnionType, UnionTypeInstance, any_over_type, + binding_type, definition_expression_type, infer_complete_scope_types, infer_scope_types, + todo_type, }; use crate::types::{CallableTypes, overrides}; use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic}; @@ -15933,279 +15935,243 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); } } - Type::SpecialForm(SpecialFormType::Tuple) => { - return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(subscript)); - } - Type::SpecialForm(SpecialFormType::Literal) => { - match self.infer_literal_parameter_type(slice) { - Ok(result) => { - return Type::KnownInstance(KnownInstanceType::Literal(InternedType::new( - self.db(), - result, - ))); - } - Err(nodes) => { - for node in nodes { - let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node) - else { - continue; - }; - builder.into_diagnostic( - "Type arguments for `Literal` must be `None`, \ + Type::SpecialForm(special_form) => match special_form.kind() { + SpecialFormCategory::Tuple => { + return tuple_generic_alias( + self.db(), + self.infer_tuple_type_expression(subscript), + ); + } + SpecialFormCategory::Other(MiscSpecialForm::Literal) => { + match self.infer_literal_parameter_type(slice) { + Ok(result) => { + return Type::KnownInstance(KnownInstanceType::Literal( + InternedType::new(self.db(), result), + )); + } + Err(nodes) => { + for node in nodes { + let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, node) + else { + continue; + }; + builder.into_diagnostic( + "Type arguments for `Literal` must be `None`, \ a literal value (int, bool, str, or bytes), or an enum member", - ); + ); + } + return Type::unknown(); } - return Type::unknown(); } } - } - Type::SpecialForm(SpecialFormType::Annotated) => { - let ast::Expr::Tuple(ast::ExprTuple { - elts: ref arguments, - .. - }) = **slice - else { - report_invalid_arguments_to_annotated(&self.context, subscript); - - return self.infer_expression(slice, TypeContext::default()); - }; + SpecialFormCategory::Other(MiscSpecialForm::Annotated) => { + let ast::Expr::Tuple(ast::ExprTuple { + elts: ref arguments, + .. + }) = **slice + else { + report_invalid_arguments_to_annotated(&self.context, subscript); - if arguments.len() < 2 { - report_invalid_arguments_to_annotated(&self.context, subscript); - } + return self.infer_expression(slice, TypeContext::default()); + }; - let [type_expr, metadata @ ..] = &arguments[..] else { - for argument in arguments { - self.infer_expression(argument, TypeContext::default()); + if arguments.len() < 2 { + report_invalid_arguments_to_annotated(&self.context, subscript); } - self.store_expression_type(slice, Type::unknown()); - return Type::unknown(); - }; - for element in metadata { - self.infer_expression(element, TypeContext::default()); - } + let [type_expr, metadata @ ..] = &arguments[..] else { + for argument in arguments { + self.infer_expression(argument, TypeContext::default()); + } + self.store_expression_type(slice, Type::unknown()); + return Type::unknown(); + }; - let ty = self.infer_type_expression(type_expr); + for element in metadata { + self.infer_expression(element, TypeContext::default()); + } - return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new( - self.db(), - ty, - ))); - } - Type::SpecialForm(SpecialFormType::Optional) => { - let db = self.db(); + let ty = self.infer_type_expression(type_expr); - if matches!(**slice, ast::Expr::Tuple(_)) - && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic(format_args!( - "`typing.Optional` requires exactly one argument" - )); + return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new( + self.db(), + ty, + ))); } + SpecialFormCategory::Other(MiscSpecialForm::Optional) => { + let db = self.db(); + + if matches!(**slice, ast::Expr::Tuple(_)) + && let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic(format_args!( + "`typing.Optional` requires exactly one argument" + )); + } - let ty = self.infer_type_expression(slice); + let ty = self.infer_type_expression(slice); - // `Optional[None]` is equivalent to `None`: - if ty.is_none(db) { - return ty; + // `Optional[None]` is equivalent to `None`: + if ty.is_none(db) { + return ty; + } + + return Type::KnownInstance(KnownInstanceType::UnionType( + UnionTypeInstance::new( + db, + None, + Ok(UnionType::from_elements(db, [ty, Type::none(db)])), + ), + )); } + SpecialFormCategory::Other(MiscSpecialForm::Union) => { + let db = self.db(); - return Type::KnownInstance(KnownInstanceType::UnionType(UnionTypeInstance::new( - db, - None, - Ok(UnionType::from_elements(db, [ty, Type::none(db)])), - ))); - } - Type::SpecialForm(SpecialFormType::Union) => { - let db = self.db(); + match **slice { + ast::Expr::Tuple(ref tuple) => { + let mut elements = tuple + .elts + .iter() + .map(|elt| self.infer_type_expression(elt)) + .peekable(); - match **slice { - ast::Expr::Tuple(ref tuple) => { - let mut elements = tuple - .elts - .iter() - .map(|elt| self.infer_type_expression(elt)) - .peekable(); + let is_empty = elements.peek().is_none(); + let union_type = Type::KnownInstance(KnownInstanceType::UnionType( + UnionTypeInstance::new( + db, + None, + Ok(UnionType::from_elements(db, elements)), + ), + )); - let is_empty = elements.peek().is_none(); - let union_type = Type::KnownInstance(KnownInstanceType::UnionType( - UnionTypeInstance::new( - db, - None, - Ok(UnionType::from_elements(db, elements)), - ), - )); + if is_empty + && let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic( + "`typing.Union` requires at least one type argument", + ); + } - if is_empty - && let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic( - "`typing.Union` requires at least one type argument", - ); + return union_type; + } + _ => { + return self.infer_expression(slice, TypeContext::default()); } - - return union_type; - } - _ => { - return self.infer_expression(slice, TypeContext::default()); } } - } - Type::SpecialForm(SpecialFormType::Type) => { - // Similar to the branch above that handles `type[…]`, handle `typing.Type[…]` - let argument_ty = self.infer_type_expression(slice); - return Type::KnownInstance(KnownInstanceType::TypeGenericAlias( - InternedType::new(self.db(), argument_ty), - )); - } - Type::SpecialForm(SpecialFormType::Callable) => { - let arguments = if let ast::Expr::Tuple(tuple) = &*subscript.slice { - &*tuple.elts - } else { - std::slice::from_ref(&*subscript.slice) - }; + SpecialFormCategory::Type => { + // Similar to the branch above that handles `type[…]`, handle `typing.Type[…]` + let argument_ty = self.infer_type_expression(slice); + return Type::KnownInstance(KnownInstanceType::TypeGenericAlias( + InternedType::new(self.db(), argument_ty), + )); + } + SpecialFormCategory::Callable => { + let arguments = if let ast::Expr::Tuple(tuple) = &*subscript.slice { + &*tuple.elts + } else { + std::slice::from_ref(&*subscript.slice) + }; - // TODO: Remove this once we support Concatenate properly. This is necessary - // to avoid a lot of false positives downstream, because we can't represent the typevar- - // specialized `Callable` types yet. - let num_arguments = arguments.len(); - if num_arguments == 2 { - let first_arg = &arguments[0]; - let second_arg = &arguments[1]; - - if first_arg.is_subscript_expr() { - let first_arg_ty = self.infer_expression(first_arg, TypeContext::default()); - if let Type::Dynamic(DynamicType::UnknownGeneric(generic_context)) = - first_arg_ty - { - let mut variables = generic_context - .variables(self.db()) - .collect::>(); + // TODO: Remove this once we support Concatenate properly. This is necessary + // to avoid a lot of false positives downstream, because we can't represent the typevar- + // specialized `Callable` types yet. + let num_arguments = arguments.len(); + if num_arguments == 2 { + let first_arg = &arguments[0]; + let second_arg = &arguments[1]; + + if first_arg.is_subscript_expr() { + let first_arg_ty = + self.infer_expression(first_arg, TypeContext::default()); + if let Type::Dynamic(DynamicType::UnknownGeneric(generic_context)) = + first_arg_ty + { + let mut variables = generic_context + .variables(self.db()) + .collect::>(); - let return_ty = - self.infer_expression(second_arg, TypeContext::default()); - return_ty.bind_and_find_all_legacy_typevars( - self.db(), - self.typevar_binding_context, - &mut variables, - ); + let return_ty = + self.infer_expression(second_arg, TypeContext::default()); + return_ty.bind_and_find_all_legacy_typevars( + self.db(), + self.typevar_binding_context, + &mut variables, + ); - let generic_context = - GenericContext::from_typevar_instances(self.db(), variables); - return Type::Dynamic(DynamicType::UnknownGeneric(generic_context)); - } + let generic_context = + GenericContext::from_typevar_instances(self.db(), variables); + return Type::Dynamic(DynamicType::UnknownGeneric(generic_context)); + } - if let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic(format_args!( + if let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic(format_args!( "The first argument to `Callable` must be either a list of types, \ ParamSpec, Concatenate, or `...`", )); + } + return Type::KnownInstance(KnownInstanceType::Callable( + CallableType::unknown(self.db()), + )); } - return Type::KnownInstance(KnownInstanceType::Callable( - CallableType::unknown(self.db()), - )); } - } - let callable = self - .infer_callable_type(subscript) - .as_callable() - .expect("always returns Type::Callable"); + let callable = self + .infer_callable_type(subscript) + .as_callable() + .expect("always returns Type::Callable"); - return Type::KnownInstance(KnownInstanceType::Callable(callable)); - } - // `typing` special forms with a single generic argument - Type::SpecialForm( - special_form @ (SpecialFormType::List - | SpecialFormType::Set - | SpecialFormType::FrozenSet - | SpecialFormType::Counter - | SpecialFormType::Deque), - ) => { - let slice_ty = self.infer_type_expression(slice); + return Type::KnownInstance(KnownInstanceType::Callable(callable)); + } + SpecialFormCategory::Other(_) => {} + SpecialFormCategory::LegacyStdlibAlias(alias) => { + let AliasSpec { + class, + expected_argument_number, + } = alias.alias_spec(); - let element_ty = if matches!(**slice, ast::Expr::Tuple(_)) { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.{}` requires exactly one argument", - special_form.name() - )); - } - Type::unknown() - } else { - slice_ty - }; + let args = if let ast::Expr::Tuple(t) = &**slice { + &*t.elts + } else { + std::slice::from_ref(&**slice) + }; - let class = special_form - .aliased_stdlib_class() - .expect("A known stdlib class is available"); - - return class - .to_specialized_class_type(self.db(), &[element_ty]) - .map(Type::from) - .unwrap_or_else(Type::unknown); - } - // `typing` special forms with two generic arguments - Type::SpecialForm( - special_form @ (SpecialFormType::Dict - | SpecialFormType::ChainMap - | SpecialFormType::DefaultDict - | SpecialFormType::OrderedDict), - ) => { - let (first_ty, second_ty) = if let ast::Expr::Tuple(ast::ExprTuple { - elts: ref arguments, - .. - }) = **slice - { - if arguments.len() != 2 - && let Some(builder) = + if args.len() != expected_argument_number { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic(format_args!( - "`typing.{}` requires exactly two arguments, got {}", - special_form.name(), - arguments.len() - )); - } - - if let [first_expr, second_expr] = &arguments[..] { - let first_ty = self.infer_type_expression(first_expr); - let second_ty = self.infer_type_expression(second_expr); - - (first_ty, second_ty) - } else { - for argument in arguments { - self.infer_type_expression(argument); + { + let noun = if expected_argument_number == 1 { + "argument" + } else { + "arguments" + }; + builder.into_diagnostic(format_args!( + "`typing.{name}` requires exactly \ + {expected_argument_number} {noun}, got {got}", + name = special_form.name(), + got = args.len() + )); } - - (Type::unknown(), Type::unknown()) - } - } else { - let first_ty = self.infer_type_expression(slice); - - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.{}` requires exactly two arguments, got 1", - special_form.name() - )); } - (first_ty, Type::unknown()) - }; + let arg_types: Vec<_> = args + .iter() + .map(|arg| self.infer_type_expression(arg)) + .collect(); - let class = special_form - .aliased_stdlib_class() - .expect("Stdlib class available"); + return class + .to_specialized_class_type(self.db(), arg_types) + .map(Type::from) + .unwrap_or_else(Type::unknown); + } + SpecialFormCategory::TypeQualifier(_) => {} + }, - return class - .to_specialized_class_type(self.db(), &[first_ty, second_ty]) - .map(Type::from) - .unwrap_or_else(Type::unknown); - } Type::KnownInstance( KnownInstanceType::UnionType(_) | KnownInstanceType::Annotated(_) 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 e57e43f056409..fe521ba3860f1 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 @@ -6,11 +6,13 @@ use crate::types::diagnostic::{ INVALID_TYPE_FORM, REDUNDANT_FINAL_CLASSVAR, report_invalid_arguments_to_annotated, }; use crate::types::infer::nearest_enclosing_class; +use crate::types::special_form::{MiscSpecialForm, SpecialFormCategory}; use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, }; use crate::types::{ - KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifiers, todo_type, + KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifier, + TypeQualifiers, todo_type, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -90,36 +92,37 @@ impl<'db> TypeInferenceBuilder<'db, '_> { pep_613_policy: PEP613Policy, ) -> TypeAndQualifiers<'db> { match ty { - Type::SpecialForm(SpecialFormType::ClassVar) => TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::CLASS_VAR, - ), - Type::SpecialForm(SpecialFormType::Final) => TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::FINAL, - ), - Type::SpecialForm(SpecialFormType::Required) => TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::REQUIRED, - ), - Type::SpecialForm(SpecialFormType::NotRequired) => TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::NOT_REQUIRED, - ), - Type::SpecialForm(SpecialFormType::ReadOnly) => TypeAndQualifiers::new( - Type::unknown(), - TypeOrigin::Declared, - TypeQualifiers::READ_ONLY, - ), - Type::SpecialForm(SpecialFormType::TypeAlias) - if pep_613_policy == PEP613Policy::Allowed => - { - TypeAndQualifiers::declared(ty) - } + Type::SpecialForm(special_form) => match special_form.kind() { + SpecialFormCategory::TypeQualifier(qualifier) => TypeAndQualifiers::new( + Type::unknown(), + TypeOrigin::Declared, + TypeQualifiers::from(qualifier), + ), + SpecialFormCategory::Other(MiscSpecialForm::TypeAlias) + if pep_613_policy == PEP613Policy::Allowed => + { + TypeAndQualifiers::declared(ty) + } + SpecialFormCategory::Type + | SpecialFormCategory::Tuple + | SpecialFormCategory::Callable + | SpecialFormCategory::LegacyStdlibAlias(_) + | SpecialFormCategory::Other(_) => TypeAndQualifiers::declared( + ty.default_specialize(builder.db()) + .in_type_expression( + builder.db(), + builder.scope(), + builder.typevar_binding_context, + ) + .unwrap_or_else(|error| { + error.into_fallback_type( + &builder.context, + annotation, + builder.is_reachable(annotation), + ) + }), + ), + }, // Conditional import of `typing.TypeAlias` or `typing_extensions.TypeAlias` on a // Python version where the former doesn't exist. Type::Union(union) @@ -228,135 +231,133 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let slice = &**slice; match value_ty { - Type::SpecialForm(SpecialFormType::Annotated) => { - // This branch is similar to the corresponding branch in `infer_parameterized_special_form_type_expression`, but - // `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly - // differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`). - if let ast::Expr::Tuple(ast::ExprTuple { - elts: arguments, .. - }) = slice - { - if arguments.len() < 2 { - report_invalid_arguments_to_annotated(&self.context, subscript); - } - - if let [inner_annotation, metadata @ ..] = &arguments[..] { - for element in metadata { - self.infer_expression(element, TypeContext::default()); + Type::SpecialForm(special_form) => match special_form.kind() { + SpecialFormCategory::Other(MiscSpecialForm::Annotated) => { + // This branch is similar to the corresponding branch in + // `infer_parameterized_special_form_type_expression`, but + // `Annotated[…]` can appear both in annotation expressions and in + // type expressions, and needs to be handled slightly + // differently in each case (calling either `infer_type_expression_*` + // or `infer_annotation_expression_*`). + if let ast::Expr::Tuple(ast::ExprTuple { + elts: arguments, .. + }) = slice + { + if arguments.len() < 2 { + report_invalid_arguments_to_annotated(&self.context, subscript); } - let inner_annotation_ty = self.infer_annotation_expression_impl( - inner_annotation, - PEP613Policy::Disallowed, - ); + if let [inner_annotation, metadata @ ..] = &arguments[..] { + for element in metadata { + self.infer_expression(element, TypeContext::default()); + } - self.store_expression_type(slice, inner_annotation_ty.inner_type()); - inner_annotation_ty - } else { - for argument in arguments { - self.infer_expression(argument, TypeContext::default()); + let inner_annotation_ty = self + .infer_annotation_expression_impl( + inner_annotation, + PEP613Policy::Disallowed, + ); + + self.store_expression_type( + slice, + inner_annotation_ty.inner_type(), + ); + inner_annotation_ty + } else { + for argument in arguments { + self.infer_expression(argument, TypeContext::default()); + } + self.store_expression_type(slice, Type::unknown()); + TypeAndQualifiers::declared(Type::unknown()) } - self.store_expression_type(slice, Type::unknown()); - TypeAndQualifiers::declared(Type::unknown()) + } else { + report_invalid_arguments_to_annotated(&self.context, subscript); + self.infer_annotation_expression_impl( + slice, + PEP613Policy::Disallowed, + ) } - } else { - report_invalid_arguments_to_annotated(&self.context, subscript); - self.infer_annotation_expression_impl(slice, PEP613Policy::Disallowed) } - } - Type::SpecialForm( - type_qualifier @ (SpecialFormType::ClassVar - | SpecialFormType::Final - | SpecialFormType::Required - | SpecialFormType::NotRequired - | SpecialFormType::ReadOnly), - ) => { - 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 { - let mut type_and_qualifiers = self.infer_annotation_expression_impl( - argument, - PEP613Policy::Disallowed, - ); - - // Emit a diagnostic if ClassVar and Final are combined in a class that is - // not a dataclass, since Final already implies the semantics of ClassVar. - let classvar_and_final = match type_qualifier { - SpecialFormType::Final => type_and_qualifiers - .qualifiers - .contains(TypeQualifiers::CLASS_VAR), - SpecialFormType::ClassVar => type_and_qualifiers - .qualifiers - .contains(TypeQualifiers::FINAL), - _ => false, + SpecialFormCategory::TypeQualifier(qualifier) => { + let arguments = if let ast::Expr::Tuple(tuple) = slice { + &*tuple.elts + } else { + std::slice::from_ref(slice) }; - if classvar_and_final - && nearest_enclosing_class(self.db(), self.index, self.scope()) - .is_none_or(|class| !class.is_dataclass_like(self.db())) - && let Some(builder) = self - .context - .report_lint(&REDUNDANT_FINAL_CLASSVAR, subscript) - { - builder.into_diagnostic(format_args!( - "`Combining `ClassVar` and `Final` is redundant" - )); - } + let type_and_qualifiers = if let [argument] = arguments { + let type_and_qualifiers = self.infer_annotation_expression_impl( + argument, + PEP613Policy::Disallowed, + ); + + // Emit a diagnostic if ClassVar and Final are combined in a class that is + // not a dataclass, since Final already implies the semantics of ClassVar. + let classvar_and_final = match qualifier { + TypeQualifier::Final => type_and_qualifiers + .qualifiers + .contains(TypeQualifiers::CLASS_VAR), + TypeQualifier::ClassVar => type_and_qualifiers + .qualifiers + .contains(TypeQualifiers::FINAL), + _ => false, + }; + if classvar_and_final + && nearest_enclosing_class(self.db(), self.index, self.scope()) + .is_none_or(|class| !class.is_dataclass_like(self.db())) + && let Some(builder) = self + .context + .report_lint(&REDUNDANT_FINAL_CLASSVAR, subscript) + { + builder.into_diagnostic(format_args!( + "`Combining `ClassVar` and `Final` is redundant" + )); + } - match type_qualifier { - SpecialFormType::ClassVar => { - type_and_qualifiers.add_qualifier(TypeQualifiers::CLASS_VAR); - if type_and_qualifiers + if qualifier == TypeQualifier::ClassVar + && type_and_qualifiers .inner_type() .has_non_self_typevar(self.db()) - && let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, subscript) - { - builder.into_diagnostic( - "`ClassVar` cannot contain type variables", - ); - } - } - SpecialFormType::Final => { - type_and_qualifiers.add_qualifier(TypeQualifiers::FINAL); + && let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic( + "`ClassVar` cannot contain type variables", + ); } - SpecialFormType::Required => { - type_and_qualifiers.add_qualifier(TypeQualifiers::REQUIRED); - } - SpecialFormType::NotRequired => { - type_and_qualifiers.add_qualifier(TypeQualifiers::NOT_REQUIRED); + type_and_qualifiers.with_qualifier(TypeQualifiers::from(qualifier)) + } else { + for element in arguments { + self.infer_annotation_expression_impl( + element, + PEP613Policy::Disallowed, + ); } - SpecialFormType::ReadOnly => { - type_and_qualifiers.add_qualifier(TypeQualifiers::READ_ONLY); + if let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + let num_arguments = arguments.len(); + builder.into_diagnostic(format_args!( + "Type qualifier `{qualifier}` expected exactly 1 \ + argument, got {num_arguments}", + )); } - _ => unreachable!(), + TypeAndQualifiers::declared(Type::unknown()) + }; + if slice.is_tuple_expr() { + self.store_expression_type(slice, type_and_qualifiers.inner_type()); } type_and_qualifiers - } 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 `{type_qualifier}` 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 - } + SpecialFormCategory::Type + | SpecialFormCategory::Tuple + | SpecialFormCategory::Callable + | SpecialFormCategory::LegacyStdlibAlias(_) + | SpecialFormCategory::Other(_) => TypeAndQualifiers::declared( + self.infer_subscript_type_expression_no_store( + subscript, slice, value_ty, + ), + ), + }, Type::ClassLiteral(class) if class.is_known(self.db(), KnownClass::InitVar) => { let arguments = if let ast::Expr::Tuple(tuple) = slice { &*tuple.elts @@ -364,12 +365,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { std::slice::from_ref(slice) }; let type_and_qualifiers = if let [argument] = arguments { - let mut type_and_qualifiers = self.infer_annotation_expression_impl( + self.infer_annotation_expression_impl( argument, PEP613Policy::Disallowed, - ); - type_and_qualifiers.add_qualifier(TypeQualifiers::INIT_VAR); - type_and_qualifiers + ) + .with_qualifier(TypeQualifiers::INIT_VAR) } else { for element in arguments { self.infer_annotation_expression_impl( 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 f41fce6f6fc9e..e411aaddab565 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 @@ -11,6 +11,7 @@ use crate::types::diagnostic::{ use crate::types::generics::bind_typevar; use crate::types::infer::builder::InnerExpressionInferenceState; use crate::types::signatures::Signature; +use crate::types::special_form::{self, AliasSpec, LegacyStdlibAlias}; use crate::types::string_annotation::parse_string_annotation; use crate::types::tuple::{TupleSpecBuilder, TupleType}; use crate::types::{ @@ -1213,9 +1214,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { fn infer_parameterized_legacy_typing_alias( &mut self, subscript_node: &ast::ExprSubscript, - expected_arg_count: usize, - alias: SpecialFormType, - class: KnownClass, + alias: LegacyStdlibAlias, ) -> Type<'db> { let arguments = &*subscript_node.slice; let args = if let ast::Expr::Tuple(t) = arguments { @@ -1223,15 +1222,21 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } else { std::slice::from_ref(arguments) }; - if args.len() != expected_arg_count { + + let AliasSpec { + class, + expected_argument_number, + } = alias.alias_spec(); + + if args.len() != expected_argument_number { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) { - let noun = if expected_arg_count == 1 { + let noun = if expected_argument_number == 1 { "argument" } else { "arguments" }; builder.into_diagnostic(format_args!( - "Legacy alias `{alias}` expected exactly {expected_arg_count} {noun}, \ + "Legacy alias `{alias}` expected exactly {expected_argument_number} {noun}, \ got {}", args.len() )); @@ -1301,15 +1306,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> { callable_type } - pub(crate) fn infer_parameterized_special_form_type_expression( + fn infer_parameterized_non_stdlib_alias_special_form( &mut self, subscript: &ast::ExprSubscript, - special_form: SpecialFormType, + special_form: special_form::MiscSpecialForm, ) -> Type<'db> { let db = self.db(); let arguments_slice = &*subscript.slice; + match special_form { - SpecialFormType::Annotated => { + special_form::MiscSpecialForm::Annotated => { let ty = self .infer_subscript_load_impl( Type::SpecialForm(SpecialFormType::Annotated), @@ -1324,27 +1330,29 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } ty } - SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { - Ok(ty) => ty, - Err(nodes) => { - for node in nodes { - let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node) - else { - continue; - }; - builder.into_diagnostic( - "Type arguments for `Literal` must be `None`, \ + special_form::MiscSpecialForm::Literal => { + match self.infer_literal_parameter_type(arguments_slice) { + Ok(ty) => ty, + Err(nodes) => { + for node in nodes { + let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node) + else { + continue; + }; + builder.into_diagnostic( + "Type arguments for `Literal` must be `None`, \ a literal value (int, bool, str, or bytes), or an enum member", - ); + ); + } + Type::unknown() } - Type::unknown() } - }, - SpecialFormType::Optional => { + } + special_form::MiscSpecialForm::Optional => { let param_type = self.infer_type_expression(arguments_slice); UnionType::from_elements_leave_aliases(db, [param_type, Type::none(db)]) } - SpecialFormType::Union => match arguments_slice { + special_form::MiscSpecialForm::Union => match arguments_slice { ast::Expr::Tuple(t) => { let union_ty = UnionType::from_elements_leave_aliases( db, @@ -1355,10 +1363,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } _ => self.infer_type_expression(arguments_slice), }, - SpecialFormType::Callable => self.infer_callable_type(subscript), // `ty_extensions` special forms - SpecialFormType::Not => { + special_form::MiscSpecialForm::Not => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1385,7 +1392,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } negated_type } - SpecialFormType::Intersection => { + special_form::MiscSpecialForm::Intersection => { let elements = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), element => Either::Right(std::iter::once(element)), @@ -1402,7 +1409,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } ty } - SpecialFormType::Top => { + special_form::MiscSpecialForm::Top => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1426,7 +1433,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }; arg.top_materialization(db) } - SpecialFormType::Bottom => { + special_form::MiscSpecialForm::Bottom => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1450,7 +1457,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }; arg.bottom_materialization(db) } - SpecialFormType::TypeOf => { + special_form::MiscSpecialForm::TypeOf => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1479,7 +1486,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { type_of_type } - SpecialFormType::CallableTypeOf => { + special_form::MiscSpecialForm::CallableTypeOf => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1532,84 +1539,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } callable_type } - - SpecialFormType::ChainMap => self.infer_parameterized_legacy_typing_alias( - subscript, - 2, - SpecialFormType::ChainMap, - KnownClass::ChainMap, - ), - SpecialFormType::OrderedDict => self.infer_parameterized_legacy_typing_alias( - subscript, - 2, - SpecialFormType::OrderedDict, - KnownClass::OrderedDict, - ), - SpecialFormType::Dict => self.infer_parameterized_legacy_typing_alias( - subscript, - 2, - SpecialFormType::Dict, - KnownClass::Dict, - ), - SpecialFormType::List => self.infer_parameterized_legacy_typing_alias( - subscript, - 1, - SpecialFormType::List, - KnownClass::List, - ), - SpecialFormType::DefaultDict => self.infer_parameterized_legacy_typing_alias( - subscript, - 2, - SpecialFormType::DefaultDict, - KnownClass::DefaultDict, - ), - SpecialFormType::Counter => self.infer_parameterized_legacy_typing_alias( - subscript, - 1, - SpecialFormType::Counter, - KnownClass::Counter, - ), - SpecialFormType::Set => self.infer_parameterized_legacy_typing_alias( - subscript, - 1, - SpecialFormType::Set, - KnownClass::Set, - ), - SpecialFormType::FrozenSet => self.infer_parameterized_legacy_typing_alias( - subscript, - 1, - SpecialFormType::FrozenSet, - KnownClass::FrozenSet, - ), - SpecialFormType::Deque => self.infer_parameterized_legacy_typing_alias( - subscript, - 1, - SpecialFormType::Deque, - KnownClass::Deque, - ), - - SpecialFormType::ClassVar - | SpecialFormType::Final - | SpecialFormType::Required - | SpecialFormType::NotRequired - | SpecialFormType::ReadOnly => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - let diag = builder.into_diagnostic(format_args!( - "Type qualifier `{special_form}` is not allowed in type expressions \ - (only in annotation expressions)", - )); - diagnostic::add_type_expression_reference_link(diag); - } - self.infer_type_expression(arguments_slice) - } - SpecialFormType::TypeIs => match arguments_slice { + special_form::MiscSpecialForm::TypeIs => match arguments_slice { ast::Expr::Tuple(_) => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - let diag = builder.into_diagnostic( - "Special form `typing.TypeIs` expected exactly one type parameter", - ); + let diag = builder.into_diagnostic(format_args!( + "Special form `{special_form}` expected exactly one type parameter", + )); diagnostic::add_type_expression_reference_link(diag); } @@ -1627,13 +1564,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> { .top_materialization(self.db()), ), }, - SpecialFormType::TypeGuard => match arguments_slice { + special_form::MiscSpecialForm::TypeGuard => match arguments_slice { ast::Expr::Tuple(_) => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let diag = builder.into_diagnostic(format_args!( - "Special form `typing.TypeGuard` expected exactly one type parameter", + "Special form `{special_form}` expected exactly one type parameter", )); diagnostic::add_type_expression_reference_link(diag); } @@ -1647,7 +1584,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_type_expression(arguments_slice), ), }, - SpecialFormType::Concatenate => { + special_form::MiscSpecialForm::Concatenate => { let arguments = if let ast::Expr::Tuple(tuple) = arguments_slice { &*tuple.elts } else { @@ -1672,14 +1609,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } inferred_type } - SpecialFormType::Unpack => { + special_form::MiscSpecialForm::Unpack => { self.infer_type_expression(arguments_slice); todo_type!("`Unpack[]` special form") } - SpecialFormType::NoReturn - | SpecialFormType::Never - | SpecialFormType::AlwaysTruthy - | SpecialFormType::AlwaysFalsy => { + special_form::MiscSpecialForm::NoReturn + | special_form::MiscSpecialForm::Never + | special_form::MiscSpecialForm::AlwaysTruthy + | special_form::MiscSpecialForm::AlwaysFalsy => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { @@ -1689,12 +1626,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - SpecialFormType::TypingSelf - | SpecialFormType::TypeAlias - | SpecialFormType::TypedDict - | SpecialFormType::Unknown - | SpecialFormType::Any - | SpecialFormType::NamedTuple => { + special_form::MiscSpecialForm::TypingSelf + | special_form::MiscSpecialForm::TypeAlias + | special_form::MiscSpecialForm::TypedDict + | special_form::MiscSpecialForm::Unknown + | special_form::MiscSpecialForm::Any + | special_form::MiscSpecialForm::NamedTuple => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { @@ -1704,7 +1641,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - SpecialFormType::LiteralString => { + special_form::MiscSpecialForm::LiteralString => { let arguments = self.infer_expression(arguments_slice, TypeContext::default()); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { @@ -1751,9 +1688,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - SpecialFormType::Type => self.infer_subclass_of_type_expression(arguments_slice), - SpecialFormType::Tuple => Type::tuple(self.infer_tuple_type_expression(subscript)), - SpecialFormType::Generic | SpecialFormType::Protocol => { + special_form::MiscSpecialForm::Generic | special_form::MiscSpecialForm::Protocol => { self.infer_expression(arguments_slice, TypeContext::default()); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( @@ -1765,6 +1700,39 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } } + fn infer_parameterized_special_form_type_expression( + &mut self, + subscript: &ast::ExprSubscript, + special_form: SpecialFormType, + ) -> Type<'db> { + match special_form.kind() { + special_form::SpecialFormCategory::LegacyStdlibAlias(alias) => { + self.infer_parameterized_legacy_typing_alias(subscript, alias) + } + special_form::SpecialFormCategory::Other(alias) => { + self.infer_parameterized_non_stdlib_alias_special_form(subscript, alias) + } + special_form::SpecialFormCategory::Tuple => { + Type::tuple(self.infer_tuple_type_expression(subscript)) + } + special_form::SpecialFormCategory::Type => { + self.infer_subclass_of_type_expression(&subscript.slice) + } + special_form::SpecialFormCategory::Callable => self.infer_callable_type(subscript), + special_form::SpecialFormCategory::TypeQualifier(qualifier) => { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + let diag = builder.into_diagnostic(format_args!( + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions)", + )); + diagnostic::add_type_expression_reference_link(diag); + } + self.infer_type_expression(&subscript.slice); + Type::unknown() + } + } + } + pub(crate) fn infer_literal_parameter_type<'param>( &mut self, parameters: &'param ast::Expr, diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 31f8b7fdffdfd..0a6d0abe3305f 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -16,7 +16,7 @@ use crate::types::typed_dict::{ }; use crate::types::{ CallableType, ClassLiteral, ClassType, IntersectionBuilder, IntersectionType, KnownClass, - KnownInstanceType, LiteralValueTypeKind, SpecialFormType, SubclassOfInner, SubclassOfType, + KnownInstanceType, LiteralValueTypeKind, SpecialFormCategory, SubclassOfInner, SubclassOfType, Truthiness, Type, TypeContext, TypeVarBoundOrConstraints, UnionBuilder, infer_expression_types, }; @@ -228,17 +228,24 @@ impl ClassInfoConstraintFunction { ) } - // We don't have a good meta-type for `Callable`s right now, - // so only apply `isinstance()` narrowing, not `issubclass()` - Type::SpecialForm(SpecialFormType::Callable) - if self == ClassInfoConstraintFunction::IsInstance => - { - Some(Type::Callable(CallableType::unknown(db)).top_materialization(db)) - } + Type::SpecialForm(form) => match form.kind() { + SpecialFormCategory::LegacyStdlibAlias(alias) => { + self.generate_constraint(db, alias.aliased_class().to_class_literal(db)) + } + SpecialFormCategory::Tuple => { + self.generate_constraint(db, KnownClass::Tuple.to_class_literal(db)) + } + SpecialFormCategory::Type => { + self.generate_constraint(db, KnownClass::Type.to_class_literal(db)) + } + + // We don't have a good meta-type for `Callable`s right now, + // so only apply `isinstance()` narrowing, not `issubclass()` + SpecialFormCategory::Callable => (self == ClassInfoConstraintFunction::IsInstance) + .then(|| Type::Callable(CallableType::unknown(db)).top_materialization(db)), - Type::SpecialForm(special_form) => special_form - .aliased_stdlib_class() - .and_then(|class| self.generate_constraint(db, class.to_class_literal(db))), + SpecialFormCategory::TypeQualifier(_) | SpecialFormCategory::Other(_) => None, + }, Type::AlwaysFalsy | Type::AlwaysTruthy diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index 7d2d43f97dcdf..109e002879076 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -4,8 +4,15 @@ use super::{ClassType, Type, class::KnownClass}; use crate::db::Db; use crate::semantic_index::place::ScopedPlaceId; -use crate::semantic_index::{FileScopeId, place_table, use_def_map}; +use crate::semantic_index::{ + FileScopeId, definition::Definition, place_table, scope::ScopeId, semantic_index, use_def_map, +}; use crate::types::TypeDefinition; +use crate::types::TypeQualifiers; +use crate::types::{ + IntersectionBuilder, InvalidTypeExpression, InvalidTypeExpressionError, generics::typing_self, + infer::nearest_enclosing_class, +}; use ruff_db::files::File; use std::str::FromStr; use ty_module_resolver::{KnownModule, file_to_module, resolve_module_confident}; @@ -281,6 +288,7 @@ impl SpecialFormType { match self { // TypedDict can be called as a constructor to create TypedDict types Self::TypedDict + // Collection constructors are callable // TODO actually implement support for calling them | Self::ChainMap @@ -290,7 +298,16 @@ impl SpecialFormType { | Self::NamedTuple | Self::OrderedDict => true, - // All other special forms are not callable + // Unlike the aliases to `collections` classes, + // the aliases to builtin classes are *not* callable... + Self::List + | Self::Dict + | Self::Set + | Self::Tuple + | Self::Type + | Self::FrozenSet => false, + + // All other special forms are also not callable Self::Annotated | Self::Literal | Self::LiteralString @@ -298,12 +315,6 @@ impl SpecialFormType { | Self::Union | Self::NoReturn | Self::Never - | Self::Tuple - | Self::List - | Self::Dict - | Self::Set - | Self::FrozenSet - | Self::Type | Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy @@ -336,158 +347,146 @@ impl SpecialFormType { /// specification for more details: /// pub(super) const fn is_valid_in_type_expression(self) -> bool { - match self { - Self::ClassVar - | Self::Final - | Self::Required - | Self::NotRequired - | SpecialFormType::ReadOnly - | SpecialFormType::Unpack - | SpecialFormType::TypeAlias => false, - Self::Annotated - | SpecialFormType::Any - | SpecialFormType::Literal - | SpecialFormType::LiteralString - | SpecialFormType::Optional - | SpecialFormType::Union - | SpecialFormType::NoReturn - | SpecialFormType::Never - | SpecialFormType::Tuple - | SpecialFormType::List - | SpecialFormType::Dict - | SpecialFormType::Set - | SpecialFormType::FrozenSet - | SpecialFormType::ChainMap - | SpecialFormType::Counter - | SpecialFormType::DefaultDict - | SpecialFormType::Deque - | SpecialFormType::OrderedDict - | SpecialFormType::Type - | SpecialFormType::Unknown - | SpecialFormType::AlwaysTruthy - | SpecialFormType::AlwaysFalsy - | SpecialFormType::Not - | SpecialFormType::Intersection - | SpecialFormType::TypeOf - | SpecialFormType::CallableTypeOf - | SpecialFormType::Top - | SpecialFormType::Bottom - | SpecialFormType::Callable - | SpecialFormType::TypingSelf - | SpecialFormType::Concatenate - | SpecialFormType::TypeGuard - | SpecialFormType::TypedDict - | SpecialFormType::TypeIs - | SpecialFormType::Protocol - | SpecialFormType::Generic - | SpecialFormType::NamedTuple => true, + match self.kind() { + SpecialFormCategory::Type + | SpecialFormCategory::Tuple + | SpecialFormCategory::Callable + | SpecialFormCategory::LegacyStdlibAlias(_) => true, + SpecialFormCategory::TypeQualifier(_) => false, + + SpecialFormCategory::Other(form) => match form { + MiscSpecialForm::Any + | MiscSpecialForm::Annotated + | MiscSpecialForm::Literal + | MiscSpecialForm::LiteralString + | MiscSpecialForm::Optional + | MiscSpecialForm::Union + | MiscSpecialForm::NoReturn + | MiscSpecialForm::Never + | MiscSpecialForm::Unknown + | MiscSpecialForm::AlwaysTruthy + | MiscSpecialForm::AlwaysFalsy + | MiscSpecialForm::Not + | MiscSpecialForm::Intersection + | MiscSpecialForm::TypeOf + | MiscSpecialForm::CallableTypeOf + | MiscSpecialForm::Top + | MiscSpecialForm::Bottom + | MiscSpecialForm::Concatenate + | MiscSpecialForm::TypeGuard + | MiscSpecialForm::TypedDict + | MiscSpecialForm::TypeIs + | MiscSpecialForm::NamedTuple + | MiscSpecialForm::TypingSelf => true, + + MiscSpecialForm::Generic + | MiscSpecialForm::Protocol + | MiscSpecialForm::TypeAlias => false, + + // TODO: seems incorrect? + MiscSpecialForm::Unpack => false, + }, } } - /// Return `Some(KnownClass)` if this special form is an alias - /// to a standard library class. - pub(super) const fn aliased_stdlib_class(self) -> Option { + pub(super) const fn kind(self) -> SpecialFormCategory { match self { - Self::List => Some(KnownClass::List), - Self::Dict => Some(KnownClass::Dict), - Self::Set => Some(KnownClass::Set), - Self::FrozenSet => Some(KnownClass::FrozenSet), - Self::ChainMap => Some(KnownClass::ChainMap), - Self::Counter => Some(KnownClass::Counter), - Self::DefaultDict => Some(KnownClass::DefaultDict), - Self::Deque => Some(KnownClass::Deque), - Self::OrderedDict => Some(KnownClass::OrderedDict), - Self::Tuple => Some(KnownClass::Tuple), - Self::Type => Some(KnownClass::Type), - - Self::AlwaysFalsy - | Self::AlwaysTruthy - | Self::Annotated - | Self::Bottom - | Self::CallableTypeOf - | Self::ClassVar - | Self::Concatenate - | Self::Final - | Self::Intersection - | Self::Literal - | Self::LiteralString - | Self::Never - | Self::NoReturn - | Self::Not - | Self::ReadOnly - | Self::Required - | Self::TypeAlias - | Self::TypeGuard - | Self::NamedTuple - | Self::NotRequired - | Self::Optional - | Self::Top - | Self::TypeIs - | Self::TypedDict - | Self::TypingSelf - | Self::Union - | Self::Unknown - | Self::TypeOf - | Self::Any - // `typing.Callable` is an alias to `collections.abc.Callable`, - // but they're both the same `SpecialFormType` in our model, - // and neither is a class in typeshed (even though the `collections.abc` one is at runtime) - | Self::Callable - | Self::Protocol - | Self::Generic - | Self::Unpack => None, + // See the `SpecialFormCategory` doc-comment for why these three are + // treated as their own category. + Self::Callable => SpecialFormCategory::Callable, + Self::Tuple => SpecialFormCategory::Tuple, + Self::Type => SpecialFormCategory::Type, + + // Type qualifiers + Self::Final => SpecialFormCategory::TypeQualifier(TypeQualifier::Final), + Self::ClassVar => SpecialFormCategory::TypeQualifier(TypeQualifier::ClassVar), + Self::ReadOnly => SpecialFormCategory::TypeQualifier(TypeQualifier::ReadOnly), + Self::Required => SpecialFormCategory::TypeQualifier(TypeQualifier::Required), + Self::NotRequired => SpecialFormCategory::TypeQualifier(TypeQualifier::NotRequired), + + // Legacy standard library aliases + Self::List => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::List), + Self::Dict => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::Dict), + Self::Set => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::Set), + Self::FrozenSet => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::FrozenSet), + Self::ChainMap => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::ChainMap), + Self::Counter => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::Counter), + Self::Deque => SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::Deque), + Self::DefaultDict => { + SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::DefaultDict) + } + Self::OrderedDict => { + SpecialFormCategory::LegacyStdlibAlias(LegacyStdlibAlias::OrderedDict) + } + + // Non-standard-library aliases + Self::AlwaysFalsy => SpecialFormCategory::Other(MiscSpecialForm::AlwaysFalsy), + Self::Unknown => SpecialFormCategory::Other(MiscSpecialForm::Unknown), + Self::Not => SpecialFormCategory::Other(MiscSpecialForm::Not), + Self::TypeOf => SpecialFormCategory::Other(MiscSpecialForm::TypeOf), + Self::Top => SpecialFormCategory::Other(MiscSpecialForm::Top), + Self::Bottom => SpecialFormCategory::Other(MiscSpecialForm::Bottom), + Self::Annotated => SpecialFormCategory::Other(MiscSpecialForm::Annotated), + Self::Any => SpecialFormCategory::Other(MiscSpecialForm::Any), + Self::Literal => SpecialFormCategory::Other(MiscSpecialForm::Literal), + Self::Optional => SpecialFormCategory::Other(MiscSpecialForm::Optional), + Self::Union => SpecialFormCategory::Other(MiscSpecialForm::Union), + Self::NoReturn => SpecialFormCategory::Other(MiscSpecialForm::NoReturn), + Self::Never => SpecialFormCategory::Other(MiscSpecialForm::Never), + Self::Concatenate => SpecialFormCategory::Other(MiscSpecialForm::Concatenate), + Self::Unpack => SpecialFormCategory::Other(MiscSpecialForm::Unpack), + Self::TypeAlias => SpecialFormCategory::Other(MiscSpecialForm::TypeAlias), + Self::TypeGuard => SpecialFormCategory::Other(MiscSpecialForm::TypeGuard), + Self::TypedDict => SpecialFormCategory::Other(MiscSpecialForm::TypedDict), + Self::TypeIs => SpecialFormCategory::Other(MiscSpecialForm::TypeIs), + Self::Protocol => SpecialFormCategory::Other(MiscSpecialForm::Protocol), + Self::Generic => SpecialFormCategory::Other(MiscSpecialForm::Generic), + Self::NamedTuple => SpecialFormCategory::Other(MiscSpecialForm::NamedTuple), + Self::AlwaysTruthy => SpecialFormCategory::Other(MiscSpecialForm::AlwaysTruthy), + Self::Intersection => SpecialFormCategory::Other(MiscSpecialForm::Intersection), + Self::TypingSelf => SpecialFormCategory::Other(MiscSpecialForm::TypingSelf), + Self::LiteralString => SpecialFormCategory::Other(MiscSpecialForm::LiteralString), + Self::CallableTypeOf => SpecialFormCategory::Other(MiscSpecialForm::CallableTypeOf), } } /// Return `true` if this special form is valid as the second argument /// to `issubclass()` and `isinstance()` calls. pub(super) const fn is_valid_isinstance_target(self) -> bool { - match self { - Self::Callable - | Self::ChainMap - | Self::Counter - | Self::DefaultDict - | Self::Deque - | Self::FrozenSet - | Self::Dict - | Self::List - | Self::OrderedDict - | Self::Set - | Self::Tuple - | Self::Type - | Self::Protocol - | Self::Generic => true, - - Self::AlwaysFalsy - | Self::AlwaysTruthy - | Self::Annotated - | Self::Bottom - | Self::CallableTypeOf - | Self::ClassVar - | Self::Concatenate - | Self::Final - | Self::Intersection - | Self::Literal - | Self::LiteralString - | Self::Never - | Self::NoReturn - | Self::Not - | Self::ReadOnly - | Self::Required - | Self::TypeAlias - | Self::TypeGuard - | Self::NamedTuple - | Self::NotRequired - | Self::Optional - | Self::Top - | Self::TypeIs - | Self::TypedDict - | Self::TypingSelf - | Self::Union - | Self::Unknown - | Self::TypeOf - | Self::Any // can be used in `issubclass()` but not `isinstance()`. - | Self::Unpack => false, + match self.kind() { + SpecialFormCategory::LegacyStdlibAlias(_) + | SpecialFormCategory::Callable + | SpecialFormCategory::Tuple + | SpecialFormCategory::Type => true, + SpecialFormCategory::TypeQualifier(_) => false, + SpecialFormCategory::Other(form) => match form { + MiscSpecialForm::Protocol | MiscSpecialForm::Generic => true, + MiscSpecialForm::Any + | MiscSpecialForm::Annotated + | MiscSpecialForm::Literal + | MiscSpecialForm::LiteralString + | MiscSpecialForm::Optional + | MiscSpecialForm::Union + | MiscSpecialForm::NoReturn + | MiscSpecialForm::Never + | MiscSpecialForm::Unknown + | MiscSpecialForm::AlwaysTruthy + | MiscSpecialForm::AlwaysFalsy + | MiscSpecialForm::Not + | MiscSpecialForm::Intersection + | MiscSpecialForm::TypeOf + | MiscSpecialForm::CallableTypeOf + | MiscSpecialForm::Top + | MiscSpecialForm::Bottom + | MiscSpecialForm::TypingSelf + | MiscSpecialForm::Concatenate + | MiscSpecialForm::Unpack + | MiscSpecialForm::TypeAlias + | MiscSpecialForm::TypeGuard + | MiscSpecialForm::TypedDict + | MiscSpecialForm::TypeIs + | MiscSpecialForm::NamedTuple => false, + }, } } @@ -620,3 +619,318 @@ impl std::fmt::Display for SpecialFormType { ) } } + +/// Various categories of special forms. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum SpecialFormCategory { + /// Special forms that are simple aliases to classes elsewhere in the standard library. + LegacyStdlibAlias(LegacyStdlibAlias), + + /// Special forms that are type qualifiers + TypeQualifier(TypeQualifier), + + /// The special form `typing.Tuple`. + /// + /// While this is technically an alias to `builtins.tuple`, it requires special handling + /// for type-expression parsing. + Tuple, + + /// The special form `typing.Type`. + /// + /// While this is technically an alias to `builtins.type`, it requires special handling + /// for type-expression parsing. + Type, + + /// The special form `Callable`. + /// + /// While `typing.Callable` aliases `collections.abc.Callable`, we view both objects + /// as inhabiting the same special form type internally. Moreover, `Callable` requires + /// special handling for both type-expression parsing and `isinstance`/`issubclass` + /// narrowing. + Callable, + + /// Everything else... + Other(MiscSpecialForm), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum LegacyStdlibAlias { + List, + Dict, + Set, + FrozenSet, + ChainMap, + Counter, + DefaultDict, + Deque, + OrderedDict, +} + +impl LegacyStdlibAlias { + pub(super) const fn alias_spec(self) -> AliasSpec { + let (class, expected_argument_number) = match self { + LegacyStdlibAlias::List => (KnownClass::List, 1), + LegacyStdlibAlias::Dict => (KnownClass::Dict, 2), + LegacyStdlibAlias::Set => (KnownClass::Set, 1), + LegacyStdlibAlias::FrozenSet => (KnownClass::FrozenSet, 1), + LegacyStdlibAlias::ChainMap => (KnownClass::ChainMap, 2), + LegacyStdlibAlias::Counter => (KnownClass::Counter, 1), + LegacyStdlibAlias::DefaultDict => (KnownClass::DefaultDict, 2), + LegacyStdlibAlias::Deque => (KnownClass::Deque, 1), + LegacyStdlibAlias::OrderedDict => (KnownClass::OrderedDict, 2), + }; + + AliasSpec { + class, + expected_argument_number, + } + } + + pub(super) const fn aliased_class(self) -> KnownClass { + self.alias_spec().class + } +} + +impl From for SpecialFormType { + fn from(value: LegacyStdlibAlias) -> Self { + match value { + LegacyStdlibAlias::List => SpecialFormType::List, + LegacyStdlibAlias::Dict => SpecialFormType::Dict, + LegacyStdlibAlias::Set => SpecialFormType::Set, + LegacyStdlibAlias::FrozenSet => SpecialFormType::FrozenSet, + LegacyStdlibAlias::ChainMap => SpecialFormType::ChainMap, + LegacyStdlibAlias::Counter => SpecialFormType::Counter, + LegacyStdlibAlias::DefaultDict => SpecialFormType::DefaultDict, + LegacyStdlibAlias::Deque => SpecialFormType::Deque, + LegacyStdlibAlias::OrderedDict => SpecialFormType::OrderedDict, + } + } +} + +impl std::fmt::Display for LegacyStdlibAlias { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + SpecialFormType::from(*self).fmt(f) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] +pub(super) enum TypeQualifier { + ReadOnly, + Final, + ClassVar, + Required, + NotRequired, +} + +impl From for SpecialFormType { + fn from(value: TypeQualifier) -> Self { + match value { + TypeQualifier::ReadOnly => SpecialFormType::ReadOnly, + TypeQualifier::Final => SpecialFormType::Final, + TypeQualifier::ClassVar => SpecialFormType::ClassVar, + TypeQualifier::Required => SpecialFormType::Required, + TypeQualifier::NotRequired => SpecialFormType::NotRequired, + } + } +} + +impl From for TypeQualifiers { + fn from(value: TypeQualifier) -> Self { + match value { + TypeQualifier::ReadOnly => TypeQualifiers::READ_ONLY, + TypeQualifier::Final => TypeQualifiers::FINAL, + TypeQualifier::ClassVar => TypeQualifiers::CLASS_VAR, + TypeQualifier::Required => TypeQualifiers::REQUIRED, + TypeQualifier::NotRequired => TypeQualifiers::NOT_REQUIRED, + } + } +} + +impl std::fmt::Display for TypeQualifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + SpecialFormType::from(*self).fmt(f) + } +} + +/// Information regarding the [`KnownClass`] a [`LegacyStdlibAlias`] refers to. +pub(super) struct AliasSpec { + pub(super) class: KnownClass, + pub(super) expected_argument_number: usize, +} + +/// Enumeration of special forms that are not aliases to classes or special constructs +/// elsewhere in the Python standard library. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum MiscSpecialForm { + Any, + Annotated, + Literal, + LiteralString, + Optional, + Union, + NoReturn, + Never, + Unknown, + AlwaysTruthy, + AlwaysFalsy, + Not, + Intersection, + TypeOf, + CallableTypeOf, + Top, + Bottom, + TypingSelf, + Concatenate, + Unpack, + TypeAlias, + TypeGuard, + TypedDict, + TypeIs, + Protocol, + Generic, + NamedTuple, +} + +impl MiscSpecialForm { + pub(super) fn in_type_expression<'db>( + self, + db: &'db dyn Db, + scope_id: ScopeId<'db>, + typevar_binding_context: Option>, + ) -> Result, InvalidTypeExpressionError<'db>> { + match self { + Self::Never | Self::NoReturn => Ok(Type::Never), + Self::LiteralString => Ok(Type::literal_string()), + Self::Any => Ok(Type::any()), + Self::Unknown => Ok(Type::unknown()), + Self::AlwaysTruthy => Ok(Type::AlwaysTruthy), + Self::AlwaysFalsy => Ok(Type::AlwaysFalsy), + + // Special case: `NamedTuple` in a type expression is understood to describe the type + // `tuple[object, ...] & `. + // This isn't very principled (since at runtime, `NamedTuple` is just a function), + // but it appears to be what users often expect, and it improves compatibility with + // other type checkers such as mypy. + // See conversation in https://github.com/astral-sh/ruff/pull/19915. + Self::NamedTuple => Ok(IntersectionBuilder::new(db) + .positive_elements([ + Type::homogeneous_tuple(db, Type::object()), + KnownClass::NamedTupleLike.to_instance(db), + ]) + .build()), + + Self::TypingSelf => { + let index = semantic_index(db, scope_id.file(db)); + let Some(class) = nearest_enclosing_class(db, index, scope_id) else { + return Err(InvalidTypeExpressionError { + fallback_type: Type::unknown(), + invalid_expressions: smallvec::smallvec_inline![ + InvalidTypeExpression::InvalidType(self.into(), scope_id) + ], + }); + }; + + Ok( + typing_self(db, scope_id, typevar_binding_context, class.into()) + .map(Type::TypeVar) + .unwrap_or(self.into()), + ) + } + // We ensure that `typing.TypeAlias` used in the expected position (annotating an + // annotated assignment statement) doesn't reach here. Using it in any other type + // expression is an error. + Self::TypeAlias => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::TypeAlias], + fallback_type: Type::unknown(), + }), + Self::TypedDict => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::TypedDict], + fallback_type: Type::unknown(), + }), + + Self::Literal | Self::Union | Self::Intersection => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![ + InvalidTypeExpression::RequiresArguments(self.into()) + ], + fallback_type: Type::unknown(), + }), + + Self::Protocol => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Protocol], + fallback_type: Type::unknown(), + }), + Self::Generic => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Generic], + fallback_type: Type::unknown(), + }), + + Self::Optional + | Self::Not + | Self::Top + | Self::Bottom + | Self::TypeOf + | Self::TypeIs + | Self::TypeGuard + | Self::Unpack + | Self::CallableTypeOf => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![ + InvalidTypeExpression::RequiresOneArgument(self.into()) + ], + fallback_type: Type::unknown(), + }), + + Self::Annotated | Self::Concatenate => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec_inline![ + InvalidTypeExpression::RequiresTwoArguments(self.into()) + ], + fallback_type: Type::unknown(), + }), + } + } +} + +impl From for SpecialFormType { + fn from(value: MiscSpecialForm) -> Self { + match value { + MiscSpecialForm::Any => SpecialFormType::Any, + MiscSpecialForm::Annotated => SpecialFormType::Annotated, + MiscSpecialForm::Literal => SpecialFormType::Literal, + MiscSpecialForm::LiteralString => SpecialFormType::LiteralString, + MiscSpecialForm::Optional => SpecialFormType::Optional, + MiscSpecialForm::Union => SpecialFormType::Union, + MiscSpecialForm::NoReturn => SpecialFormType::NoReturn, + MiscSpecialForm::Never => SpecialFormType::Never, + MiscSpecialForm::Unknown => SpecialFormType::Unknown, + MiscSpecialForm::AlwaysTruthy => SpecialFormType::AlwaysTruthy, + MiscSpecialForm::AlwaysFalsy => SpecialFormType::AlwaysFalsy, + MiscSpecialForm::Not => SpecialFormType::Not, + MiscSpecialForm::Intersection => SpecialFormType::Intersection, + MiscSpecialForm::TypeOf => SpecialFormType::TypeOf, + MiscSpecialForm::CallableTypeOf => SpecialFormType::CallableTypeOf, + MiscSpecialForm::Top => SpecialFormType::Top, + MiscSpecialForm::Bottom => SpecialFormType::Bottom, + MiscSpecialForm::TypingSelf => SpecialFormType::TypingSelf, + MiscSpecialForm::Concatenate => SpecialFormType::Concatenate, + MiscSpecialForm::Unpack => SpecialFormType::Unpack, + MiscSpecialForm::TypeAlias => SpecialFormType::TypeAlias, + MiscSpecialForm::TypeGuard => SpecialFormType::TypeGuard, + MiscSpecialForm::TypedDict => SpecialFormType::TypedDict, + MiscSpecialForm::TypeIs => SpecialFormType::TypeIs, + MiscSpecialForm::Protocol => SpecialFormType::Protocol, + MiscSpecialForm::Generic => SpecialFormType::Generic, + MiscSpecialForm::NamedTuple => SpecialFormType::NamedTuple, + } + } +} + +impl From for Type<'_> { + fn from(value: MiscSpecialForm) -> Self { + Type::SpecialForm(SpecialFormType::from(value)) + } +} + +impl std::fmt::Display for MiscSpecialForm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + SpecialFormType::from(*self).fmt(f) + } +}