From 9ea95e9f24c4118d2e90e32be9733dd2f4fce19c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 26 Jun 2025 17:04:14 +0100 Subject: [PATCH] rework `check_call` more --- .../ty_python_semantic/src/types/call/bind.rs | 5 +- crates/ty_python_semantic/src/types/class.rs | 131 ++++++++---------- crates/ty_python_semantic/src/types/infer.rs | 5 +- 3 files changed, 62 insertions(+), 79 deletions(-) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 6929d17149..d8035621cd 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -67,7 +67,10 @@ impl<'db> Bindings<'db> { )) } - pub(crate) fn from_overloads(callee: Type<'db>, overloads: impl IntoIterator>) -> Self { + pub(crate) fn from_overloads( + callee: Type<'db>, + overloads: impl IntoIterator>, + ) -> Self { Self::from(CallableBinding::from_overloads(callee, overloads)) } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index cc988fd9a6..ad6a4faa61 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3184,16 +3184,16 @@ impl KnownClass { } } - /// Evaluate a call to this known class, and emit any diagnostics that are necessary - /// as a result of the call. + /// Evaluate a call to this known class, emit any diagnostics that are necessary + /// as a result of the call, and return the type that results from the call. pub(super) fn check_call<'db>( self, context: &InferContext<'db, '_>, index: &SemanticIndex<'db>, - overload_binding: &mut Binding<'db>, + overload_binding: &Binding<'db>, call_argument_types: &CallArgumentTypes<'_, 'db>, call_expression: &ast::ExprCall, - ) { + ) -> Option> { let db = context.db(); let scope = context.scope(); let module = context.module(); @@ -3209,10 +3209,9 @@ impl KnownClass { let Some(enclosing_class) = nearest_enclosing_class(db, index, scope, module) else { - overload_binding.set_return_type(Type::unknown()); BoundSuperError::UnavailableImplicitArguments .report_diagnostic(context, call_expression.into()); - return; + return Some(Type::unknown()); }; // The type of the first parameter if the given scope is function-like (i.e. function or lambda). @@ -3232,10 +3231,9 @@ impl KnownClass { }; let Some(first_param) = first_param else { - overload_binding.set_return_type(Type::unknown()); BoundSuperError::UnavailableImplicitArguments .report_diagnostic(context, call_expression.into()); - return; + return Some(Type::unknown()); }; let definition = index.expect_single_definition(first_param); @@ -3252,7 +3250,7 @@ impl KnownClass { Type::unknown() }); - overload_binding.set_return_type(bound_super); + Some(bound_super) } [Some(pivot_class_type), Some(owner_type)] => { let bound_super = BoundSuperType::build(db, *pivot_class_type, *owner_type) @@ -3261,9 +3259,9 @@ impl KnownClass { Type::unknown() }); - overload_binding.set_return_type(bound_super); + Some(bound_super) } - _ => {} + _ => None, } } @@ -3278,14 +3276,12 @@ impl KnownClass { _ => None, } }) else { - if let Some(builder) = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) - { - builder.into_diagnostic( - "A legacy `typing.TypeVar` must be immediately assigned to a variable", - ); - } - return; + let builder = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; + builder.into_diagnostic( + "A legacy `typing.TypeVar` must be immediately assigned to a variable", + ); + return None; }; let [ @@ -3298,7 +3294,7 @@ impl KnownClass { _infer_variance, ] = overload_binding.parameter_types() else { - return; + return None; }; let covariant = covariant @@ -3311,39 +3307,30 @@ impl KnownClass { let variance = match (contravariant, covariant) { (Truthiness::Ambiguous, _) => { - let Some(builder) = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) - else { - return; - }; + let builder = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; builder.into_diagnostic( "The `contravariant` parameter of a legacy `typing.TypeVar` \ cannot have an ambiguous value", ); - return; + return None; } (_, Truthiness::Ambiguous) => { - let Some(builder) = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) - else { - return; - }; + let builder = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; builder.into_diagnostic( "The `covariant` parameter of a legacy `typing.TypeVar` \ cannot have an ambiguous value", ); - return; + return None; } (Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => { - let Some(builder) = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) - else { - return; - }; + let builder = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; builder.into_diagnostic( "A legacy `typing.TypeVar` cannot be both covariant and contravariant", ); - return; + return None; } (Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => { TypeVarVariance::Contravariant @@ -3357,11 +3344,8 @@ impl KnownClass { let name_param = name_param.into_string_literal().map(|name| name.value(db)); if name_param.is_none_or(|name_param| name_param != target.id) { - let Some(builder) = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) - else { - return; - }; + let builder = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; builder.into_diagnostic(format_args!( "The name of a legacy `typing.TypeVar`{} must match \ the name of the variable it is assigned to (`{}`)", @@ -3372,7 +3356,7 @@ impl KnownClass { }, target.id, )); - return; + return None; } let bound_or_constraint = match (bound, constraints) { @@ -3397,13 +3381,13 @@ impl KnownClass { // TODO: Emit a diagnostic that TypeVar cannot be both bounded and // constrained - (Some(_), Some(_)) => return, + (Some(_), Some(_)) => return None, (None, None) => None, }; let containing_assignment = index.expect_single_definition(target); - overload_binding.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar( + Some(Type::KnownInstance(KnownInstanceType::TypeVar( TypeVarInstance::new( db, target.id.clone(), @@ -3413,7 +3397,7 @@ impl KnownClass { *default, TypeVarKind::Legacy, ), - ))); + ))) } KnownClass::TypeAliasType => { @@ -3429,44 +3413,37 @@ impl KnownClass { }); let [Some(name), Some(value), ..] = overload_binding.parameter_types() else { - return; + return None; }; - if let Some(name) = name.into_string_literal() { - overload_binding.set_return_type(Type::KnownInstance( - KnownInstanceType::TypeAliasType(TypeAliasType::Bare( + name.into_string_literal() + .map(|name| { + Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::Bare( BareTypeAliasType::new( db, ast::name::Name::new(name.value(db)), containing_assignment, value, ), - )), - )); - } else if let Some(builder) = - context.report_lint(&INVALID_TYPE_ALIAS_TYPE, call_expression) - { - builder.into_diagnostic( - "The name of a `typing.TypeAlias` must be a string literal", - ); - } + ))) + }) + .or_else(|| { + let builder = + context.report_lint(&INVALID_TYPE_ALIAS_TYPE, call_expression)?; + builder.into_diagnostic( + "The name of a `typing.TypeAlias` must be a string literal", + ); + None + }) } - _ => {} + _ => None, } } - // TODO: Ideally we'd use `try_call_constructor` for all constructor calls. - // Currently we don't for a few special known types, either because their - // constructors are defined with overloads, or because we want to special case - // their return type beyond what typeshed provides (though this support could - // likely be moved into the `try_call_constructor` path). Once we support - // overloads, re-evaluate the need for these arms. - pub(super) fn bindings<'db>( - self, - db: &'db dyn Db, - callee: Type<'db>, - ) -> Option> { + /// Return the bindings we should use when inferring a constructor call for this special-cased class. + /// Return `None` if this class doesn't require special handling for calls to its constructor. + pub(super) fn bindings<'db>(self, db: &'db dyn Db, callee: Type<'db>) -> Option> { let bindings = match self { // ```py // class bool(int): @@ -3536,7 +3513,7 @@ impl KnownClass { .with_annotated_type(str_instance) .with_default_type(Type::string_literal(db, "strict")), ]), - Some(KnownClass::Str.to_instance(db)), + Some(str_instance), ), ], ) @@ -3655,7 +3632,7 @@ impl KnownClass { db, [Type::any(), none_instance], )) - .with_default_type(Type::none(db)), + .with_default_type(none_instance), Parameter::keyword_only(Name::new_static("default")) .type_form() .with_annotated_type(Type::any()) @@ -3738,19 +3715,19 @@ impl KnownClass { db, [CallableType::single(db, getter_signature), none_instance], )) - .with_default_type(Type::none(db)), + .with_default_type(none_instance), Parameter::positional_or_keyword(Name::new_static("fset")) .with_annotated_type(UnionType::from_elements( db, [CallableType::single(db, setter_signature), none_instance], )) - .with_default_type(Type::none(db)), + .with_default_type(none_instance), Parameter::positional_or_keyword(Name::new_static("fdel")) .with_annotated_type(UnionType::from_elements( db, [CallableType::single(db, deleter_signature), none_instance], )) - .with_default_type(Type::none(db)), + .with_default_type(none_instance), Parameter::positional_or_keyword(Name::new_static("doc")) .with_annotated_type(UnionType::from_elements( db, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index a93e2fb57c..3cb426adce 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5404,13 +5404,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let Some(known_class) = class.known(self.db()) else { continue; }; - known_class.check_call( + let overridden_return = known_class.check_call( &self.context, self.index, overload, &call_argument_types, call_expression, ); + if let Some(overridden_return) = overridden_return { + overload.set_return_type(overridden_return); + } } _ => {} }