diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/union.md b/crates/red_knot_python_semantic/resources/mdtest/call/union.md index c72196c695..15ef569ca2 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/union.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/union.md @@ -161,3 +161,17 @@ def _(flag: bool): reveal_type(repr("string")) # revealed: Literal["'string'"] reveal_type(f("string")) # revealed: Literal["string", "'string'"] ``` + +## Cannot use an argument as both a value and a type form + +```py +from knot_extensions import is_fully_static + +def _(flag: bool): + if flag: + f = repr + else: + f = is_fully_static + # error: [conflicting-argument-forms] "Argument is used as both a value and a type form in call" + reveal_type(f(int)) # revealed: str | Literal[True] +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/directives/cast.md b/crates/red_knot_python_semantic/resources/mdtest/directives/cast.md index 17ee202dad..4203d83fe9 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/directives/cast.md +++ b/crates/red_knot_python_semantic/resources/mdtest/directives/cast.md @@ -13,17 +13,16 @@ reveal_type(cast("str", True)) # revealed: str reveal_type(cast(int | str, 1)) # revealed: int | str +reveal_type(cast(val="foo", typ=int)) # revealed: int + # error: [invalid-type-form] reveal_type(cast(Literal, True)) # revealed: Unknown # error: [invalid-type-form] reveal_type(cast(1, True)) # revealed: Unknown -# TODO: These should be errors +# error: [missing-argument] "No argument provided for required parameter `val` of function `cast`" cast(str) +# error: [too-many-positional-arguments] "Too many positional arguments to function `cast`: expected 2, got 3" cast(str, b"ar", "foo") - -# TODO: Either support keyword arguments properly, -# or give a comprehensible error message saying they're unsupported -cast(val="foo", typ=int) # error: [unresolved-reference] "Name `foo` used when not defined" ``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index cd94d3d95d..5797934d0d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -32,13 +32,13 @@ use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; use crate::symbol::{imported_symbol, Boundness, Symbol, SymbolAndQualifiers}; -use crate::types::call::{Bindings, CallArguments}; +use crate::types::call::{Bindings, CallArgumentTypes}; use crate::types::class_base::ClassBase; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; -use crate::types::signatures::{Parameter, ParameterKind, Parameters}; +use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters}; use crate::{Db, FxOrderSet, Module, Program}; pub(crate) use class::{Class, ClassLiteralType, InstanceType, KnownClass, KnownInstanceType}; @@ -1701,7 +1701,7 @@ impl<'db> Type<'db> { if let Symbol::Type(descr_get, descr_get_boundness) = descr_get { let return_ty = descr_get - .try_call(db, &CallArguments::positional([self, instance, owner])) + .try_call(db, CallArgumentTypes::positional([self, instance, owner])) .map(|bindings| { if descr_get_boundness == Boundness::Bound { bindings.return_type(db) @@ -2040,28 +2040,28 @@ impl<'db> Type<'db> { InstanceFallbackShadowsNonDataDescriptor::No, ); - let custom_getattr_result = - || { - // Typeshed has a fake `__getattr__` on `types.ModuleType` to help out with dynamic imports. - // We explicitly hide it here to prevent arbitrary attributes from being available on modules. - if self.into_instance().is_some_and(|instance| { - instance.class.is_known(db, KnownClass::ModuleType) - }) { - return Symbol::Unbound.into(); - } + let custom_getattr_result = || { + // Typeshed has a fake `__getattr__` on `types.ModuleType` to help out with dynamic imports. + // We explicitly hide it here to prevent arbitrary attributes from being available on modules. + if self + .into_instance() + .is_some_and(|instance| instance.class.is_known(db, KnownClass::ModuleType)) + { + return Symbol::Unbound.into(); + } - self.try_call_dunder( - db, - "__getattr__", - &CallArguments::positional([Type::StringLiteral( - StringLiteralType::new(db, Box::from(name.as_str())), - )]), - ) - .map(|outcome| Symbol::bound(outcome.return_type(db))) - // TODO: Handle call errors here. - .unwrap_or(Symbol::Unbound) - .into() - }; + self.try_call_dunder( + db, + "__getattr__", + CallArgumentTypes::positional([Type::StringLiteral( + StringLiteralType::new(db, Box::from(name.as_str())), + )]), + ) + .map(|outcome| Symbol::bound(outcome.return_type(db))) + // TODO: Handle call errors here. + .unwrap_or(Symbol::Unbound) + .into() + }; match result { member @ SymbolAndQualifiers { @@ -2174,7 +2174,7 @@ impl<'db> Type<'db> { } }; - match self.try_call_dunder(db, "__bool__", &CallArguments::none()) { + match self.try_call_dunder(db, "__bool__", CallArgumentTypes::none()) { Ok(outcome) => { let return_type = outcome.return_type(db); if !return_type.is_assignable_to(db, KnownClass::Bool.to_instance(db)) { @@ -2312,7 +2312,7 @@ impl<'db> Type<'db> { return usize_len.try_into().ok().map(Type::IntLiteral); } - let return_ty = match self.try_call_dunder(db, "__len__", &CallArguments::none()) { + let return_ty = match self.try_call_dunder(db, "__len__", CallArgumentTypes::none()) { Ok(bindings) => bindings.return_type(db), Err(CallDunderError::PossiblyUnbound(bindings)) => bindings.return_type(db), @@ -2362,42 +2362,23 @@ impl<'db> Type<'db> { [ Signature::new( Parameters::new([ - Parameter::new( - Some(Type::none(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("instance")), - default_ty: None, - }, - ), - Parameter::new( - Some(KnownClass::Type.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("owner")), - default_ty: None, - }, - ), + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(KnownClass::Type.to_instance(db)), ]), None, ), Signature::new( Parameters::new([ - Parameter::new( - Some(not_none), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("instance")), - default_ty: None, - }, - ), - Parameter::new( - Some(UnionType::from_elements( + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(not_none), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(UnionType::from_elements( db, [KnownClass::Type.to_instance(db), Type::none(db)], - )), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("owner")), - default_ty: Some(Type::none(db)), - }, - ), + )) + .with_default_type(Type::none(db)), ]), None, ), @@ -2419,56 +2400,27 @@ impl<'db> Type<'db> { [ Signature::new( Parameters::new([ - Parameter::new( - Some(KnownClass::FunctionType.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("self")), - default_ty: None, - }, - ), - Parameter::new( - Some(Type::none(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("instance")), - default_ty: None, - }, - ), - Parameter::new( - Some(KnownClass::Type.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("owner")), - default_ty: None, - }, - ), + Parameter::positional_only(Some(Name::new_static("self"))) + .with_annotated_type(KnownClass::FunctionType.to_instance(db)), + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(Type::none(db)), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(KnownClass::Type.to_instance(db)), ]), None, ), Signature::new( Parameters::new([ - Parameter::new( - Some(KnownClass::FunctionType.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("self")), - default_ty: None, - }, - ), - Parameter::new( - Some(not_none), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("instance")), - default_ty: None, - }, - ), - Parameter::new( - Some(UnionType::from_elements( + Parameter::positional_only(Some(Name::new_static("self"))) + .with_annotated_type(KnownClass::FunctionType.to_instance(db)), + Parameter::positional_only(Some(Name::new_static("instance"))) + .with_annotated_type(not_none), + Parameter::positional_only(Some(Name::new_static("owner"))) + .with_annotated_type(UnionType::from_elements( db, [KnownClass::Type.to_instance(db), Type::none(db)], - )), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("owner")), - default_ty: Some(Type::none(db)), - }, - ), + )) + .with_default_type(Type::none(db)), ]), None, ), @@ -2477,10 +2429,89 @@ impl<'db> Type<'db> { Signatures::single(signature) } - Type::FunctionLiteral(function_type) => Signatures::single(CallableSignature::single( - self, - function_type.signature(db).clone(), - )), + Type::FunctionLiteral(function_type) => match function_type.known(db) { + Some( + KnownFunction::IsEquivalentTo + | KnownFunction::IsSubtypeOf + | KnownFunction::IsAssignableTo + | KnownFunction::IsDisjointFrom + | KnownFunction::IsGradualEquivalentTo, + ) => { + let signature = CallableSignature::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("a"))) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("b"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(KnownClass::Bool.to_instance(db)), + ), + ); + Signatures::single(signature) + } + + Some( + KnownFunction::IsFullyStatic + | KnownFunction::IsSingleton + | KnownFunction::IsSingleValued, + ) => { + let signature = CallableSignature::single( + self, + Signature::new( + Parameters::new([Parameter::positional_only(Some(Name::new_static( + "a", + ))) + .type_form() + .with_annotated_type(Type::any())]), + Some(KnownClass::Bool.to_instance(db)), + ), + ); + Signatures::single(signature) + } + + Some(KnownFunction::AssertType) => { + let signature = CallableSignature::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_only(Some(Name::new_static("value"))) + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("type"))) + .type_form() + .with_annotated_type(Type::any()), + ]), + Some(Type::none(db)), + ), + ); + Signatures::single(signature) + } + + Some(KnownFunction::Cast) => { + let signature = CallableSignature::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_or_keyword(Name::new_static("typ")) + .type_form() + .with_annotated_type(Type::any()), + Parameter::positional_or_keyword(Name::new_static("val")) + .with_annotated_type(Type::any()), + ]), + Some(Type::any()), + ), + ); + Signatures::single(signature) + } + + _ => Signatures::single(CallableSignature::single( + self, + function_type.signature(db).clone(), + )), + }, Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) { Some(KnownClass::Bool) => { @@ -2491,13 +2522,11 @@ impl<'db> Type<'db> { let signature = CallableSignature::single( self, Signature::new( - Parameters::new([Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("o")), - default_ty: Some(Type::BooleanLiteral(false)), - }, - )]), + Parameters::new([Parameter::positional_only(Some(Name::new_static( + "o", + ))) + .with_annotated_type(Type::any()) + .with_default_type(Type::BooleanLiteral(false))]), Some(KnownClass::Bool.to_instance(db)), ), ); @@ -2516,38 +2545,21 @@ impl<'db> Type<'db> { self, [ Signature::new( - Parameters::new([Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("o")), - default_ty: Some(Type::string_literal(db, "")), - }, - )]), + Parameters::new([Parameter::positional_only(Some( + Name::new_static("o"), + )) + .with_annotated_type(Type::any()) + .with_default_type(Type::string_literal(db, ""))]), Some(KnownClass::Str.to_instance(db)), ), Signature::new( Parameters::new([ - Parameter::new( - Some(Type::any()), // TODO: ReadableBuffer - ParameterKind::PositionalOnly { - name: Some(Name::new_static("o")), - default_ty: None, - }, - ), - Parameter::new( - Some(KnownClass::Str.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("encoding")), - default_ty: None, - }, - ), - Parameter::new( - Some(KnownClass::Str.to_instance(db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("errors")), - default_ty: None, - }, - ), + Parameter::positional_only(Some(Name::new_static("o"))) + .with_annotated_type(Type::any()), // TODO: ReadableBuffer + Parameter::positional_only(Some(Name::new_static("encoding"))) + .with_annotated_type(KnownClass::Str.to_instance(db)), + Parameter::positional_only(Some(Name::new_static("errors"))) + .with_annotated_type(KnownClass::Str.to_instance(db)), ]), Some(KnownClass::Str.to_instance(db)), ), @@ -2568,38 +2580,20 @@ impl<'db> Type<'db> { self, [ Signature::new( - Parameters::new([Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("o")), - default_ty: None, - }, - )]), + Parameters::new([Parameter::positional_only(Some( + Name::new_static("o"), + )) + .with_annotated_type(Type::any())]), Some(KnownClass::Type.to_instance(db)), ), Signature::new( Parameters::new([ - Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("o")), - default_ty: None, - }, - ), - Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("bases")), - default_ty: None, - }, - ), - Parameter::new( - Some(Type::any()), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("dict")), - default_ty: None, - }, - ), + Parameter::positional_only(Some(Name::new_static("o"))) + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("bases"))) + .with_annotated_type(Type::any()), + Parameter::positional_only(Some(Name::new_static("dict"))) + .with_annotated_type(Type::any()), ]), Some(KnownClass::Type.to_instance(db)), ), @@ -2680,282 +2674,11 @@ impl<'db> Type<'db> { fn try_call( self, db: &'db dyn Db, - arguments: &CallArguments<'_, 'db>, + mut argument_types: CallArgumentTypes<'_, 'db>, ) -> Result, CallError<'db>> { let signatures = self.signatures(db); - let mut bindings = Bindings::bind(db, &signatures, arguments)?; - for binding in &mut bindings { - // For certain known callables, we have special-case logic to determine the return type - // in a way that isn't directly expressible in the type system. Each special case - // listed here should have a corresponding clause above in `signatures`. - let binding_type = binding.callable_type; - let Some((overload_index, overload)) = binding.matching_overload_mut() else { - continue; - }; - - match binding_type { - Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { - if function.has_known_class_decorator(db, KnownClass::Classmethod) - && function.decorators(db).len() == 1 - { - if let Some(owner) = arguments.second_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, owner), - ))); - } else if let Some(instance) = arguments.first_argument() { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, instance.to_meta_type(db)), - ))); - } - } else if let Some(first) = arguments.first_argument() { - if first.is_none(db) { - overload.set_return_type(Type::FunctionLiteral(function)); - } else { - overload.set_return_type(Type::Callable(CallableType::BoundMethod( - BoundMethodType::new(db, function, first), - ))); - } - } - } - - Type::Callable(CallableType::WrapperDescriptorDunderGet) => { - if let Some(function_ty @ Type::FunctionLiteral(function)) = - arguments.first_argument() - { - if function.has_known_class_decorator(db, KnownClass::Classmethod) - && function.decorators(db).len() == 1 - { - if let Some(owner) = arguments.third_argument() { - overload.set_return_type(Type::Callable( - CallableType::BoundMethod(BoundMethodType::new( - db, function, owner, - )), - )); - } else if let Some(instance) = arguments.second_argument() { - overload.set_return_type(Type::Callable( - CallableType::BoundMethod(BoundMethodType::new( - db, - function, - instance.to_meta_type(db), - )), - )); - } - } else { - match (arguments.second_argument(), arguments.third_argument()) { - (Some(instance), _) if instance.is_none(db) => { - overload.set_return_type(function_ty); - } - - ( - Some(Type::KnownInstance(KnownInstanceType::TypeAliasType( - type_alias, - ))), - Some(Type::ClassLiteral(ClassLiteralType { class })), - ) if class.is_known(db, KnownClass::TypeAliasType) - && function.name(db) == "__name__" => - { - overload.set_return_type(Type::string_literal( - db, - type_alias.name(db), - )); - } - - ( - Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), - Some(Type::ClassLiteral(ClassLiteralType { class })), - ) if class.is_known(db, KnownClass::TypeVar) - && function.name(db) == "__name__" => - { - overload.set_return_type(Type::string_literal( - db, - typevar.name(db), - )); - } - - (Some(_), _) - if function - .has_known_class_decorator(db, KnownClass::Property) => - { - overload.set_return_type(todo_type!("@property")); - } - - (Some(instance), _) => { - overload.set_return_type(Type::Callable( - CallableType::BoundMethod(BoundMethodType::new( - db, function, instance, - )), - )); - } - - (None, _) => {} - } - } - } - } - - Type::FunctionLiteral(function_type) => match function_type.known(db) { - Some(KnownFunction::IsEquivalentTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_equivalent_to(db, *ty_b), - )); - } - } - - Some(KnownFunction::IsSubtypeOf) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_subtype_of(db, *ty_b), - )); - } - } - - Some(KnownFunction::IsAssignableTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_assignable_to(db, *ty_b), - )); - } - } - - Some(KnownFunction::IsDisjointFrom) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_disjoint_from(db, *ty_b), - )); - } - } - - Some(KnownFunction::IsGradualEquivalentTo) => { - if let [ty_a, ty_b] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_gradual_equivalent_to(db, *ty_b), - )); - } - } - - Some(KnownFunction::IsFullyStatic) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); - } - } - - Some(KnownFunction::IsSingleton) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); - } - } - - Some(KnownFunction::IsSingleValued) => { - if let [ty] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); - } - } - - Some(KnownFunction::Len) => { - if let [first_arg] = overload.parameter_types() { - if let Some(len_ty) = first_arg.len(db) { - overload.set_return_type(len_ty); - } - }; - } - - Some(KnownFunction::Repr) => { - if let [first_arg] = overload.parameter_types() { - overload.set_return_type(first_arg.repr(db)); - }; - } - - Some(KnownFunction::Cast) => { - // TODO: Use `.parameter_types()` exclusively when overloads are supported. - if let Some(casted_ty) = arguments.first_argument() { - if let [_, _] = overload.parameter_types() { - overload.set_return_type(casted_ty); - } - }; - } - - Some(KnownFunction::Overload) => { - overload.set_return_type(todo_type!("overload(..) return type")); - } - - Some(KnownFunction::GetattrStatic) => { - let [instance_ty, attr_name, default] = overload.parameter_types() else { - continue; - }; - - let Some(attr_name) = attr_name.into_string_literal() else { - continue; - }; - - let default = if default.is_unknown() { - Type::Never - } else { - *default - }; - - let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); - - // TODO: we could emit a diagnostic here (if default is not set) - overload.set_return_type( - match instance_ty.static_member(db, attr_name.value(db)) { - Symbol::Type(ty, Boundness::Bound) => { - if instance_ty.is_fully_static(db) { - ty - } else { - // Here, we attempt to model the fact that an attribute lookup on - // a non-fully static type could fail. This is an approximation, - // as there are gradual types like `tuple[Any]`, on which a lookup - // of (e.g. of the `index` method) would always succeed. - - union_with_default(ty) - } - } - Symbol::Type(ty, Boundness::PossiblyUnbound) => { - union_with_default(ty) - } - Symbol::Unbound => default, - }, - ); - } - - _ => {} - }, - - Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) { - Some(KnownClass::Bool) => { - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.bool(db).into_type(db)) - .unwrap_or(Type::BooleanLiteral(false)), - ); - } - - Some(KnownClass::Str) if overload_index == 0 => { - overload.set_return_type( - arguments - .first_argument() - .map(|arg| arg.str(db)) - .unwrap_or_else(|| Type::string_literal(db, "")), - ); - } - - Some(KnownClass::Type) if overload_index == 0 => { - if let Some(arg) = arguments.first_argument() { - overload.set_return_type(arg.to_meta_type(db)); - } - } - - _ => {} - }, - - // Not a special case - _ => {} - } - } - - Ok(bindings) + Bindings::match_parameters(signatures, &mut argument_types) + .check_types(db, &mut argument_types) } /// Look up a dunder method on the meta-type of `self` and call it. @@ -2966,7 +2689,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, name: &str, - arguments: &CallArguments<'_, 'db>, + mut argument_types: CallArgumentTypes<'_, 'db>, ) -> Result, CallDunderError<'db>> { match self .member_lookup_with_policy(db, name.into(), MemberLookupPolicy::NoInstanceFallback) @@ -2974,7 +2697,8 @@ impl<'db> Type<'db> { { Symbol::Type(dunder_callable, boundness) => { let signatures = dunder_callable.signatures(db); - let bindings = Bindings::bind(db, &signatures, arguments)?; + let bindings = Bindings::match_parameters(signatures, &mut argument_types) + .check_types(db, &mut argument_types)?; if boundness == Boundness::PossiblyUnbound { return Err(CallDunderError::PossiblyUnbound(Box::new(bindings))); } @@ -3010,19 +2734,19 @@ impl<'db> Type<'db> { self.try_call_dunder( db, "__getitem__", - &CallArguments::positional([KnownClass::Int.to_instance(db)]), + CallArgumentTypes::positional([KnownClass::Int.to_instance(db)]), ) .map(|dunder_getitem_outcome| dunder_getitem_outcome.return_type(db)) }; let try_call_dunder_next_on_iterator = |iterator: Type<'db>| { iterator - .try_call_dunder(db, "__next__", &CallArguments::none()) + .try_call_dunder(db, "__next__", CallArgumentTypes::none()) .map(|dunder_next_outcome| dunder_next_outcome.return_type(db)) }; let dunder_iter_result = self - .try_call_dunder(db, "__iter__", &CallArguments::none()) + .try_call_dunder(db, "__iter__", CallArgumentTypes::none()) .map(|dunder_iter_outcome| dunder_iter_outcome.return_type(db)); match dunder_iter_result { @@ -3106,11 +2830,11 @@ impl<'db> Type<'db> { /// pass /// ``` fn try_enter(self, db: &'db dyn Db) -> Result, ContextManagerError<'db>> { - let enter = self.try_call_dunder(db, "__enter__", &CallArguments::none()); + let enter = self.try_call_dunder(db, "__enter__", CallArgumentTypes::none()); let exit = self.try_call_dunder( db, "__exit__", - &CallArguments::positional([Type::none(db), Type::none(db), Type::none(db)]), + CallArgumentTypes::positional([Type::none(db), Type::none(db), Type::none(db)]), ); // TODO: Make use of Protocols when we support it (the manager be assignable to `contextlib.AbstractContextManager`). @@ -3899,7 +3623,7 @@ impl<'db> IterationError<'db> { Self::IterCallError(_, dunder_iter_bindings) => dunder_iter_bindings .return_type(db) - .try_call_dunder(db, "__next__", &CallArguments::none()) + .try_call_dunder(db, "__next__", CallArgumentTypes::none()) .map(|dunder_next_outcome| Some(dunder_next_outcome.return_type(db))) .unwrap_or_else(|dunder_next_call_error| dunder_next_call_error.return_type(db)), @@ -3952,7 +3676,7 @@ impl<'db> IterationError<'db> { because its `__iter__` attribute has type `{dunder_iter_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), + dunder_iter_type = bindings.callable_type().display(db), )), Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => { report_not_iterable(format_args!( @@ -3960,7 +3684,7 @@ impl<'db> IterationError<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), + dunder_iter_type = bindings.callable_type().display(db), )); } Self::IterCallError(CallErrorKind::PossiblyNotCallable, bindings) => { @@ -3969,7 +3693,7 @@ impl<'db> IterationError<'db> { because its `__iter__` attribute (with type `{dunder_iter_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), + dunder_iter_type = bindings.callable_type().display(db), )); } Self::IterCallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( @@ -3983,7 +3707,7 @@ impl<'db> IterationError<'db> { because its `__iter__` method (with type `{dunder_iter_type}`) \ may have an invalid signature (expected `def __iter__(self): ...`)", iterable_type = iterable_type.display(db), - dunder_iter_type = bindings.callable_type.display(db), + dunder_iter_type = bindings.callable_type().display(db), )), Self::IterReturnsInvalidIterator { @@ -4054,7 +3778,7 @@ impl<'db> IterationError<'db> { and its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4069,7 +3793,7 @@ impl<'db> IterationError<'db> { and its `__getitem__` attribute (with type `{dunder_getitem_type}`) \ may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )); } CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( @@ -4089,7 +3813,7 @@ impl<'db> IterationError<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), } @@ -4110,7 +3834,7 @@ impl<'db> IterationError<'db> { its `__getitem__` attribute has type `{dunder_getitem_type}`, \ which is not callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), CallDunderError::CallError(CallErrorKind::PossiblyNotCallable, bindings) if bindings.is_single() => report_not_iterable(format_args!( "Object of type `{iterable_type}` may not be iterable \ @@ -4124,7 +3848,7 @@ impl<'db> IterationError<'db> { because it has no `__iter__` method and its `__getitem__` attribute \ (with type `{dunder_getitem_type}`) may not be callable", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )); } CallDunderError::CallError(CallErrorKind::BindingError, bindings) if bindings.is_single() => report_not_iterable(format_args!( @@ -4144,7 +3868,7 @@ impl<'db> IterationError<'db> { (expected a signature at least as permissive as \ `def __getitem__(self, key: int): ...`)", iterable_type = iterable_type.display(db), - dunder_getitem_type = bindings.callable_type.display(db), + dunder_getitem_type = bindings.callable_type().display(db), )), } } @@ -4535,35 +4259,6 @@ impl KnownFunction { | Self::StaticAssert => module.is_knot_extensions(), } } - - /// Return the [`ParameterExpectations`] for this function. - const fn parameter_expectations(self) -> ParameterExpectations { - match self { - Self::IsFullyStatic | Self::IsSingleton | Self::IsSingleValued => { - ParameterExpectations::SingleTypeExpression - } - - Self::IsEquivalentTo - | Self::IsSubtypeOf - | Self::IsAssignableTo - | Self::IsDisjointFrom - | Self::IsGradualEquivalentTo => ParameterExpectations::TwoTypeExpressions, - - Self::AssertType => ParameterExpectations::ValueExpressionAndTypeExpression, - Self::Cast => ParameterExpectations::TypeExpressionAndValueExpression, - - Self::IsInstance - | Self::IsSubclass - | Self::Len - | Self::Repr - | Self::Overload - | Self::Final - | Self::NoTypeCheck - | Self::RevealType - | Self::GetattrStatic - | Self::StaticAssert => ParameterExpectations::AllValueExpressions, - } - } } /// This type represents bound method objects that are created when a method is accessed @@ -4654,11 +4349,11 @@ impl<'db> GeneralCallableType<'db> { match (self_parameter.kind(), other_parameter.kind()) { ( ParameterKind::PositionalOnly { - default_ty: self_default, + default_type: self_default, .. }, ParameterKind::PositionalOnly { - default_ty: other_default, + default_type: other_default, .. }, ) if self_default.is_some() == other_default.is_some() => {} @@ -4666,11 +4361,11 @@ impl<'db> GeneralCallableType<'db> { ( ParameterKind::PositionalOrKeyword { name: self_name, - default_ty: self_default, + default_type: self_default, }, ParameterKind::PositionalOrKeyword { name: other_name, - default_ty: other_default, + default_type: other_default, }, ) if self_default.is_some() == other_default.is_some() && self_name == other_name => {} @@ -4680,11 +4375,11 @@ impl<'db> GeneralCallableType<'db> { ( ParameterKind::KeywordOnly { name: self_name, - default_ty: self_default, + default_type: self_default, }, ParameterKind::KeywordOnly { name: other_name, - default_ty: other_default, + default_type: other_default, }, ) if self_default.is_some() == other_default.is_some() && self_name == other_name => {} @@ -4841,13 +4536,13 @@ impl<'db> GeneralCallableType<'db> { match next_parameter { EitherOrBoth::Left(self_parameter) => match self_parameter.kind() { - ParameterKind::PositionalOnly { default_ty, .. } - | ParameterKind::PositionalOrKeyword { default_ty, .. } - | ParameterKind::KeywordOnly { default_ty, .. } => { + ParameterKind::PositionalOnly { default_type, .. } + | ParameterKind::PositionalOrKeyword { default_type, .. } + | ParameterKind::KeywordOnly { default_type, .. } => { // For `self <: other` to be valid, if there are no more parameters in // `other`, then the non-variadic parameters in `self` must have a default // value. - if default_ty.is_none() { + if default_type.is_none() { return false; } } @@ -4867,15 +4562,15 @@ impl<'db> GeneralCallableType<'db> { match (self_parameter.kind(), other_parameter.kind()) { ( ParameterKind::PositionalOnly { - default_ty: self_default, + default_type: self_default, .. } | ParameterKind::PositionalOrKeyword { - default_ty: self_default, + default_type: self_default, .. }, ParameterKind::PositionalOnly { - default_ty: other_default, + default_type: other_default, .. }, ) => { @@ -4893,11 +4588,11 @@ impl<'db> GeneralCallableType<'db> { ( ParameterKind::PositionalOrKeyword { name: self_name, - default_ty: self_default, + default_type: self_default, }, ParameterKind::PositionalOrKeyword { name: other_name, - default_ty: other_default, + default_type: other_default, }, ) => { if self_name != other_name { @@ -5020,16 +4715,16 @@ impl<'db> GeneralCallableType<'db> { match other_parameter.kind() { ParameterKind::KeywordOnly { name: other_name, - default_ty: other_default, + default_type: other_default, } => { if let Some(self_parameter) = self_keywords.remove(other_name) { match self_parameter.kind() { ParameterKind::PositionalOrKeyword { - default_ty: self_default, + default_type: self_default, .. } | ParameterKind::KeywordOnly { - default_ty: self_default, + default_type: self_default, .. } => { if self_default.is_none() && other_default.is_some() { @@ -5123,69 +4818,6 @@ pub enum CallableType<'db> { WrapperDescriptorDunderGet, } -/// Describes whether the parameters in a function expect value expressions or type expressions. -/// -/// Whether a specific parameter in the function expects a type expression can be queried -/// using [`ParameterExpectations::expectation_at_index`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -enum ParameterExpectations { - /// All parameters in the function expect value expressions - #[default] - AllValueExpressions, - /// The first parameter in the function expects a type expression - SingleTypeExpression, - /// The first two parameters in the function expect type expressions - TwoTypeExpressions, - /// The first parameter in the function expects a value expression, - /// and the second expects a type expression - ValueExpressionAndTypeExpression, - /// The first parameter in the function expects a type expression, - /// and the second expects a value expression - TypeExpressionAndValueExpression, -} - -impl ParameterExpectations { - /// Query whether the parameter at `parameter_index` expects a value expression or a type expression - fn expectation_at_index(self, parameter_index: usize) -> ParameterExpectation { - match self { - Self::AllValueExpressions => ParameterExpectation::ValueExpression, - Self::SingleTypeExpression | Self::TypeExpressionAndValueExpression => { - if parameter_index == 0 { - ParameterExpectation::TypeExpression - } else { - ParameterExpectation::ValueExpression - } - } - Self::TwoTypeExpressions => { - if parameter_index < 2 { - ParameterExpectation::TypeExpression - } else { - ParameterExpectation::ValueExpression - } - } - Self::ValueExpressionAndTypeExpression => { - if parameter_index == 1 { - ParameterExpectation::TypeExpression - } else { - ParameterExpectation::ValueExpression - } - } - } - } -} - -/// Whether a single parameter in a given function expects a value expression or a [type expression] -/// -/// [type expression]: https://typing.readthedocs.io/en/latest/spec/annotations.html#type-and-annotation-expressions -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -enum ParameterExpectation { - /// The parameter expects a value expression - #[default] - ValueExpression, - /// The parameter expects a type expression - TypeExpression, -} - #[salsa::interned(debug)] pub struct ModuleLiteralType<'db> { /// The file in which this module was imported. diff --git a/crates/red_knot_python_semantic/src/types/call.rs b/crates/red_knot_python_semantic/src/types/call.rs index 84f64bcc49..a2761f91cb 100644 --- a/crates/red_knot_python_semantic/src/types/call.rs +++ b/crates/red_knot_python_semantic/src/types/call.rs @@ -4,14 +4,14 @@ use crate::Db; mod arguments; mod bind; -pub(super) use arguments::{Argument, CallArguments}; +pub(super) use arguments::{Argument, CallArgumentTypes, CallArguments}; pub(super) use bind::Bindings; /// Wraps a [`Bindings`] for an unsuccessful call with information about why the call was /// unsuccessful. /// /// The bindings are boxed so that we do not pass around large `Err` variants on the stack. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(crate) struct CallError<'db>(pub(crate) CallErrorKind, pub(crate) Box>); /// The reason why calling a type failed. @@ -32,7 +32,7 @@ pub(crate) enum CallErrorKind { PossiblyNotCallable, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(super) enum CallDunderError<'db> { /// The dunder attribute exists but it can't be called with the given arguments. /// diff --git a/crates/red_knot_python_semantic/src/types/call/arguments.rs b/crates/red_knot_python_semantic/src/types/call/arguments.rs index 814db5324b..cce0c81c0b 100644 --- a/crates/red_knot_python_semantic/src/types/call/arguments.rs +++ b/crates/red_knot_python_semantic/src/types/call/arguments.rs @@ -1,88 +1,128 @@ +use std::collections::VecDeque; +use std::ops::{Deref, DerefMut}; + use super::Type; -/// Typed arguments for a single call, in source order. +/// Arguments for a single call, in source order. #[derive(Clone, Debug, Default)] -pub(crate) struct CallArguments<'a, 'db>(Vec>); +pub(crate) struct CallArguments<'a>(VecDeque>); -impl<'a, 'db> CallArguments<'a, 'db> { - /// Create a [`CallArguments`] with no arguments. - pub(crate) fn none() -> Self { - Self(Vec::new()) +impl<'a> CallArguments<'a> { + /// Invoke a function with an optional extra synthetic argument (for a `self` or `cls` + /// parameter) prepended to the front of this argument list. (If `bound_self` is none, the + /// function is invoked with the unmodified argument list.) + pub(crate) fn with_self(&mut self, bound_self: Option>, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + if bound_self.is_some() { + self.0.push_front(Argument::Synthetic); + } + let result = f(self); + if bound_self.is_some() { + self.0.pop_front(); + } + result } - /// Create a [`CallArguments`] from an iterator over non-variadic positional argument types. - pub(crate) fn positional(positional_tys: impl IntoIterator>) -> Self { - positional_tys - .into_iter() - .map(Argument::Positional) - .collect() + pub(crate) fn len(&self) -> usize { + self.0.len() } - /// Prepend an extra positional argument. - pub(crate) fn with_self(&self, self_ty: Type<'db>) -> Self { - let mut arguments = Vec::with_capacity(self.0.len() + 1); - arguments.push(Argument::Synthetic(self_ty)); - arguments.extend_from_slice(&self.0); - Self(arguments) - } - - pub(crate) fn iter(&self) -> impl Iterator> { - self.0.iter() - } - - // TODO this should be eliminated in favor of [`bind_call`] - pub(crate) fn first_argument(&self) -> Option> { - self.0.first().map(Argument::ty) - } - - // TODO this should be eliminated in favor of [`bind_call`] - pub(crate) fn second_argument(&self) -> Option> { - self.0.get(1).map(Argument::ty) - } - - // TODO this should be eliminated in favor of [`bind_call`] - pub(crate) fn third_argument(&self) -> Option> { - self.0.get(2).map(Argument::ty) + pub(crate) fn iter(&self) -> impl Iterator> + '_ { + self.0.iter().copied() } } -impl<'db, 'a, 'b> IntoIterator for &'b CallArguments<'a, 'db> { - type Item = &'b Argument<'a, 'db>; - type IntoIter = std::slice::Iter<'b, Argument<'a, 'db>>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, 'db> FromIterator> for CallArguments<'a, 'db> { - fn from_iter>>(iter: T) -> Self { +impl<'a> FromIterator> for CallArguments<'a> { + fn from_iter>>(iter: T) -> Self { Self(iter.into_iter().collect()) } } -#[derive(Clone, Debug)] -pub(crate) enum Argument<'a, 'db> { +#[derive(Clone, Copy, Debug)] +pub(crate) enum Argument<'a> { /// The synthetic `self` or `cls` argument, which doesn't appear explicitly at the call site. - Synthetic(Type<'db>), + Synthetic, /// A positional argument. - Positional(Type<'db>), + Positional, /// A starred positional argument (e.g. `*args`). - Variadic(Type<'db>), + Variadic, /// A keyword argument (e.g. `a=1`). - Keyword { name: &'a str, ty: Type<'db> }, + Keyword(&'a str), /// The double-starred keywords argument (e.g. `**kwargs`). - Keywords(Type<'db>), + Keywords, } -impl<'db> Argument<'_, 'db> { - fn ty(&self) -> Type<'db> { - match self { - Self::Synthetic(ty) => *ty, - Self::Positional(ty) => *ty, - Self::Variadic(ty) => *ty, - Self::Keyword { name: _, ty } => *ty, - Self::Keywords(ty) => *ty, +/// Arguments for a single call, in source order, along with inferred types for each argument. +pub(crate) struct CallArgumentTypes<'a, 'db> { + arguments: CallArguments<'a>, + types: VecDeque>, +} + +impl<'a, 'db> CallArgumentTypes<'a, 'db> { + /// Create a [`CallArgumentTypes`] with no arguments. + pub(crate) fn none() -> Self { + let arguments = CallArguments::default(); + let types = VecDeque::default(); + Self { arguments, types } + } + + /// Create a [`CallArgumentTypes`] from an iterator over non-variadic positional argument + /// types. + pub(crate) fn positional(positional_tys: impl IntoIterator>) -> Self { + let types: VecDeque<_> = positional_tys.into_iter().collect(); + let arguments = CallArguments(vec![Argument::Positional; types.len()].into()); + Self { arguments, types } + } + + /// Create a new [`CallArgumentTypes`] to store the inferred types of the arguments in a + /// [`CallArguments`]. Uses the provided callback to infer each argument type. + pub(crate) fn new(arguments: CallArguments<'a>, mut f: F) -> Self + where + F: FnMut(usize, Argument<'a>) -> Type<'db>, + { + let types = arguments + .iter() + .enumerate() + .map(|(idx, argument)| f(idx, argument)) + .collect(); + Self { arguments, types } + } + + /// Invoke a function with an optional extra synthetic argument (for a `self` or `cls` + /// parameter) prepended to the front of this argument list. (If `bound_self` is none, the + /// function is invoked with the unmodified argument list.) + pub(crate) fn with_self(&mut self, bound_self: Option>, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + if let Some(bound_self) = bound_self { + self.arguments.0.push_front(Argument::Synthetic); + self.types.push_front(bound_self); } + let result = f(self); + if bound_self.is_some() { + self.arguments.0.pop_front(); + self.types.pop_front(); + } + result + } + + pub(crate) fn iter(&self) -> impl Iterator, Type<'db>)> + '_ { + self.arguments.iter().zip(self.types.iter().copied()) + } +} + +impl<'a> Deref for CallArgumentTypes<'a, '_> { + type Target = CallArguments<'a>; + fn deref(&self) -> &CallArguments<'a> { + &self.arguments + } +} + +impl<'a> DerefMut for CallArgumentTypes<'a, '_> { + fn deref_mut(&mut self) -> &mut CallArguments<'a> { + &mut self.arguments } } diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 3b25fe8945..f38851ac68 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -3,21 +3,24 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. -use std::borrow::Cow; - use smallvec::SmallVec; use super::{ - Argument, CallArguments, CallError, CallErrorKind, CallableSignature, InferContext, Signature, - Signatures, Type, + Argument, CallArgumentTypes, CallArguments, CallError, CallErrorKind, CallableSignature, + InferContext, Signature, Signatures, Type, }; use crate::db::Db; +use crate::symbol::{Boundness, Symbol}; use crate::types::diagnostic::{ - CALL_NON_CALLABLE, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, - PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, + CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, + NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, + UNKNOWN_ARGUMENT, +}; +use crate::types::signatures::{Parameter, ParameterForm}; +use crate::types::{ + todo_type, BoundMethodType, CallableType, ClassLiteralType, KnownClass, KnownFunction, + KnownInstanceType, UnionType, }; -use crate::types::signatures::Parameter; -use crate::types::{CallableType, UnionType}; use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span}; use ruff_python_ast as ast; use ruff_text_size::Ranged; @@ -26,29 +29,75 @@ use ruff_text_size::Ranged; /// compatible with _all_ of the types in the union for the call to be valid. /// /// It's guaranteed that the wrapped bindings have no errors. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(crate) struct Bindings<'db> { - pub(crate) callable_type: Type<'db>, + signatures: Signatures<'db>, /// By using `SmallVec`, we avoid an extra heap allocation for the common case of a non-union /// type. elements: SmallVec<[CallableBinding<'db>; 1]>, + + /// Whether each argument will be used as a value and/or a type form in this call. + pub(crate) argument_forms: Box<[Option]>, + + conflicting_forms: Box<[bool]>, } impl<'db> Bindings<'db> { - /// Binds the arguments of a call site against a signature. + /// Match the arguments of a call site against the parameters of a collection of possibly + /// unioned, possibly overloaded signatures. /// - /// The returned bindings provide the return type of the call, the bound types for all + /// The returned bindings tell you which parameter (in each signature) each argument was + /// matched against. You can then perform type inference on each argument with extra context + /// about the expected parameter types. (You do this by creating a [`CallArgumentTypes`] object + /// from the `arguments` that you match against.) + /// + /// Once you have argument types available, you can call [`check_types`][Self::check_types] to + /// verify that each argument type is assignable to the corresponding parameter type. + pub(crate) fn match_parameters( + signatures: Signatures<'db>, + arguments: &mut CallArguments<'_>, + ) -> Self { + let mut argument_forms = vec![None; arguments.len()]; + let mut conflicting_forms = vec![false; arguments.len()]; + let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures + .iter() + .map(|signature| { + CallableBinding::match_parameters( + signature, + arguments, + &mut argument_forms, + &mut conflicting_forms, + ) + }) + .collect(); + + Bindings { + signatures, + elements, + argument_forms: argument_forms.into(), + conflicting_forms: conflicting_forms.into(), + } + } + + /// Verify that the type of each argument is assignable to type of the parameter that it was + /// matched to. + /// + /// You must provide an `argument_types` that was created from the same `arguments` that you + /// provided to [`match_parameters`][Self::match_parameters]. + /// + /// We update the bindings to include the return type of the call, the bound types for all /// parameters, and any errors resulting from binding the call, all for each union element and /// overload (if any). - pub(crate) fn bind( + pub(crate) fn check_types( + mut self, db: &'db dyn Db, - signatures: &Signatures<'db>, - arguments: &CallArguments<'_, 'db>, + argument_types: &mut CallArgumentTypes<'_, 'db>, ) -> Result> { - let elements: SmallVec<[CallableBinding<'db>; 1]> = signatures - .into_iter() - .map(|signature| CallableBinding::bind(db, signature, arguments)) - .collect(); + for (signature, element) in self.signatures.iter().zip(&mut self.elements) { + element.check_types(db, signature, argument_types); + } + + self.evaluate_known_cases(db); // In order of precedence: // @@ -68,28 +117,28 @@ impl<'db> Bindings<'db> { let mut all_ok = true; let mut any_binding_error = false; let mut all_not_callable = true; - for binding in &elements { + if self.conflicting_forms.contains(&true) { + all_ok = false; + any_binding_error = true; + all_not_callable = false; + } + for binding in &self.elements { let result = binding.as_result(); all_ok &= result.is_ok(); any_binding_error |= matches!(result, Err(CallErrorKind::BindingError)); all_not_callable &= matches!(result, Err(CallErrorKind::NotCallable)); } - let bindings = Bindings { - callable_type: signatures.callable_type, - elements, - }; - if all_ok { - Ok(bindings) + Ok(self) } else if any_binding_error { - Err(CallError(CallErrorKind::BindingError, Box::new(bindings))) + Err(CallError(CallErrorKind::BindingError, Box::new(self))) } else if all_not_callable { - Err(CallError(CallErrorKind::NotCallable, Box::new(bindings))) + Err(CallError(CallErrorKind::NotCallable, Box::new(self))) } else { Err(CallError( CallErrorKind::PossiblyNotCallable, - Box::new(bindings), + Box::new(self), )) } } @@ -98,6 +147,10 @@ impl<'db> Bindings<'db> { self.elements.len() == 1 } + pub(crate) fn callable_type(&self) -> Type<'db> { + self.signatures.callable_type + } + /// Returns the return type of the call. For successful calls, this is the actual return type. /// For calls with binding errors, this is a type that best approximates the return type. For /// types that are not callable, returns `Type::Unknown`. @@ -122,12 +175,22 @@ impl<'db> Bindings<'db> { node, format_args!( "Object of type `{}` is not callable", - self.callable_type.display(context.db()) + self.callable_type().display(context.db()) ), ); return; } + for (index, conflicting_form) in self.conflicting_forms.iter().enumerate() { + if *conflicting_form { + context.report_lint( + &CONFLICTING_ARGUMENT_FORMS, + BindingError::get_node(node, Some(index)), + format_args!("Argument is used as both a value and a type form in call"), + ); + } + } + // TODO: We currently only report errors for the first union element. Ideally, we'd report // an error saying that the union type can't be called, followed by subdiagnostics // explaining why. @@ -135,6 +198,286 @@ impl<'db> Bindings<'db> { first.report_diagnostics(context, node); } } + + /// Evaluates the return type of certain known callables, where we have special-case logic to + /// determine the return type in a way that isn't directly expressible in the type system. + fn evaluate_known_cases(&mut self, db: &'db dyn Db) { + // Each special case listed here should have a corresponding clause in `Type::signatures`. + for binding in &mut self.elements { + let binding_type = binding.callable_type; + let Some((overload_index, overload)) = binding.matching_overload_mut() else { + continue; + }; + + match binding_type { + Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { + if function.has_known_class_decorator(db, KnownClass::Classmethod) + && function.decorators(db).len() == 1 + { + match overload.parameter_types() { + [_, Some(owner)] => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, function, *owner, + )), + )); + } + [Some(instance), None] => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, + function, + instance.to_meta_type(db), + )), + )); + } + _ => {} + } + } else if let [Some(first), _] = overload.parameter_types() { + if first.is_none(db) { + overload.set_return_type(Type::FunctionLiteral(function)); + } else { + overload.set_return_type(Type::Callable(CallableType::BoundMethod( + BoundMethodType::new(db, function, *first), + ))); + } + } + } + + Type::Callable(CallableType::WrapperDescriptorDunderGet) => { + if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] = + overload.parameter_types() + { + if function.has_known_class_decorator(db, KnownClass::Classmethod) + && function.decorators(db).len() == 1 + { + match overload.parameter_types() { + [_, _, Some(owner)] => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, *function, *owner, + )), + )); + } + + [_, Some(instance), None] => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, + *function, + instance.to_meta_type(db), + )), + )); + } + + _ => {} + } + } else { + match overload.parameter_types() { + [_, Some(instance), _] if instance.is_none(db) => { + overload.set_return_type(*function_ty); + } + + [_, Some(Type::KnownInstance(KnownInstanceType::TypeAliasType( + type_alias, + ))), Some(Type::ClassLiteral(ClassLiteralType { class }))] + if class.is_known(db, KnownClass::TypeAliasType) + && function.name(db) == "__name__" => + { + overload.set_return_type(Type::string_literal( + db, + type_alias.name(db), + )); + } + + [_, Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), Some(Type::ClassLiteral(ClassLiteralType { class }))] + if class.is_known(db, KnownClass::TypeVar) + && function.name(db) == "__name__" => + { + overload.set_return_type(Type::string_literal( + db, + typevar.name(db), + )); + } + + [_, Some(_), _] + if function + .has_known_class_decorator(db, KnownClass::Property) => + { + overload.set_return_type(todo_type!("@property")); + } + + [_, Some(instance), _] => { + overload.set_return_type(Type::Callable( + CallableType::BoundMethod(BoundMethodType::new( + db, *function, *instance, + )), + )); + } + + _ => {} + } + } + } + } + + Type::FunctionLiteral(function_type) => match function_type.known(db) { + Some(KnownFunction::IsEquivalentTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_equivalent_to(db, *ty_b), + )); + } + } + + Some(KnownFunction::IsSubtypeOf) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_subtype_of(db, *ty_b), + )); + } + } + + Some(KnownFunction::IsAssignableTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_assignable_to(db, *ty_b), + )); + } + } + + Some(KnownFunction::IsDisjointFrom) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_disjoint_from(db, *ty_b), + )); + } + } + + Some(KnownFunction::IsGradualEquivalentTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_gradual_equivalent_to(db, *ty_b), + )); + } + } + + Some(KnownFunction::IsFullyStatic) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + } + } + + Some(KnownFunction::IsSingleton) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); + } + } + + Some(KnownFunction::IsSingleValued) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); + } + } + + Some(KnownFunction::Len) => { + if let [Some(first_arg)] = overload.parameter_types() { + if let Some(len_ty) = first_arg.len(db) { + overload.set_return_type(len_ty); + } + }; + } + + Some(KnownFunction::Repr) => { + if let [Some(first_arg)] = overload.parameter_types() { + overload.set_return_type(first_arg.repr(db)); + }; + } + + Some(KnownFunction::Cast) => { + if let [Some(casted_ty), Some(_)] = overload.parameter_types() { + overload.set_return_type(*casted_ty); + } + } + + Some(KnownFunction::Overload) => { + overload.set_return_type(todo_type!("overload(..) return type")); + } + + Some(KnownFunction::GetattrStatic) => { + let [Some(instance_ty), Some(attr_name), default] = + overload.parameter_types() + else { + continue; + }; + + let Some(attr_name) = attr_name.into_string_literal() else { + continue; + }; + + let default = if let Some(default) = default { + *default + } else { + Type::Never + }; + + let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); + + // TODO: we could emit a diagnostic here (if default is not set) + overload.set_return_type( + match instance_ty.static_member(db, attr_name.value(db)) { + Symbol::Type(ty, Boundness::Bound) => { + if instance_ty.is_fully_static(db) { + ty + } else { + // Here, we attempt to model the fact that an attribute lookup on + // a non-fully static type could fail. This is an approximation, + // as there are gradual types like `tuple[Any]`, on which a lookup + // of (e.g. of the `index` method) would always succeed. + + union_with_default(ty) + } + } + Symbol::Type(ty, Boundness::PossiblyUnbound) => { + union_with_default(ty) + } + Symbol::Unbound => default, + }, + ); + } + + _ => {} + }, + + Type::ClassLiteral(ClassLiteralType { class }) => match class.known(db) { + Some(KnownClass::Bool) => match overload.parameter_types() { + [Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)), + [None] => overload.set_return_type(Type::BooleanLiteral(false)), + _ => {} + }, + + Some(KnownClass::Str) if overload_index == 0 => { + match overload.parameter_types() { + [Some(arg)] => overload.set_return_type(arg.str(db)), + [None] => overload.set_return_type(Type::string_literal(db, "")), + _ => {} + } + } + + Some(KnownClass::Type) if overload_index == 0 => { + if let [Some(arg)] = overload.parameter_types() { + overload.set_return_type(arg.to_meta_type(db)); + } + } + + _ => {} + }, + + // Not a special case + _ => {} + } + } + } } impl<'a, 'db> IntoIterator for &'a Bindings<'db> { @@ -170,7 +513,7 @@ impl<'a, 'db> IntoIterator for &'a mut Bindings<'db> { /// overloads, we store this error information for each overload. /// /// [overloads]: https://github.com/python/typing/pull/1839 -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(crate) struct CallableBinding<'db> { pub(crate) callable_type: Type<'db>, pub(crate) signature_type: Type<'db>, @@ -184,39 +527,55 @@ pub(crate) struct CallableBinding<'db> { } impl<'db> CallableBinding<'db> { - /// Bind a [`CallArguments`] against a [`CallableSignature`]. - /// - /// The returned [`CallableBinding`] provides the return type of the call, the bound types for - /// all parameters, and any errors resulting from binding the call. - fn bind( - db: &'db dyn Db, + fn match_parameters( signature: &CallableSignature<'db>, - arguments: &CallArguments<'_, 'db>, + arguments: &mut CallArguments<'_>, + argument_forms: &mut [Option], + conflicting_forms: &mut [bool], ) -> Self { // If this callable is a bound method, prepend the self instance onto the arguments list // before checking. - let arguments = if let Some(bound_type) = signature.bound_type { - Cow::Owned(arguments.with_self(bound_type)) - } else { - Cow::Borrowed(arguments) - }; + arguments.with_self(signature.bound_type, |arguments| { + // TODO: This checks every overload. In the proposed more detailed call checking spec [1], + // arguments are checked for arity first, and are only checked for type assignability against + // the matching overloads. Make sure to implement that as part of separating call binding into + // two phases. + // + // [1] https://github.com/python/typing/pull/1839 + let overloads = signature + .into_iter() + .map(|signature| { + Binding::match_parameters( + signature, + arguments, + argument_forms, + conflicting_forms, + ) + }) + .collect(); - // TODO: This checks every overload. In the proposed more detailed call checking spec [1], - // arguments are checked for arity first, and are only checked for type assignability against - // the matching overloads. Make sure to implement that as part of separating call binding into - // two phases. - // - // [1] https://github.com/python/typing/pull/1839 - let overloads = signature - .into_iter() - .map(|signature| Binding::bind(db, signature, arguments.as_ref())) - .collect(); - CallableBinding { - callable_type: signature.callable_type, - signature_type: signature.signature_type, - dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, - overloads, - } + CallableBinding { + callable_type: signature.callable_type, + signature_type: signature.signature_type, + dunder_call_is_possibly_unbound: signature.dunder_call_is_possibly_unbound, + overloads, + } + }) + } + + fn check_types( + &mut self, + db: &'db dyn Db, + signature: &CallableSignature<'db>, + argument_types: &mut CallArgumentTypes<'_, 'db>, + ) { + // If this callable is a bound method, prepend the self instance onto the arguments list + // before checking. + argument_types.with_self(signature.bound_type, |argument_types| { + for (signature, overload) in signature.iter().zip(&mut self.overloads) { + overload.check_types(db, signature, argument_types); + } + }); } fn as_result(&self) -> Result<(), CallErrorKind> { @@ -333,27 +692,35 @@ impl<'db> CallableBinding<'db> { } /// Binding information for one of the overloads of a callable. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(crate) struct Binding<'db> { /// Return type of the call. return_ty: Type<'db>, - /// Bound types for parameters, in parameter source order. - parameter_tys: Box<[Type<'db>]>, + /// The formal parameter that each argument is matched with, in argument source order, or + /// `None` if the argument was not matched to any parameter. + argument_parameters: Box<[Option]>, + + /// Bound types for parameters, in parameter source order, or `None` if no argument was matched + /// to that parameter. + parameter_tys: Box<[Option>]>, /// Call binding errors, if any. errors: Vec>, } impl<'db> Binding<'db> { - fn bind( - db: &'db dyn Db, + fn match_parameters( signature: &Signature<'db>, - arguments: &CallArguments<'_, 'db>, + arguments: &CallArguments<'_>, + argument_forms: &mut [Option], + conflicting_forms: &mut [bool], ) -> Self { let parameters = signature.parameters(); - // The type assigned to each parameter at this call site. - let mut parameter_tys = vec![None; parameters.len()]; + // The parameter that each argument is matched with. + let mut argument_parameters = vec![None; arguments.len()]; + // Whether each parameter has been matched with an argument. + let mut parameter_matched = vec![false; parameters.len()]; let mut errors = vec![]; let mut next_positional = 0; let mut first_excess_positional = None; @@ -370,9 +737,9 @@ impl<'db> Binding<'db> { } }; for (argument_index, argument) in arguments.iter().enumerate() { - let (index, parameter, argument_ty, positional) = match argument { - Argument::Positional(ty) | Argument::Synthetic(ty) => { - if matches!(argument, Argument::Synthetic(_)) { + let (index, parameter, positional) = match argument { + Argument::Positional | Argument::Synthetic => { + if matches!(argument, Argument::Synthetic) { num_synthetic_args += 1; } let Some((index, parameter)) = parameters @@ -385,9 +752,9 @@ impl<'db> Binding<'db> { continue; }; next_positional += 1; - (index, parameter, ty, !parameter.is_variadic()) + (index, parameter, !parameter.is_variadic()) } - Argument::Keyword { name, ty } => { + Argument::Keyword(name) => { let Some((index, parameter)) = parameters .keyword_by_name(name) .or_else(|| parameters.keyword_variadic()) @@ -398,35 +765,33 @@ impl<'db> Binding<'db> { }); continue; }; - (index, parameter, ty, false) + (index, parameter, false) } - Argument::Variadic(_) | Argument::Keywords(_) => { + Argument::Variadic | Argument::Keywords => { // TODO continue; } }; - if let Some(expected_ty) = parameter.annotated_type() { - if !argument_ty.is_assignable_to(db, expected_ty) { - errors.push(BindingError::InvalidArgumentType { - parameter: ParameterContext::new(parameter, index, positional), - argument_index: get_argument_index(argument_index, num_synthetic_args), - expected_ty, - provided_ty: *argument_ty, - }); + if !matches!(argument, Argument::Synthetic) { + if let Some(existing) = + argument_forms[argument_index - num_synthetic_args].replace(parameter.form) + { + if existing != parameter.form { + conflicting_forms[argument_index - num_synthetic_args] = true; + } } } - if let Some(existing) = parameter_tys[index].replace(*argument_ty) { - if parameter.is_variadic() || parameter.is_keyword_variadic() { - let union = UnionType::from_elements(db, [existing, *argument_ty]); - parameter_tys[index].replace(union); - } else { + if parameter_matched[index] { + if !parameter.is_variadic() && !parameter.is_keyword_variadic() { errors.push(BindingError::ParameterAlreadyAssigned { argument_index: get_argument_index(argument_index, num_synthetic_args), parameter: ParameterContext::new(parameter, index, positional), }); } } + argument_parameters[argument_index] = Some(index); + parameter_matched[index] = true; } if let Some(first_excess_argument_index) = first_excess_positional { errors.push(BindingError::TooManyPositionalArguments { @@ -439,8 +804,8 @@ impl<'db> Binding<'db> { }); } let mut missing = vec![]; - for (index, bound_ty) in parameter_tys.iter().enumerate() { - if bound_ty.is_none() { + for (index, matched) in parameter_matched.iter().copied().enumerate() { + if !matched { let param = ¶meters[index]; if param.is_variadic() || param.is_keyword_variadic() @@ -461,14 +826,65 @@ impl<'db> Binding<'db> { Self { return_ty: signature.return_ty.unwrap_or(Type::unknown()), - parameter_tys: parameter_tys - .into_iter() - .map(|opt_ty| opt_ty.unwrap_or(Type::unknown())) - .collect(), + argument_parameters: argument_parameters.into_boxed_slice(), + parameter_tys: vec![None; parameters.len()].into_boxed_slice(), errors, } } + fn check_types( + &mut self, + db: &'db dyn Db, + signature: &Signature<'db>, + argument_types: &CallArgumentTypes<'_, 'db>, + ) { + let parameters = signature.parameters(); + let mut num_synthetic_args = 0; + let get_argument_index = |argument_index: usize, num_synthetic_args: usize| { + if argument_index >= num_synthetic_args { + // Adjust the argument index to skip synthetic args, which don't appear at the call + // site and thus won't be in the Call node arguments list. + Some(argument_index - num_synthetic_args) + } else { + // we are erroring on a synthetic argument, we'll just emit the diagnostic on the + // entire Call node, since there's no argument node for this argument at the call site + None + } + }; + for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() { + if matches!(argument, Argument::Synthetic) { + num_synthetic_args += 1; + } + let Some(parameter_index) = self.argument_parameters[argument_index] else { + // There was an error with argument when matching parameters, so don't bother + // type-checking it. + continue; + }; + let parameter = ¶meters[parameter_index]; + if let Some(expected_ty) = parameter.annotated_type() { + if !argument_type.is_assignable_to(db, expected_ty) { + let positional = matches!(argument, Argument::Positional | Argument::Synthetic) + && !parameter.is_variadic(); + self.errors.push(BindingError::InvalidArgumentType { + parameter: ParameterContext::new(parameter, parameter_index, positional), + argument_index: get_argument_index(argument_index, num_synthetic_args), + expected_ty, + provided_ty: argument_type, + }); + } + } + // We still update the actual type of the parameter in this binding to match the + // argument, even if the argument type is not assignable to the expected parameter + // type. + if let Some(existing) = self.parameter_tys[parameter_index].replace(argument_type) { + // We already verified in `match_parameters` that we only match multiple arguments + // with variadic parameters. + let union = UnionType::from_elements(db, [existing, argument_type]); + self.parameter_tys[parameter_index] = Some(union); + } + } + } + pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) { self.return_ty = return_ty; } @@ -477,7 +893,7 @@ impl<'db> Binding<'db> { self.return_ty } - pub(crate) fn parameter_types(&self) -> &[Type<'db>] { + pub(crate) fn parameter_types(&self) -> &[Option>] { &self.parameter_tys } diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 6db2590c01..5b3c0ccfff 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -11,7 +11,7 @@ use crate::{ Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers, }, types::{ - definition_expression_type, CallArguments, CallError, CallErrorKind, DynamicType, + definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType, MetaclassCandidate, TupleType, UnionBuilder, UnionType, }, Db, KnownModule, Program, @@ -279,13 +279,13 @@ impl<'db> Class<'db> { let namespace = KnownClass::Dict.to_instance(db); // TODO: Other keyword arguments? - let arguments = CallArguments::positional([name, bases, namespace]); + let arguments = CallArgumentTypes::positional([name, bases, namespace]); - let return_ty_result = match metaclass.try_call(db, &arguments) { + let return_ty_result = match metaclass.try_call(db, arguments) { Ok(bindings) => Ok(bindings.return_type(db)), Err(CallError(CallErrorKind::NotCallable, bindings)) => Err(MetaclassError { - kind: MetaclassErrorKind::NotCallable(bindings.callable_type), + kind: MetaclassErrorKind::NotCallable(bindings.callable_type()), }), // TODO we should also check for binding errors that would indicate the metaclass diff --git a/crates/red_knot_python_semantic/src/types/diagnostic.rs b/crates/red_knot_python_semantic/src/types/diagnostic.rs index 1485809948..2026dbd8f6 100644 --- a/crates/red_knot_python_semantic/src/types/diagnostic.rs +++ b/crates/red_knot_python_semantic/src/types/diagnostic.rs @@ -24,6 +24,7 @@ use std::sync::Arc; pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&CALL_NON_CALLABLE); registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD); + registry.register_lint(&CONFLICTING_ARGUMENT_FORMS); registry.register_lint(&CONFLICTING_DECLARATIONS); registry.register_lint(&CONFLICTING_METACLASS); registry.register_lint(&CYCLIC_CLASS_DEFINITION); @@ -106,6 +107,16 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks whether an argument is used as both a value and a type form in a call + pub(crate) static CONFLICTING_ARGUMENT_FORMS = { + summary: "detects when an argument is used as both a value and a type form in a call", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + declare_lint! { /// TODO #14889 pub(crate) static CONFLICTING_DECLARATIONS = { diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index f2fe2bc87c..2411c4e862 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -476,8 +476,7 @@ mod tests { use crate::db::tests::setup_db; use crate::types::{ - KnownClass, Parameter, ParameterKind, Parameters, Signature, SliceLiteralType, - StringLiteralType, Type, + KnownClass, Parameter, Parameters, Signature, SliceLiteralType, StringLiteralType, Type, }; use crate::Db; @@ -574,13 +573,7 @@ mod tests { assert_eq!( display_signature( &db, - [Parameter::new( - Some(Type::none(&db)), - ParameterKind::PositionalOnly { - name: None, - default_ty: None - } - )], + [Parameter::positional_only(None).with_annotated_type(Type::none(&db))], Some(Type::none(&db)) ), "(None, /) -> None" @@ -591,20 +584,11 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::PositionalOrKeyword { - name: Name::new_static("x"), - default_ty: Some(KnownClass::Int.to_instance(&db)) - } - ), - Parameter::new( - Some(KnownClass::Str.to_instance(&db)), - ParameterKind::PositionalOrKeyword { - name: Name::new_static("y"), - default_ty: Some(KnownClass::Str.to_instance(&db)) - } - ) + Parameter::positional_or_keyword(Name::new_static("x")) + .with_default_type(KnownClass::Int.to_instance(&db)), + Parameter::positional_or_keyword(Name::new_static("y")) + .with_annotated_type(KnownClass::Str.to_instance(&db)) + .with_default_type(KnownClass::Str.to_instance(&db)), ], Some(Type::none(&db)) ), @@ -616,20 +600,8 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(Name::new_static("x")), - default_ty: None - } - ), - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(Name::new_static("y")), - default_ty: None - } - ) + Parameter::positional_only(Some(Name::new_static("x"))), + Parameter::positional_only(Some(Name::new_static("y"))), ], Some(Type::none(&db)) ), @@ -641,20 +613,8 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(Name::new_static("x")), - default_ty: None - } - ), - Parameter::new( - None, - ParameterKind::PositionalOrKeyword { - name: Name::new_static("y"), - default_ty: None - } - ) + Parameter::positional_only(Some(Name::new_static("x"))), + Parameter::positional_or_keyword(Name::new_static("y")), ], Some(Type::none(&db)) ), @@ -666,20 +626,8 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::KeywordOnly { - name: Name::new_static("x"), - default_ty: None - } - ), - Parameter::new( - None, - ParameterKind::KeywordOnly { - name: Name::new_static("y"), - default_ty: None - } - ) + Parameter::keyword_only(Name::new_static("x")), + Parameter::keyword_only(Name::new_static("y")), ], Some(Type::none(&db)) ), @@ -691,20 +639,8 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::PositionalOrKeyword { - name: Name::new_static("x"), - default_ty: None - } - ), - Parameter::new( - None, - ParameterKind::KeywordOnly { - name: Name::new_static("y"), - default_ty: None - } - ) + Parameter::positional_or_keyword(Name::new_static("x")), + Parameter::keyword_only(Name::new_static("y")), ], Some(Type::none(&db)) ), @@ -716,74 +652,28 @@ mod tests { display_signature( &db, [ - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(Name::new_static("a")), - default_ty: None - }, - ), - Parameter::new( - Some(KnownClass::Int.to_instance(&db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("b")), - default_ty: None - }, - ), - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(Name::new_static("c")), - default_ty: Some(Type::IntLiteral(1)), - }, - ), - Parameter::new( - Some(KnownClass::Int.to_instance(&db)), - ParameterKind::PositionalOnly { - name: Some(Name::new_static("d")), - default_ty: Some(Type::IntLiteral(2)), - }, - ), - Parameter::new( - None, - ParameterKind::PositionalOrKeyword { - name: Name::new_static("e"), - default_ty: Some(Type::IntLiteral(3)), - }, - ), - Parameter::new( - Some(KnownClass::Int.to_instance(&db)), - ParameterKind::PositionalOrKeyword { - name: Name::new_static("f"), - default_ty: Some(Type::IntLiteral(4)), - }, - ), - Parameter::new( - Some(Type::object(&db)), - ParameterKind::Variadic { - name: Name::new_static("args") - }, - ), - Parameter::new( - None, - ParameterKind::KeywordOnly { - name: Name::new_static("g"), - default_ty: Some(Type::IntLiteral(5)), - }, - ), - Parameter::new( - Some(KnownClass::Int.to_instance(&db)), - ParameterKind::KeywordOnly { - name: Name::new_static("h"), - default_ty: Some(Type::IntLiteral(6)), - }, - ), - Parameter::new( - Some(KnownClass::Str.to_instance(&db)), - ParameterKind::KeywordVariadic { - name: Name::new_static("kwargs") - }, - ), + Parameter::positional_only(Some(Name::new_static("a"))), + Parameter::positional_only(Some(Name::new_static("b"))) + .with_annotated_type(KnownClass::Int.to_instance(&db)), + Parameter::positional_only(Some(Name::new_static("c"))) + .with_default_type(Type::IntLiteral(1)), + Parameter::positional_only(Some(Name::new_static("d"))) + .with_annotated_type(KnownClass::Int.to_instance(&db)) + .with_default_type(Type::IntLiteral(2)), + Parameter::positional_or_keyword(Name::new_static("e")) + .with_default_type(Type::IntLiteral(3)), + Parameter::positional_or_keyword(Name::new_static("f")) + .with_annotated_type(KnownClass::Int.to_instance(&db)) + .with_default_type(Type::IntLiteral(4)), + Parameter::variadic(Name::new_static("args")) + .with_annotated_type(Type::object(&db)), + Parameter::keyword_only(Name::new_static("g")) + .with_default_type(Type::IntLiteral(5)), + Parameter::keyword_only(Name::new_static("h")) + .with_annotated_type(KnownClass::Int.to_instance(&db)) + .with_default_type(Type::IntLiteral(6)), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(KnownClass::Str.to_instance(&db)), ], Some(KnownClass::Bytes.to_instance(&db)) ), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 4b8936e7d5..cb780a4f1f 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -61,7 +61,7 @@ use crate::symbol::{ module_type_implicit_global_symbol, symbol, symbol_from_bindings, symbol_from_declarations, typing_extensions_symbol, Boundness, LookupError, }; -use crate::types::call::{Argument, CallArguments, CallError}; +use crate::types::call::{Argument, Bindings, CallArgumentTypes, CallArguments, CallError}; use crate::types::diagnostic::{ report_implicit_return_type, report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_assignment, @@ -79,12 +79,12 @@ use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ class::MetaclassErrorKind, todo_type, Class, DynamicType, FunctionType, InstanceType, IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, - MetaclassCandidate, Parameter, Parameters, SliceLiteralType, SubclassOfType, Symbol, - SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, + MetaclassCandidate, Parameter, ParameterForm, Parameters, SliceLiteralType, SubclassOfType, + Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, }; -use crate::types::{CallableType, GeneralCallableType, ParameterKind, Signature}; +use crate::types::{CallableType, GeneralCallableType, Signature}; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; use crate::Db; @@ -102,7 +102,7 @@ use super::slots::check_class_slots; use super::string_annotation::{ parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, }; -use super::{CallDunderError, ParameterExpectation, ParameterExpectations}; +use super::CallDunderError; /// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope. /// Use when checking a scope, or needing to provide a type for an arbitrary expression in the @@ -1141,7 +1141,9 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_type_parameters(type_params); if let Some(arguments) = class.arguments.as_deref() { - self.infer_arguments(arguments, ParameterExpectations::default()); + let call_arguments = Self::parse_arguments(arguments); + let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()]; + self.infer_argument_types(arguments, call_arguments, &argument_forms); } } @@ -1517,7 +1519,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) { if let Some(annotation) = parameter.annotation() { let _annotated_ty = self.file_expression_type(annotation); - // TODO `tuple[annotated_ty, ...]` + // TODO `tuple[annotated_type, ...]` let ty = KnownClass::Tuple.to_instance(self.db()); self.add_declaration_with_binding( parameter.into(), @@ -1548,7 +1550,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) { if let Some(annotation) = parameter.annotation() { let _annotated_ty = self.file_expression_type(annotation); - // TODO `dict[str, annotated_ty]` + // TODO `dict[str, annotated_type]` let ty = KnownClass::Dict.to_instance(self.db()); self.add_declaration_with_binding( parameter.into(), @@ -2276,7 +2278,7 @@ impl<'db> TypeInferenceBuilder<'db> { let successful_call = meta_dunder_set .try_call( db, - &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), + CallArgumentTypes::positional([meta_attr_ty, object_ty, value_ty]), ) .is_ok(); @@ -2375,7 +2377,11 @@ impl<'db> TypeInferenceBuilder<'db> { let successful_call = meta_dunder_set .try_call( db, - &CallArguments::positional([meta_attr_ty, object_ty, value_ty]), + CallArgumentTypes::positional([ + meta_attr_ty, + object_ty, + value_ty, + ]), ) .is_ok(); @@ -2783,7 +2789,7 @@ impl<'db> TypeInferenceBuilder<'db> { let call = target_type.try_call_dunder( db, op.in_place_dunder(), - &CallArguments::positional([value_type]), + CallArgumentTypes::positional([value_type]), ); match call { @@ -3232,45 +3238,22 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_expression(expression) } - fn infer_arguments<'a>( - &mut self, - arguments: &'a ast::Arguments, - parameter_expectations: ParameterExpectations, - ) -> CallArguments<'a, 'db> { + fn parse_arguments(arguments: &ast::Arguments) -> CallArguments<'_> { arguments .arguments_source_order() - .enumerate() - .map(|(index, arg_or_keyword)| { - let infer_argument_type = match parameter_expectations.expectation_at_index(index) { - ParameterExpectation::TypeExpression => Self::infer_type_expression, - ParameterExpectation::ValueExpression => Self::infer_expression, - }; - + .map(|arg_or_keyword| { match arg_or_keyword { ast::ArgOrKeyword::Arg(arg) => match arg { - ast::Expr::Starred(ast::ExprStarred { - value, - range: _, - ctx: _, - }) => { - let ty = infer_argument_type(self, value); - self.store_expression_type(arg, ty); - Argument::Variadic(ty) - } + ast::Expr::Starred(ast::ExprStarred { .. }) => Argument::Variadic, // TODO diagnostic if after a keyword argument - _ => Argument::Positional(infer_argument_type(self, arg)), + _ => Argument::Positional, }, - ast::ArgOrKeyword::Keyword(ast::Keyword { - arg, - value, - range: _, - }) => { - let ty = infer_argument_type(self, value); + ast::ArgOrKeyword::Keyword(ast::Keyword { arg, .. }) => { if let Some(arg) = arg { - Argument::Keyword { name: &arg.id, ty } + Argument::Keyword(&arg.id) } else { // TODO diagnostic if not last - Argument::Keywords(ty) + Argument::Keywords } } } @@ -3278,6 +3261,44 @@ impl<'db> TypeInferenceBuilder<'db> { .collect() } + fn infer_argument_types<'a>( + &mut self, + ast_arguments: &ast::Arguments, + arguments: CallArguments<'a>, + argument_forms: &[Option], + ) -> CallArgumentTypes<'a, 'db> { + let mut ast_arguments = ast_arguments.arguments_source_order(); + CallArgumentTypes::new(arguments, |index, _| { + let arg_or_keyword = ast_arguments + .next() + .expect("argument lists should have consistent lengths"); + match arg_or_keyword { + ast::ArgOrKeyword::Arg(arg) => match arg { + ast::Expr::Starred(ast::ExprStarred { value, .. }) => { + let ty = self.infer_argument_type(value, argument_forms[index]); + self.store_expression_type(arg, ty); + ty + } + _ => self.infer_argument_type(arg, argument_forms[index]), + }, + ast::ArgOrKeyword::Keyword(ast::Keyword { value, .. }) => { + self.infer_argument_type(value, argument_forms[index]) + } + } + }) + } + + fn infer_argument_type( + &mut self, + ast_argument: &ast::Expr, + form: Option, + ) -> Type<'db> { + match form { + None | Some(ParameterForm::Value) => self.infer_expression(ast_argument), + Some(ParameterForm::Type) => self.infer_type_expression(ast_argument), + } + } + fn infer_optional_expression(&mut self, expression: Option<&ast::Expr>) -> Option> { expression.map(|expr| self.infer_expression(expr)) } @@ -3769,64 +3790,44 @@ impl<'db> TypeInferenceBuilder<'db> { let positional_only = parameters .posonlyargs .iter() - .map(|parameter| { - Parameter::new( - None, - ParameterKind::PositionalOnly { - name: Some(parameter.name().id.clone()), - default_ty: parameter - .default() - .map(|default| self.infer_expression(default)), - }, - ) + .map(|param| { + let mut parameter = Parameter::positional_only(Some(param.name().id.clone())); + if let Some(default) = param.default() { + parameter = parameter.with_default_type(self.infer_expression(default)); + } + parameter }) .collect::>(); let positional_or_keyword = parameters .args .iter() - .map(|parameter| { - Parameter::new( - None, - ParameterKind::PositionalOrKeyword { - name: parameter.name().id.clone(), - default_ty: parameter - .default() - .map(|default| self.infer_expression(default)), - }, - ) + .map(|param| { + let mut parameter = Parameter::positional_or_keyword(param.name().id.clone()); + if let Some(default) = param.default() { + parameter = parameter.with_default_type(self.infer_expression(default)); + } + parameter }) .collect::>(); - let variadic = parameters.vararg.as_ref().map(|parameter| { - Parameter::new( - None, - ParameterKind::Variadic { - name: parameter.name.id.clone(), - }, - ) - }); + let variadic = parameters + .vararg + .as_ref() + .map(|param| Parameter::variadic(param.name().id.clone())); let keyword_only = parameters .kwonlyargs .iter() - .map(|parameter| { - Parameter::new( - None, - ParameterKind::KeywordOnly { - name: parameter.name().id.clone(), - default_ty: parameter - .default() - .map(|default| self.infer_expression(default)), - }, - ) + .map(|param| { + let mut parameter = Parameter::keyword_only(param.name().id.clone()); + if let Some(default) = param.default() { + parameter = parameter.with_default_type(self.infer_expression(default)); + } + parameter }) .collect::>(); - let keyword_variadic = parameters.kwarg.as_ref().map(|parameter| { - Parameter::new( - None, - ParameterKind::KeywordVariadic { - name: parameter.name.id.clone(), - }, - ) - }); + let keyword_variadic = parameters + .kwarg + .as_ref() + .map(|param| Parameter::keyword_variadic(param.name().id.clone())); Parameters::new( positional_only @@ -3856,16 +3857,17 @@ impl<'db> TypeInferenceBuilder<'db> { arguments, } = call_expression; + // We don't call `Type::try_call`, because we want to perform type inference on the + // arguments after matching them to parameters, but before checking that the argument types + // are assignable to any parameter annotations. + let mut call_arguments = Self::parse_arguments(arguments); let function_type = self.infer_expression(func); + let signatures = function_type.signatures(self.db()); + let bindings = Bindings::match_parameters(signatures, &mut call_arguments); + let mut call_argument_types = + self.infer_argument_types(arguments, call_arguments, &bindings.argument_forms); - let parameter_expectations = function_type - .into_function_literal() - .and_then(|f| f.known(self.db())) - .map(KnownFunction::parameter_expectations) - .unwrap_or_default(); - - let call_arguments = self.infer_arguments(arguments, parameter_expectations); - match function_type.try_call(self.db(), &call_arguments) { + match bindings.check_types(self.db(), &mut call_argument_types) { Ok(bindings) => { for binding in &bindings { let Some(known_function) = binding @@ -3882,7 +3884,7 @@ impl<'db> TypeInferenceBuilder<'db> { match known_function { KnownFunction::RevealType => { - if let [revealed_type] = overload.parameter_types() { + if let [Some(revealed_type)] = overload.parameter_types() { self.context.report_diagnostic( call_expression, DiagnosticId::RevealedType, @@ -3896,7 +3898,8 @@ impl<'db> TypeInferenceBuilder<'db> { } } KnownFunction::AssertType => { - if let [actual_ty, asserted_ty] = overload.parameter_types() { + if let [Some(actual_ty), Some(asserted_ty)] = overload.parameter_types() + { if !actual_ty.is_gradual_equivalent_to(self.db(), *asserted_ty) { self.context.report_lint( &TYPE_ASSERTION_FAILURE, @@ -3911,7 +3914,7 @@ impl<'db> TypeInferenceBuilder<'db> { } } KnownFunction::StaticAssert => { - if let [parameter_ty, message] = overload.parameter_types() { + if let [Some(parameter_ty), message] = overload.parameter_types() { let truthiness = match parameter_ty.try_bool(self.db()) { Ok(truthiness) => truthiness, Err(err) => { @@ -3934,8 +3937,9 @@ impl<'db> TypeInferenceBuilder<'db> { }; if !truthiness.is_always_true() { - if let Some(message) = - message.into_string_literal().map(|s| &**s.value(self.db())) + if let Some(message) = message + .and_then(Type::into_string_literal) + .map(|s| &**s.value(self.db())) { self.context.report_lint( &STATIC_ASSERT_ERROR, @@ -4352,7 +4356,7 @@ impl<'db> TypeInferenceBuilder<'db> { match operand_type.try_call_dunder( self.db(), unary_dunder_method, - &CallArguments::none(), + CallArgumentTypes::none(), ) { Ok(outcome) => outcome.return_type(self.db()), Err(e) => { @@ -4634,7 +4638,7 @@ impl<'db> TypeInferenceBuilder<'db> { .try_call_dunder( self.db(), reflected_dunder, - &CallArguments::positional([left_ty]), + CallArgumentTypes::positional([left_ty]), ) .map(|outcome| outcome.return_type(self.db())) .or_else(|_| { @@ -4642,7 +4646,7 @@ impl<'db> TypeInferenceBuilder<'db> { .try_call_dunder( self.db(), op.dunder(), - &CallArguments::positional([right_ty]), + CallArgumentTypes::positional([right_ty]), ) .map(|outcome| outcome.return_type(self.db())) }) @@ -4654,7 +4658,7 @@ impl<'db> TypeInferenceBuilder<'db> { .try_call_dunder( self.db(), op.dunder(), - &CallArguments::positional([right_ty]), + CallArgumentTypes::positional([right_ty]), ) .map(|outcome| outcome.return_type(self.db())) .ok(); @@ -4667,7 +4671,7 @@ impl<'db> TypeInferenceBuilder<'db> { .try_call_dunder( self.db(), op.reflected_dunder(), - &CallArguments::positional([left_ty]), + CallArgumentTypes::positional([left_ty]), ) .map(|outcome| outcome.return_type(self.db())) .ok() @@ -5318,7 +5322,7 @@ impl<'db> TypeInferenceBuilder<'db> { .try_call_dunder( db, op.dunder(), - &CallArguments::positional([Type::Instance(right)]), + CallArgumentTypes::positional([Type::Instance(right)]), ) .map(|outcome| outcome.return_type(db)) .ok() @@ -5367,7 +5371,10 @@ impl<'db> TypeInferenceBuilder<'db> { contains_dunder .try_call( db, - &CallArguments::positional([Type::Instance(right), Type::Instance(left)]), + CallArgumentTypes::positional([ + Type::Instance(right), + Type::Instance(left), + ]), ) .map(|bindings| bindings.return_type(db)) .ok() @@ -5643,7 +5650,7 @@ impl<'db> TypeInferenceBuilder<'db> { match value_ty.try_call_dunder( self.db(), "__getitem__", - &CallArguments::positional([slice_ty]), + CallArgumentTypes::positional([slice_ty]), ) { Ok(outcome) => return outcome.return_type(self.db()), Err(err @ CallDunderError::PossiblyUnbound { .. }) => { @@ -5664,7 +5671,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type.display(self.db()), + bindings.callable_type().display(self.db()), value_ty.display(self.db()), ), ); @@ -5705,7 +5712,7 @@ impl<'db> TypeInferenceBuilder<'db> { match ty.try_call( self.db(), - &CallArguments::positional([value_ty, slice_ty]), + CallArgumentTypes::positional([value_ty, slice_ty]), ) { Ok(bindings) => return bindings.return_type(self.db()), Err(CallError(_, bindings)) => { @@ -5714,7 +5721,7 @@ impl<'db> TypeInferenceBuilder<'db> { value_node, format_args!( "Method `__class_getitem__` of type `{}` is not callable on object of type `{}`", - bindings.callable_type.display(self.db()), + bindings.callable_type().display(self.db()), value_ty.display(self.db()), ), ); @@ -6974,13 +6981,7 @@ impl<'db> TypeInferenceBuilder<'db> { Parameters::todo() } else { Parameters::new(parameter_types.iter().map(|param_type| { - Parameter::new( - Some(*param_type), - ParameterKind::PositionalOnly { - name: None, - default_ty: None, - }, - ) + Parameter::positional_only(None).with_annotated_type(*param_type) })) } } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index d4324d271c..a59a4a8165 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -66,6 +66,10 @@ impl<'db> Signatures<'db> { } } + pub(crate) fn iter(&self) -> std::slice::Iter<'_, CallableSignature<'db>> { + self.elements.iter() + } + pub(crate) fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { if self.callable_type == before { self.callable_type = after; @@ -87,7 +91,7 @@ impl<'a, 'db> IntoIterator for &'a Signatures<'db> { type IntoIter = std::slice::Iter<'a, CallableSignature<'db>>; fn into_iter(self) -> Self::IntoIter { - self.elements.iter() + self.iter() } } @@ -179,6 +183,10 @@ impl<'db> CallableSignature<'db> { self } + pub(crate) fn iter(&self) -> std::slice::Iter<'_, Signature<'db>> { + self.overloads.iter() + } + fn replace_callable_type(&mut self, before: Type<'db>, after: Type<'db>) { if self.callable_type == before { self.callable_type = after; @@ -191,7 +199,7 @@ impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { type IntoIter = std::slice::Iter<'a, Signature<'db>>; fn into_iter(self) -> Self::IntoIter { - self.overloads.iter() + self.iter() } } @@ -306,18 +314,10 @@ impl<'db> Parameters<'db> { pub(crate) fn todo() -> Self { Self { value: vec![ - Parameter { - annotated_ty: Some(todo_type!("todo signature *args")), - kind: ParameterKind::Variadic { - name: Name::new_static("args"), - }, - }, - Parameter { - annotated_ty: Some(todo_type!("todo signature **kwargs")), - kind: ParameterKind::KeywordVariadic { - name: Name::new_static("kwargs"), - }, - }, + Parameter::variadic(Name::new_static("args")) + .with_annotated_type(todo_type!("todo signature *args")), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(todo_type!("todo signature **kwargs")), ], is_gradual: false, } @@ -331,18 +331,10 @@ impl<'db> Parameters<'db> { pub(crate) fn gradual_form() -> Self { Self { value: vec![ - Parameter { - annotated_ty: Some(Type::Dynamic(DynamicType::Any)), - kind: ParameterKind::Variadic { - name: Name::new_static("args"), - }, - }, - Parameter { - annotated_ty: Some(Type::Dynamic(DynamicType::Any)), - kind: ParameterKind::KeywordVariadic { - name: Name::new_static("kwargs"), - }, - }, + Parameter::variadic(Name::new_static("args")) + .with_annotated_type(Type::Dynamic(DynamicType::Any)), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(Type::Dynamic(DynamicType::Any)), ], is_gradual: true, } @@ -357,18 +349,10 @@ impl<'db> Parameters<'db> { pub(crate) fn unknown() -> Self { Self { value: vec![ - Parameter { - annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)), - kind: ParameterKind::Variadic { - name: Name::new_static("args"), - }, - }, - Parameter { - annotated_ty: Some(Type::Dynamic(DynamicType::Unknown)), - kind: ParameterKind::KeywordVariadic { - name: Name::new_static("kwargs"), - }, - }, + Parameter::variadic(Name::new_static("args")) + .with_annotated_type(Type::Dynamic(DynamicType::Unknown)), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(Type::Dynamic(DynamicType::Unknown)), ], is_gradual: true, } @@ -387,7 +371,7 @@ impl<'db> Parameters<'db> { kwarg, range: _, } = parameters; - let default_ty = |param: &ast::ParameterWithDefault| { + let default_type = |param: &ast::ParameterWithDefault| { param .default() .map(|default| definition_expression_type(db, definition, default)) @@ -399,7 +383,7 @@ impl<'db> Parameters<'db> { &arg.parameter, ParameterKind::PositionalOnly { name: Some(arg.parameter.name.id.clone()), - default_ty: default_ty(arg), + default_type: default_type(arg), }, ) }); @@ -410,7 +394,7 @@ impl<'db> Parameters<'db> { &arg.parameter, ParameterKind::PositionalOrKeyword { name: arg.parameter.name.id.clone(), - default_ty: default_ty(arg), + default_type: default_type(arg), }, ) }); @@ -431,7 +415,7 @@ impl<'db> Parameters<'db> { &arg.parameter, ParameterKind::KeywordOnly { name: arg.parameter.name.id.clone(), - default_ty: default_ty(arg), + default_type: default_type(arg), }, ) }); @@ -531,14 +515,82 @@ impl<'db> std::ops::Index for Parameters<'db> { #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub(crate) struct Parameter<'db> { /// Annotated type of the parameter. - annotated_ty: Option>, + annotated_type: Option>, kind: ParameterKind<'db>, + pub(crate) form: ParameterForm, } impl<'db> Parameter<'db> { - pub(crate) fn new(annotated_ty: Option>, kind: ParameterKind<'db>) -> Self { - Self { annotated_ty, kind } + pub(crate) fn positional_only(name: Option) -> Self { + Self { + annotated_type: None, + kind: ParameterKind::PositionalOnly { + name, + default_type: None, + }, + form: ParameterForm::Value, + } + } + + pub(crate) fn positional_or_keyword(name: Name) -> Self { + Self { + annotated_type: None, + kind: ParameterKind::PositionalOrKeyword { + name, + default_type: None, + }, + form: ParameterForm::Value, + } + } + + pub(crate) fn variadic(name: Name) -> Self { + Self { + annotated_type: None, + kind: ParameterKind::Variadic { name }, + form: ParameterForm::Value, + } + } + + pub(crate) fn keyword_only(name: Name) -> Self { + Self { + annotated_type: None, + kind: ParameterKind::KeywordOnly { + name, + default_type: None, + }, + form: ParameterForm::Value, + } + } + + pub(crate) fn keyword_variadic(name: Name) -> Self { + Self { + annotated_type: None, + kind: ParameterKind::KeywordVariadic { name }, + form: ParameterForm::Value, + } + } + + pub(crate) fn with_annotated_type(mut self, annotated_type: Type<'db>) -> Self { + self.annotated_type = Some(annotated_type); + self + } + + pub(crate) fn with_default_type(mut self, default: Type<'db>) -> Self { + match &mut self.kind { + ParameterKind::PositionalOnly { default_type, .. } + | ParameterKind::PositionalOrKeyword { default_type, .. } + | ParameterKind::KeywordOnly { default_type, .. } => *default_type = Some(default), + ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { + panic!("cannot set default value for variadic parameter") + } + } + self + } + + pub(crate) fn type_form(mut self) -> Self { + self.form = ParameterForm::Type; + self } fn from_node_and_kind( @@ -548,10 +600,11 @@ impl<'db> Parameter<'db> { kind: ParameterKind<'db>, ) -> Self { Self { - annotated_ty: parameter + annotated_type: parameter .annotation() .map(|annotation| definition_expression_type(db, definition, annotation)), kind, + form: ParameterForm::Value, } } @@ -598,7 +651,7 @@ impl<'db> Parameter<'db> { /// Annotated type of the parameter, if annotated. pub(crate) fn annotated_type(&self) -> Option> { - self.annotated_ty + self.annotated_type } /// Kind of the parameter. @@ -629,11 +682,10 @@ impl<'db> Parameter<'db> { /// Default-value type of the parameter, if any. pub(crate) fn default_type(&self) -> Option> { match self.kind { - ParameterKind::PositionalOnly { default_ty, .. } => default_ty, - ParameterKind::PositionalOrKeyword { default_ty, .. } => default_ty, - ParameterKind::Variadic { .. } => None, - ParameterKind::KeywordOnly { default_ty, .. } => default_ty, - ParameterKind::KeywordVariadic { .. } => None, + ParameterKind::PositionalOnly { default_type, .. } + | ParameterKind::PositionalOrKeyword { default_type, .. } + | ParameterKind::KeywordOnly { default_type, .. } => default_type, + ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None, } } } @@ -647,14 +699,14 @@ pub(crate) enum ParameterKind<'db> { /// It is possible for signatures to be defined in ways that leave positional-only parameters /// nameless (e.g. via `Callable` annotations). name: Option, - default_ty: Option>, + default_type: Option>, }, /// Positional-or-keyword parameter, e.g. `def f(x): ...` PositionalOrKeyword { /// Parameter name. name: Name, - default_ty: Option>, + default_type: Option>, }, /// Variadic parameter, e.g. `def f(*args): ...` @@ -667,7 +719,7 @@ pub(crate) enum ParameterKind<'db> { KeywordOnly { /// Parameter name. name: Name, - default_ty: Option>, + default_type: Option>, }, /// Variadic keywords parameter, e.g. `def f(**kwargs): ...` @@ -677,6 +729,13 @@ pub(crate) enum ParameterKind<'db> { }, } +/// Whether a parameter is used as a value or a type form. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub(crate) enum ParameterForm { + Value, + Type, +} + #[cfg(test)] mod tests { use super::*; @@ -734,74 +793,28 @@ mod tests { assert_params( &sig, &[ - Parameter { - annotated_ty: None, - kind: ParameterKind::PositionalOnly { - name: Some(Name::new_static("a")), - default_ty: None, - }, - }, - Parameter { - annotated_ty: Some(KnownClass::Int.to_instance(&db)), - kind: ParameterKind::PositionalOnly { - name: Some(Name::new_static("b")), - default_ty: None, - }, - }, - Parameter { - annotated_ty: None, - kind: ParameterKind::PositionalOnly { - name: Some(Name::new_static("c")), - default_ty: Some(Type::IntLiteral(1)), - }, - }, - Parameter { - annotated_ty: Some(KnownClass::Int.to_instance(&db)), - kind: ParameterKind::PositionalOnly { - name: Some(Name::new_static("d")), - default_ty: Some(Type::IntLiteral(2)), - }, - }, - Parameter { - annotated_ty: None, - kind: ParameterKind::PositionalOrKeyword { - name: Name::new_static("e"), - default_ty: Some(Type::IntLiteral(3)), - }, - }, - Parameter { - annotated_ty: Some(Type::IntLiteral(4)), - kind: ParameterKind::PositionalOrKeyword { - name: Name::new_static("f"), - default_ty: Some(Type::IntLiteral(4)), - }, - }, - Parameter { - annotated_ty: Some(Type::object(&db)), - kind: ParameterKind::Variadic { - name: Name::new_static("args"), - }, - }, - Parameter { - annotated_ty: None, - kind: ParameterKind::KeywordOnly { - name: Name::new_static("g"), - default_ty: Some(Type::IntLiteral(5)), - }, - }, - Parameter { - annotated_ty: Some(Type::IntLiteral(6)), - kind: ParameterKind::KeywordOnly { - name: Name::new_static("h"), - default_ty: Some(Type::IntLiteral(6)), - }, - }, - Parameter { - annotated_ty: Some(KnownClass::Str.to_instance(&db)), - kind: ParameterKind::KeywordVariadic { - name: Name::new_static("kwargs"), - }, - }, + Parameter::positional_only(Some(Name::new_static("a"))), + Parameter::positional_only(Some(Name::new_static("b"))) + .with_annotated_type(KnownClass::Int.to_instance(&db)), + Parameter::positional_only(Some(Name::new_static("c"))) + .with_default_type(Type::IntLiteral(1)), + Parameter::positional_only(Some(Name::new_static("d"))) + .with_annotated_type(KnownClass::Int.to_instance(&db)) + .with_default_type(Type::IntLiteral(2)), + Parameter::positional_or_keyword(Name::new_static("e")) + .with_default_type(Type::IntLiteral(3)), + Parameter::positional_or_keyword(Name::new_static("f")) + .with_annotated_type(Type::IntLiteral(4)) + .with_default_type(Type::IntLiteral(4)), + Parameter::variadic(Name::new_static("args")) + .with_annotated_type(Type::object(&db)), + Parameter::keyword_only(Name::new_static("g")) + .with_default_type(Type::IntLiteral(5)), + Parameter::keyword_only(Name::new_static("h")) + .with_annotated_type(Type::IntLiteral(6)) + .with_default_type(Type::IntLiteral(6)), + Parameter::keyword_variadic(Name::new_static("kwargs")) + .with_annotated_type(KnownClass::Str.to_instance(&db)), ], ); } @@ -828,15 +841,16 @@ mod tests { let sig = func.internal_signature(&db); let [Parameter { - annotated_ty, + annotated_type, kind: ParameterKind::PositionalOrKeyword { name, .. }, + .. }] = &sig.parameters.value[..] else { panic!("expected one positional-or-keyword parameter"); }; assert_eq!(name, "a"); // Parameter resolution not deferred; we should see A not B - assert_eq!(annotated_ty.unwrap().display(&db).to_string(), "A"); + assert_eq!(annotated_type.unwrap().display(&db).to_string(), "A"); } #[test] @@ -861,15 +875,16 @@ mod tests { let sig = func.internal_signature(&db); let [Parameter { - annotated_ty, + annotated_type, kind: ParameterKind::PositionalOrKeyword { name, .. }, + .. }] = &sig.parameters.value[..] else { panic!("expected one positional-or-keyword parameter"); }; assert_eq!(name, "a"); // Parameter resolution deferred; we should see B - assert_eq!(annotated_ty.unwrap().display(&db).to_string(), "B"); + assert_eq!(annotated_type.unwrap().display(&db).to_string(), "B"); } #[test] @@ -894,11 +909,13 @@ mod tests { let sig = func.internal_signature(&db); let [Parameter { - annotated_ty: a_annotated_ty, + annotated_type: a_annotated_ty, kind: ParameterKind::PositionalOrKeyword { name: a_name, .. }, + .. }, Parameter { - annotated_ty: b_annotated_ty, + annotated_type: b_annotated_ty, kind: ParameterKind::PositionalOrKeyword { name: b_name, .. }, + .. }] = &sig.parameters.value[..] else { panic!("expected two positional-or-keyword parameters"); @@ -935,11 +952,13 @@ mod tests { let sig = func.internal_signature(&db); let [Parameter { - annotated_ty: a_annotated_ty, + annotated_type: a_annotated_ty, kind: ParameterKind::PositionalOrKeyword { name: a_name, .. }, + .. }, Parameter { - annotated_ty: b_annotated_ty, + annotated_type: b_annotated_ty, kind: ParameterKind::PositionalOrKeyword { name: b_name, .. }, + .. }] = &sig.parameters.value[..] else { panic!("expected two positional-or-keyword parameters"); diff --git a/knot.schema.json b/knot.schema.json index b5dbbb921d..66e11b3b26 100644 --- a/knot.schema.json +++ b/knot.schema.json @@ -240,6 +240,16 @@ } ] }, + "conflicting-argument-forms": { + "title": "detects when an argument is used as both a value and a type form in a call", + "description": "## What it does\nChecks whether an argument is used as both a value and a type form in a call", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "conflicting-declarations": { "title": "detects conflicting declarations", "description": "TODO #14889",