diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 86bed2e1f8..0e15a8081f 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3387,10 +3387,10 @@ impl KnownClass { self, context: &InferContext<'db, '_>, index: &SemanticIndex<'db>, - overload_binding: &Binding<'db>, - call_argument_types: &CallArguments<'_, 'db>, + overload: &mut Binding<'db>, + call_arguments: &CallArguments<'_, 'db>, call_expression: &ast::ExprCall, - ) -> Option> { + ) { let db = context.db(); let scope = context.scope(); let module = context.module(); @@ -3401,14 +3401,15 @@ impl KnownClass { // In this case, we need to infer the two arguments: // 1. The nearest enclosing class // 2. The first parameter of the current function (typically `self` or `cls`) - match overload_binding.parameter_types() { + match overload.parameter_types() { [] => { let Some(enclosing_class) = nearest_enclosing_class(db, index, scope, module) else { BoundSuperError::UnavailableImplicitArguments .report_diagnostic(context, call_expression.into()); - return Some(Type::unknown()); + overload.set_return_type(Type::unknown()); + return; }; // The type of the first parameter if the given scope is function-like (i.e. function or lambda). @@ -3430,7 +3431,8 @@ impl KnownClass { let Some(first_param) = first_param else { BoundSuperError::UnavailableImplicitArguments .report_diagnostic(context, call_expression.into()); - return Some(Type::unknown()); + overload.set_return_type(Type::unknown()); + return; }; let definition = index.expect_single_definition(first_param); @@ -3447,7 +3449,7 @@ impl KnownClass { Type::unknown() }); - Some(bound_super) + overload.set_return_type(bound_super); } [Some(pivot_class_type), Some(owner_type)] => { let bound_super = BoundSuperType::build(db, *pivot_class_type, *owner_type) @@ -3455,10 +3457,9 @@ impl KnownClass { err.report_diagnostic(context, call_expression.into()); Type::unknown() }); - - Some(bound_super) + overload.set_return_type(bound_super); } - _ => None, + _ => {} } } @@ -3473,12 +3474,14 @@ impl KnownClass { _ => None, } }) else { - 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; + 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 [ @@ -3489,9 +3492,9 @@ impl KnownClass { contravariant, covariant, _infer_variance, - ] = overload_binding.parameter_types() + ] = overload.parameter_types() else { - return None; + return; }; let covariant = covariant @@ -3504,30 +3507,37 @@ impl KnownClass { let variance = match (contravariant, covariant) { (Truthiness::Ambiguous, _) => { - let builder = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; - builder.into_diagnostic( - "The `contravariant` parameter of a legacy `typing.TypeVar` \ + if let Some(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 None; + ); + } + return; } (_, Truthiness::Ambiguous) => { - let builder = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; - builder.into_diagnostic( - "The `covariant` parameter of a legacy `typing.TypeVar` \ + if let Some(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 None; + ); + } + return; } (Truthiness::AlwaysTrue, Truthiness::AlwaysTrue) => { - 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 None; + if let Some(builder) = + context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression) + { + builder.into_diagnostic( + "A legacy `typing.TypeVar` cannot be both \ + covariant and contravariant", + ); + } + return; } (Truthiness::AlwaysTrue, Truthiness::AlwaysFalse) => { TypeVarVariance::Contravariant @@ -3541,19 +3551,21 @@ 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 builder = - context.report_lint(&INVALID_LEGACY_TYPE_VARIABLE, call_expression)?; - builder.into_diagnostic(format_args!( - "The name of a legacy `typing.TypeVar`{} must match \ + if let Some(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 (`{}`)", - if let Some(name_param) = name_param { - format!(" (`{name_param}`)") - } else { - String::new() - }, - target.id, - )); - return None; + if let Some(name_param) = name_param { + format!(" (`{name_param}`)") + } else { + String::new() + }, + target.id, + )); + } + return; } let bound_or_constraint = match (bound, constraints) { @@ -3568,8 +3580,8 @@ impl KnownClass { // typevar constraints. let elements = UnionType::new( db, - overload_binding - .arguments_for_parameter(call_argument_types, 1) + overload + .arguments_for_parameter(call_arguments, 1) .map(|(_, ty)| ty) .collect::>(), ); @@ -3578,13 +3590,13 @@ impl KnownClass { // TODO: Emit a diagnostic that TypeVar cannot be both bounded and // constrained - (Some(_), Some(_)) => return None, + (Some(_), Some(_)) => return, (None, None) => None, }; let containing_assignment = index.expect_single_definition(target); - Some(Type::KnownInstance(KnownInstanceType::TypeVar( + overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeVar( TypeVarInstance::new( db, &target.id, @@ -3594,7 +3606,7 @@ impl KnownClass { *default, TypeVarKind::Legacy, ), - ))) + ))); } KnownClass::TypeAliasType => { @@ -3609,32 +3621,31 @@ impl KnownClass { } }); - let [Some(name), Some(value), ..] = overload_binding.parameter_types() else { - return None; + let [Some(name), Some(value), ..] = overload.parameter_types() else { + return; }; - 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, - ), - ))) - }) - .or_else(|| { - let builder = - context.report_lint(&INVALID_TYPE_ALIAS_TYPE, call_expression)?; + let Some(name) = name.into_string_literal() 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", ); - None - }) + } + return; + }; + overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeAliasType( + TypeAliasType::Bare(BareTypeAliasType::new( + db, + ast::name::Name::new(name.value(db)), + containing_assignment, + value, + )), + ))); } - _ => None, + _ => {} } } } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index b97c12960c..9909da3fea 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -64,6 +64,7 @@ use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::place::ScopeId; use crate::semantic_index::semantic_index; +use crate::types::call::{Binding, CallArguments}; use crate::types::context::InferContext; use crate::types::diagnostic::{ REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE, @@ -76,7 +77,7 @@ use crate::types::signatures::{CallableSignature, Signature}; use crate::types::visitor::any_over_type; use crate::types::{ BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation, - TypeTransformer, TypeVarInstance, walk_type_mapping, + TypeTransformer, TypeVarInstance, UnionBuilder, walk_type_mapping, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -1039,86 +1040,90 @@ impl KnownFunction { pub(super) fn check_call<'db>( self, context: &InferContext<'db, '_>, - parameter_types: &[Option>], + overload: &mut Binding<'db>, + call_arguments: &CallArguments<'_, 'db>, call_expression: &ast::ExprCall, file: File, - ) -> Option> { + ) { let db = context.db(); + let parameter_types = overload.parameter_types(); match self { KnownFunction::RevealType => { - let [Some(revealed_type)] = parameter_types else { - return None; - }; - let builder = - context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info)?; - let mut diag = builder.into_diagnostic("Revealed type"); - let span = context.span(&call_expression.arguments.args[0]); - diag.annotate( - Annotation::primary(span) - .message(format_args!("`{}`", revealed_type.display(db))), - ); - None + let revealed_type = overload + .arguments_for_parameter(call_arguments, 0) + .fold(UnionBuilder::new(db), |builder, (_, ty)| builder.add(ty)) + .build(); + if let Some(builder) = + context.report_diagnostic(DiagnosticId::RevealedType, Severity::Info) + { + let mut diag = builder.into_diagnostic("Revealed type"); + let span = context.span(&call_expression.arguments.args[0]); + diag.annotate( + Annotation::primary(span) + .message(format_args!("`{}`", revealed_type.display(db))), + ); + } } + KnownFunction::AssertType => { let [Some(actual_ty), Some(asserted_ty)] = parameter_types else { - return None; + return; }; - if actual_ty.is_equivalent_to(db, *asserted_ty) { - return None; + return; } - let builder = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)?; + if let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression) + { + let mut diagnostic = builder.into_diagnostic(format_args!( + "Argument does not have asserted type `{}`", + asserted_ty.display(db), + )); - let mut diagnostic = builder.into_diagnostic(format_args!( - "Argument does not have asserted type `{}`", - asserted_ty.display(db), - )); + diagnostic.annotate( + Annotation::secondary(context.span(&call_expression.arguments.args[0])) + .message(format_args!( + "Inferred type of argument is `{}`", + actual_ty.display(db), + )), + ); - diagnostic.annotate( - Annotation::secondary(context.span(&call_expression.arguments.args[0])) - .message(format_args!( - "Inferred type of argument is `{}`", - actual_ty.display(db), - )), - ); - - diagnostic.info(format_args!( - "`{asserted_type}` and `{inferred_type}` are not equivalent types", - asserted_type = asserted_ty.display(db), - inferred_type = actual_ty.display(db), - )); - - None + diagnostic.info(format_args!( + "`{asserted_type}` and `{inferred_type}` are not equivalent types", + asserted_type = asserted_ty.display(db), + inferred_type = actual_ty.display(db), + )); + } } + KnownFunction::AssertNever => { let [Some(actual_ty)] = parameter_types else { - return None; + return; }; if actual_ty.is_equivalent_to(db, Type::Never) { - return None; + return; + } + if let Some(builder) = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression) + { + let mut diagnostic = + builder.into_diagnostic("Argument does not have asserted type `Never`"); + diagnostic.annotate( + Annotation::secondary(context.span(&call_expression.arguments.args[0])) + .message(format_args!( + "Inferred type of argument is `{}`", + actual_ty.display(db) + )), + ); + diagnostic.info(format_args!( + "`Never` and `{inferred_type}` are not equivalent types", + inferred_type = actual_ty.display(db), + )); } - let builder = context.report_lint(&TYPE_ASSERTION_FAILURE, call_expression)?; - - let mut diagnostic = - builder.into_diagnostic("Argument does not have asserted type `Never`"); - diagnostic.annotate( - Annotation::secondary(context.span(&call_expression.arguments.args[0])) - .message(format_args!( - "Inferred type of argument is `{}`", - actual_ty.display(db) - )), - ); - diagnostic.info(format_args!( - "`Never` and `{inferred_type}` are not equivalent types", - inferred_type = actual_ty.display(db), - )); - - None } + KnownFunction::StaticAssert => { let [Some(parameter_ty), message] = parameter_types else { - return None; + return; }; let truthiness = match parameter_ty.try_bool(db) { Ok(truthiness) => truthiness, @@ -1138,41 +1143,42 @@ impl KnownFunction { err.report_diagnostic(context, condition); - return None; + return; } }; - let builder = context.report_lint(&STATIC_ASSERT_ERROR, call_expression)?; - if truthiness.is_always_true() { - return None; - } - if let Some(message) = message - .and_then(Type::into_string_literal) - .map(|s| s.value(db)) - { - builder.into_diagnostic(format_args!("Static assertion error: {message}")); - } else if *parameter_ty == Type::BooleanLiteral(false) { - builder - .into_diagnostic("Static assertion error: argument evaluates to `False`"); - } else if truthiness.is_always_false() { - builder.into_diagnostic(format_args!( - "Static assertion error: argument of type `{parameter_ty}` \ + if let Some(builder) = context.report_lint(&STATIC_ASSERT_ERROR, call_expression) { + if truthiness.is_always_true() { + return; + } + if let Some(message) = message + .and_then(Type::into_string_literal) + .map(|s| s.value(db)) + { + builder.into_diagnostic(format_args!("Static assertion error: {message}")); + } else if *parameter_ty == Type::BooleanLiteral(false) { + builder.into_diagnostic( + "Static assertion error: argument evaluates to `False`", + ); + } else if truthiness.is_always_false() { + builder.into_diagnostic(format_args!( + "Static assertion error: argument of type `{parameter_ty}` \ is statically known to be falsy", - parameter_ty = parameter_ty.display(db) - )); - } else { - builder.into_diagnostic(format_args!( - "Static assertion error: argument of type `{parameter_ty}` \ + parameter_ty = parameter_ty.display(db) + )); + } else { + builder.into_diagnostic(format_args!( + "Static assertion error: argument of type `{parameter_ty}` \ has an ambiguous static truthiness", - parameter_ty = parameter_ty.display(db) - )); + parameter_ty = parameter_ty.display(db) + )); + } } - - None } + KnownFunction::Cast => { let [Some(casted_type), Some(source_type)] = parameter_types else { - return None; + return; }; let contains_unknown_or_todo = |ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any); @@ -1180,31 +1186,34 @@ impl KnownFunction { && !any_over_type(db, *source_type, &contains_unknown_or_todo) && !any_over_type(db, *casted_type, &contains_unknown_or_todo) { - let builder = context.report_lint(&REDUNDANT_CAST, call_expression)?; - builder.into_diagnostic(format_args!( - "Value is already of type `{}`", - casted_type.display(db), - )); + if let Some(builder) = context.report_lint(&REDUNDANT_CAST, call_expression) { + builder.into_diagnostic(format_args!( + "Value is already of type `{}`", + casted_type.display(db), + )); + } } - None } + KnownFunction::GetProtocolMembers => { let [Some(Type::ClassLiteral(class))] = parameter_types else { - return None; + return; }; if class.is_protocol(db) { - return None; + return; } report_bad_argument_to_get_protocol_members(context, call_expression, *class); - None } + KnownFunction::IsInstance | KnownFunction::IsSubclass => { let [_, Some(Type::ClassLiteral(class))] = parameter_types else { - return None; + return; + }; + let Some(protocol_class) = class.into_protocol_class(db) else { + return; }; - let protocol_class = class.into_protocol_class(db)?; if protocol_class.is_runtime_checkable(db) { - return None; + return; } report_runtime_check_against_non_runtime_checkable_protocol( context, @@ -1212,16 +1221,16 @@ impl KnownFunction { protocol_class, self, ); - None } + known @ (KnownFunction::DunderImport | KnownFunction::ImportModule) => { let [Some(Type::StringLiteral(full_module_name)), rest @ ..] = parameter_types else { - return None; + return; }; if rest.iter().any(Option::is_some) { - return None; + return; } let module_name = full_module_name.value(db); @@ -1231,16 +1240,20 @@ impl KnownFunction { // `importlib.import_module("collections.abc")` returns the `collections.abc` module. // ty doesn't have a way to represent the return type of the former yet. // https://github.com/astral-sh/ruff/pull/19008#discussion_r2173481311 - return None; + return; } - let module_name = ModuleName::new(module_name)?; - let module = resolve_module(db, &module_name)?; + let Some(module_name) = ModuleName::new(module_name) else { + return; + }; + let Some(module) = resolve_module(db, &module_name) else { + return; + }; - Some(Type::module_literal(db, file, &module)) + overload.set_return_type(Type::module_literal(db, file, &module)); } - _ => None, + _ => {} } } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 15c280f92f..8d6211d771 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5615,30 +5615,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { match binding_type { Type::FunctionLiteral(function_literal) => { if let Some(known_function) = function_literal.known(self.db()) { - if let Some(return_type) = known_function.check_call( + known_function.check_call( &self.context, - overload.parameter_types(), + overload, + &call_arguments, call_expression, self.file(), - ) { - overload.set_return_type(return_type); - } + ); } } - Type::ClassLiteral(class) => { - let Some(known_class) = class.known(self.db()) else { - continue; - }; - let overridden_return = known_class.check_call( - &self.context, - self.index, - overload, - &call_arguments, - call_expression, - ); - if let Some(overridden_return) = overridden_return { - overload.set_return_type(overridden_return); + if let Some(known_class) = class.known(self.db()) { + known_class.check_call( + &self.context, + self.index, + overload, + &call_arguments, + call_expression, + ); } } _ => {}