Compare commits

...

5 Commits

Author SHA1 Message Date
Micha Reiser
20cfc116db Remove nested PossiblyUndefined variants 2025-02-12 18:49:38 +01:00
Micha Reiser
44eaf112f2 [red-knot] Outline integration point for len diagnostics 2025-02-12 18:17:39 +01:00
Micha Reiser
302b64b139 More simplification 2025-02-12 16:04:42 +01:00
Micha Reiser
34ef941f0c Extract check_call function so that we can use early-returns 2025-02-12 14:04:41 +01:00
Micha Reiser
daf19cc40a Move reveal_type, assert_type handling out of CallOutcome 2025-02-12 13:42:02 +01:00
3 changed files with 222 additions and 252 deletions

View File

@@ -5,7 +5,6 @@ use context::InferContext;
use diagnostic::{report_not_iterable, report_not_iterable_possibly_unbound};
use indexmap::IndexSet;
use itertools::Itertools;
use ruff_db::diagnostic::Severity;
use ruff_db::files::File;
use ruff_python_ast as ast;
use type_ordering::union_elements_ordering;
@@ -36,7 +35,7 @@ use crate::stdlib::{builtins_symbol, known_module_symbol, typing_extensions_symb
use crate::suppression::check_suppressions;
use crate::symbol::{Boundness, Symbol};
use crate::types::call::{
bind_call, CallArguments, CallBinding, CallDunderResult, CallOutcome, StaticAssertionErrorKind,
bind_call, CallArguments, CallBinding, CallDunderLenOutcome, CallDunderOutcome, CallOutcome,
};
use crate::types::class_base::ClassBase;
use crate::types::diagnostic::INVALID_TYPE_FORM;
@@ -1892,21 +1891,40 @@ impl<'db> Type<'db> {
}
}
/// Return the type of `len()` on a type if it is known more precisely than `int`,
/// Return the type of calling the `__len__` method on a type.
fn __len__(&self, db: &'db dyn Db) -> CallDunderLenOutcome<'db> {
let statically_known_len = match self {
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
Type::StringLiteral(string) => Some(string.python_len(db)),
Type::Tuple(tuple) => Some(tuple.len(db)),
_ => None,
};
if let Some(len) = statically_known_len {
return CallDunderLenOutcome::StaticallyKnown(len);
}
match self.call_dunder(db, "__len__", &CallArguments::positional([*self])) {
CallDunderOutcome::MethodNotAvailable => CallDunderLenOutcome::MethodNotAvailable,
CallDunderOutcome::Call(outcome) => CallDunderLenOutcome::Call(outcome),
}
}
/// Return the type of `len(type)` on a type if it is known more precisely than `int`,
/// or `None` otherwise.
///
/// In the second case, the return type of `len()` in `typeshed` (`int`)
/// is used as a fallback.
#[must_use]
fn len(&self, db: &'db dyn Db) -> Option<Type<'db>> {
fn non_negative_int_literal<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
match ty {
// TODO: Emit diagnostic for non-integers and negative integers
Type::IntLiteral(value) => (value >= 0).then_some(ty),
Type::BooleanLiteral(value) => Some(Type::IntLiteral(value.into())),
Type::Union(union) => {
let mut builder = UnionBuilder::new(db);
for element in union.elements(db) {
builder = builder.add(non_negative_int_literal(db, *element)?);
for &element in union.elements(db) {
builder = builder.add(non_negative_int_literal(db, element)?);
}
Some(builder.build())
}
@@ -1914,70 +1932,17 @@ impl<'db> Type<'db> {
}
}
let usize_len = match self {
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
Type::StringLiteral(string) => Some(string.python_len(db)),
Type::Tuple(tuple) => Some(tuple.len(db)),
_ => None,
};
let len = self.__len__(db).return_type(db)?;
if let Some(usize_len) = usize_len {
return usize_len.try_into().ok().map(Type::IntLiteral);
}
let return_ty = match self.call_dunder(db, "__len__", &CallArguments::positional([*self])) {
// TODO: emit a diagnostic
CallDunderResult::MethodNotAvailable => return None,
CallDunderResult::CallOutcome(outcome) | CallDunderResult::PossiblyUnbound(outcome) => {
outcome.return_type(db)?
}
};
non_negative_int_literal(db, return_ty)
non_negative_int_literal(db, len)
}
/// Return the outcome of calling an object of this type.
#[must_use]
fn call(self, db: &'db dyn Db, arguments: &CallArguments<'_, 'db>) -> CallOutcome<'db> {
match self {
Type::FunctionLiteral(function_type) => {
let mut binding = bind_call(db, arguments, function_type.signature(db), Some(self));
match function_type.known(db) {
Some(KnownFunction::RevealType) => {
let revealed_ty = binding.one_parameter_type().unwrap_or(Type::unknown());
CallOutcome::revealed(binding, revealed_ty)
}
Some(KnownFunction::StaticAssert) => {
if let Some((parameter_ty, message)) = binding.two_parameter_types() {
let truthiness = parameter_ty.bool(db);
if truthiness.is_always_true() {
CallOutcome::callable(binding)
} else {
let error_kind = if let Some(message) =
message.into_string_literal().map(|s| &**s.value(db))
{
StaticAssertionErrorKind::CustomError(message)
} else if parameter_ty == Type::BooleanLiteral(false) {
StaticAssertionErrorKind::ArgumentIsFalse
} else if truthiness.is_always_false() {
StaticAssertionErrorKind::ArgumentIsFalsy(parameter_ty)
} else {
StaticAssertionErrorKind::ArgumentTruthinessIsAmbiguous(
parameter_ty,
)
};
CallOutcome::StaticAssertionError {
binding,
error_kind,
}
}
} else {
CallOutcome::callable(binding)
}
}
Some(KnownFunction::IsEquivalentTo) => {
let (ty_a, ty_b) = binding
.two_parameter_types()
@@ -2052,14 +2017,6 @@ impl<'db> Type<'db> {
CallOutcome::callable(binding)
}
Some(KnownFunction::AssertType) => {
let Some((_, asserted_ty)) = binding.two_parameter_types() else {
return CallOutcome::callable(binding);
};
CallOutcome::asserted(binding, asserted_ty)
}
Some(KnownFunction::Cast) => {
// TODO: Use `.two_parameter_tys()` exclusively
// when overloads are supported.
@@ -2101,23 +2058,26 @@ impl<'db> Type<'db> {
instance_ty @ Type::Instance(_) => {
match instance_ty.call_dunder(db, "__call__", &arguments.with_self(instance_ty)) {
CallDunderResult::CallOutcome(CallOutcome::NotCallable { .. }) => {
CallDunderOutcome::Call(CallOutcome::NotCallable { .. }) => {
// Turn "`<type of illegal '__call__'>` not callable" into
// "`X` not callable"
CallOutcome::NotCallable {
not_callable_ty: self,
}
}
CallDunderResult::CallOutcome(outcome) => outcome,
CallDunderResult::PossiblyUnbound(call_outcome) => {
// Turn "possibly unbound object of type `Literal['__call__']`"
// into "`X` not callable (possibly unbound `__call__` method)"
CallOutcome::PossiblyUnboundDunderCall {
called_ty: self,
call_outcome: Box::new(call_outcome),
}
}
CallDunderResult::MethodNotAvailable => {
// Turn "possibly unbound object of type `Literal['__call__']`"
// into "`X` not callable (possibly unbound `__call__` method)"
CallDunderOutcome::Call(CallOutcome::PossiblyUnboundDunderCall {
called_ty: _,
call_outcome,
}) => CallOutcome::PossiblyUnboundDunderCall {
called_ty: self,
call_outcome,
},
CallDunderOutcome::Call(outcome) => outcome,
CallDunderOutcome::MethodNotAvailable => {
// Turn "`X.__call__` unbound" into "`X` not callable"
CallOutcome::NotCallable {
not_callable_ty: self,
@@ -2151,7 +2111,6 @@ impl<'db> Type<'db> {
/// `receiver_ty` must be `Type::Instance(_)` or `Type::ClassLiteral`.
///
/// TODO: handle `super()` objects properly
#[must_use]
fn call_bound(
self,
db: &'db dyn Db,
@@ -2197,15 +2156,18 @@ impl<'db> Type<'db> {
db: &'db dyn Db,
name: &str,
arguments: &CallArguments<'_, 'db>,
) -> CallDunderResult<'db> {
) -> CallDunderOutcome<'db> {
match self.to_meta_type(db).member(db, name) {
Symbol::Type(callable_ty, Boundness::Bound) => {
CallDunderResult::CallOutcome(callable_ty.call(db, arguments))
CallDunderOutcome::Call(callable_ty.call(db, arguments))
}
Symbol::Type(callable_ty, Boundness::PossiblyUnbound) => {
CallDunderResult::PossiblyUnbound(callable_ty.call(db, arguments))
CallDunderOutcome::Call(CallOutcome::PossiblyUnboundDunderCall {
call_outcome: Box::new(callable_ty.call(db, arguments)),
called_ty: callable_ty,
})
}
Symbol::Unbound => CallDunderResult::MethodNotAvailable,
Symbol::Unbound => CallDunderOutcome::MethodNotAvailable,
}
}
@@ -2227,8 +2189,7 @@ impl<'db> Type<'db> {
let dunder_iter_result =
self.call_dunder(db, "__iter__", &CallArguments::positional([self]));
match dunder_iter_result {
CallDunderResult::CallOutcome(ref call_outcome)
| CallDunderResult::PossiblyUnbound(ref call_outcome) => {
CallDunderOutcome::Call(ref call_outcome) => {
let Some(iterator_ty) = call_outcome.return_type(db) else {
return IterationOutcome::NotIterable {
not_iterable_ty: self,
@@ -2239,7 +2200,7 @@ impl<'db> Type<'db> {
.call_dunder(db, "__next__", &CallArguments::positional([iterator_ty]))
.return_type(db)
{
if matches!(dunder_iter_result, CallDunderResult::PossiblyUnbound(..)) {
if call_outcome.is_possibly_unbound() {
IterationOutcome::PossiblyUnboundDunderIter {
iterable_ty: self,
element_ty,
@@ -2253,7 +2214,7 @@ impl<'db> Type<'db> {
}
};
}
CallDunderResult::MethodNotAvailable => {}
CallDunderOutcome::MethodNotAvailable => {}
}
// Although it's not considered great practice,
@@ -4074,10 +4035,7 @@ impl<'db> Class<'db> {
// TODO we should also check for binding errors that would indicate the metaclass
// does not accept the right arguments
CallOutcome::Callable { binding }
| CallOutcome::RevealType { binding, .. }
| CallOutcome::StaticAssertionError { binding, .. }
| CallOutcome::AssertType { binding, .. } => Ok(binding.return_type()),
CallOutcome::Callable { binding } => Ok(binding.return_type()),
};
return return_ty_result.map(|ty| ty.to_meta_type(db));

View File

@@ -1,9 +1,7 @@
use super::context::InferContext;
use super::diagnostic::{CALL_NON_CALLABLE, TYPE_ASSERTION_FAILURE};
use super::{Severity, Signature, Type, TypeArrayDisplay, UnionBuilder};
use crate::types::diagnostic::STATIC_ASSERT_ERROR;
use super::diagnostic::CALL_NON_CALLABLE;
use super::{Signature, Type, TypeArrayDisplay, UnionBuilder};
use crate::Db;
use ruff_db::diagnostic::DiagnosticId;
use ruff_python_ast as ast;
mod arguments;
@@ -12,23 +10,12 @@ mod bind;
pub(super) use arguments::{Argument, CallArguments};
pub(super) use bind::{bind_call, CallBinding};
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum StaticAssertionErrorKind<'db> {
ArgumentIsFalse,
ArgumentIsFalsy(Type<'db>),
ArgumentTruthinessIsAmbiguous(Type<'db>),
CustomError(&'db str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum CallOutcome<'db> {
pub(crate) enum CallOutcome<'db> {
Callable {
binding: CallBinding<'db>,
},
RevealType {
binding: CallBinding<'db>,
revealed_ty: Type<'db>,
},
NotCallable {
not_callable_ty: Type<'db>,
},
@@ -40,14 +27,6 @@ pub(super) enum CallOutcome<'db> {
called_ty: Type<'db>,
call_outcome: Box<CallOutcome<'db>>,
},
StaticAssertionError {
binding: CallBinding<'db>,
error_kind: StaticAssertionErrorKind<'db>,
},
AssertType {
binding: CallBinding<'db>,
asserted_ty: Type<'db>,
},
}
impl<'db> CallOutcome<'db> {
@@ -61,14 +40,6 @@ impl<'db> CallOutcome<'db> {
CallOutcome::NotCallable { not_callable_ty }
}
/// Create a new `CallOutcome::RevealType` with given revealed and return types.
pub(super) fn revealed(binding: CallBinding<'db>, revealed_ty: Type<'db>) -> CallOutcome<'db> {
CallOutcome::RevealType {
binding,
revealed_ty,
}
}
/// Create a new `CallOutcome::Union` with given wrapped outcomes.
pub(super) fn union(
called_ty: Type<'db>,
@@ -80,22 +51,14 @@ impl<'db> CallOutcome<'db> {
}
}
/// Create a new `CallOutcome::AssertType` with given asserted and return types.
pub(super) fn asserted(binding: CallBinding<'db>, asserted_ty: Type<'db>) -> CallOutcome<'db> {
CallOutcome::AssertType {
binding,
asserted_ty,
}
pub(super) fn is_possibly_unbound(&self) -> bool {
matches!(self, Self::PossiblyUnboundDunderCall { .. })
}
/// Get the return type of the call, or `None` if not callable.
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
Self::Callable { binding } => Some(binding.return_type()),
Self::RevealType {
binding,
revealed_ty: _,
} => Some(binding.return_type()),
Self::NotCallable { not_callable_ty: _ } => None,
Self::Union {
outcomes,
@@ -114,11 +77,6 @@ impl<'db> CallOutcome<'db> {
})
.map(UnionBuilder::build),
Self::PossiblyUnboundDunderCall { call_outcome, .. } => call_outcome.return_type(db),
Self::StaticAssertionError { .. } => Some(Type::none(db)),
Self::AssertType {
binding,
asserted_ty: _,
} => Some(binding.return_type()),
}
}
@@ -204,22 +162,12 @@ impl<'db> CallOutcome<'db> {
// only non-callable diagnostics in the union case, which is inconsistent.
match self {
Self::Callable { binding } => {
// TODO: Move this out of the `CallOutcome` and into `TypeInferenceBuilder`?
// This check is required everywhere where we call `return_type_result`
// from the TypeInferenceBuilder.
binding.report_diagnostics(context, node);
Ok(binding.return_type())
}
Self::RevealType {
binding,
revealed_ty,
} => {
binding.report_diagnostics(context, node);
context.report_diagnostic(
node,
DiagnosticId::RevealedType,
Severity::Info,
format_args!("Revealed type is `{}`", revealed_ty.display(context.db())),
);
Ok(binding.return_type())
}
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
not_callable_ty: *not_callable_ty,
return_ty: Type::unknown(),
@@ -239,24 +187,12 @@ impl<'db> CallOutcome<'db> {
} => {
let mut not_callable = vec![];
let mut union_builder = UnionBuilder::new(context.db());
let mut revealed = false;
for outcome in outcomes {
let return_ty = match outcome {
Self::NotCallable { not_callable_ty } => {
not_callable.push(*not_callable_ty);
Type::unknown()
}
Self::RevealType {
binding,
revealed_ty: _,
} => {
if revealed {
binding.return_type()
} else {
revealed = true;
outcome.unwrap_with_diagnostic(context, node)
}
}
_ => outcome.unwrap_with_diagnostic(context, node),
};
union_builder = union_builder.add(return_ty);
@@ -280,88 +216,27 @@ impl<'db> CallOutcome<'db> {
}),
}
}
Self::StaticAssertionError {
binding,
error_kind,
} => {
binding.report_diagnostics(context, node);
match error_kind {
StaticAssertionErrorKind::ArgumentIsFalse => {
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!("Static assertion error: argument evaluates to `False`"),
);
}
StaticAssertionErrorKind::ArgumentIsFalsy(parameter_ty) => {
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!(
"Static assertion error: argument of type `{parameter_ty}` is statically known to be falsy",
parameter_ty=parameter_ty.display(context.db())
),
);
}
StaticAssertionErrorKind::ArgumentTruthinessIsAmbiguous(parameter_ty) => {
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!(
"Static assertion error: argument of type `{parameter_ty}` has an ambiguous static truthiness",
parameter_ty=parameter_ty.display(context.db())
),
);
}
StaticAssertionErrorKind::CustomError(message) => {
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!("Static assertion error: {message}"),
);
}
}
Ok(Type::unknown())
}
Self::AssertType {
binding,
asserted_ty,
} => {
let [actual_ty, _asserted] = binding.parameter_types() else {
return Ok(binding.return_type());
};
if !actual_ty.is_gradual_equivalent_to(context.db(), *asserted_ty) {
context.report_lint(
&TYPE_ASSERTION_FAILURE,
node,
format_args!(
"Actual type `{}` is not the same as asserted type `{}`",
actual_ty.display(context.db()),
asserted_ty.display(context.db()),
),
);
}
Ok(binding.return_type())
}
}
}
}
pub(super) enum CallDunderResult<'db> {
CallOutcome(CallOutcome<'db>),
PossiblyUnbound(CallOutcome<'db>),
#[must_use]
#[derive(Debug)]
pub(super) enum CallDunderOutcome<'db> {
Call(CallOutcome<'db>),
MethodNotAvailable,
}
impl<'db> CallDunderResult<'db> {
impl<'db> CallDunderOutcome<'db> {
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
Self::CallOutcome(outcome) => outcome.return_type(db),
Self::PossiblyUnbound { .. } => None,
Self::Call(outcome) => {
if outcome.is_possibly_unbound() {
None
} else {
outcome.return_type(db)
}
}
Self::MethodNotAvailable => None,
}
}
@@ -421,3 +296,29 @@ impl<'db> NotCallableError<'db> {
}
}
}
#[must_use]
#[derive(Debug)]
pub(super) enum CallDunderLenOutcome<'db> {
/// The length is statically known.
StaticallyKnown(usize),
/// The length is determined by calling `__len__`.
Call(CallOutcome<'db>),
/// The object doesn't have a `__len__` method and, thus, doesn't implement sized.
MethodNotAvailable,
}
impl<'db> CallDunderLenOutcome<'db> {
pub(super) fn return_type(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
CallDunderLenOutcome::StaticallyKnown(len) => {
// TODO: Fall back to `int` if value is too large?
i64::try_from(*len).ok().map(Type::IntLiteral)
}
CallDunderLenOutcome::Call(call_outcome) => call_outcome.return_type(db),
CallDunderLenOutcome::MethodNotAvailable => None,
}
}
}

View File

@@ -29,6 +29,7 @@
use std::num::NonZeroU32;
use itertools::{Either, Itertools};
use ruff_db::diagnostic::{DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext};
@@ -49,7 +50,9 @@ use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
use crate::semantic_index::SemanticIndex;
use crate::stdlib::builtins_module_scope;
use crate::types::call::{Argument, CallArguments};
use crate::types::call::{
Argument, CallArguments, CallDunderLenOutcome, CallDunderOutcome, CallOutcome,
};
use crate::types::diagnostic::{
report_invalid_arguments_to_annotated, report_invalid_assignment,
report_invalid_attribute_assignment, report_unresolved_module, TypeCheckDiagnostics,
@@ -58,18 +61,19 @@ use crate::types::diagnostic::{
INCONSISTENT_MRO, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_CONTEXT_MANAGER,
INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
};
use crate::types::mro::MroErrorKind;
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
builtins_symbol, global_symbol, symbol, symbol_from_bindings, symbol_from_declarations,
todo_type, typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType,
DynamicType, FunctionType, InstanceType, IntersectionBuilder, IntersectionType,
IterationOutcome, KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate,
MetaclassErrorKind, SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness,
TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers,
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
todo_type, typing_extensions_symbol, Boundness, Class, ClassLiteralType, DynamicType,
FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome,
KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind,
SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@@ -3245,9 +3249,117 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_or_default();
let call_arguments = self.infer_arguments(arguments, parameter_expectations);
function_type
.call(self.db(), &call_arguments)
.unwrap_with_diagnostic(&self.context, call_expression.into())
let call_outcome = self.check_call(function_type, &call_arguments, call_expression);
call_outcome.unwrap_with_diagnostic(&self.context, call_expression.into())
}
fn check_call(
&self,
callee: Type<'db>,
arguments: &CallArguments<'_, 'db>,
call_expression: &ast::ExprCall,
) -> CallOutcome<'db> {
let call = callee.call(self.db(), arguments);
let Type::FunctionLiteral(function_type) = callee else {
return call;
};
let CallOutcome::Callable { binding } = &call else {
return call;
};
let Some(known) = function_type.known(self.db()) else {
return call;
};
match known {
KnownFunction::RevealType => {
let revealed_ty = binding.one_parameter_type().unwrap_or(Type::unknown());
self.context.report_diagnostic(
call_expression.into(),
DiagnosticId::RevealedType,
Severity::Info,
format_args!("Revealed type is `{}`", revealed_ty.display(self.db())),
);
}
KnownFunction::AssertType => {
let [actual_ty, asserted_ty] = binding.parameter_types() else {
return call;
};
if !actual_ty.is_gradual_equivalent_to(self.db(), *asserted_ty) {
self.context.report_lint(
&TYPE_ASSERTION_FAILURE,
call_expression.into(),
format_args!(
"Actual type `{}` is not the same as asserted type `{}`",
actual_ty.display(self.db()),
asserted_ty.display(self.db()),
),
);
}
}
KnownFunction::StaticAssert => {
let Some((parameter_ty, message)) = binding.two_parameter_types() else {
return call;
};
let truthiness = parameter_ty.bool(self.db());
if !truthiness.is_always_true() {
if let Some(message) =
message.into_string_literal().map(|s| &**s.value(self.db()))
{
self.context.report_lint(
&STATIC_ASSERT_ERROR,
call_expression.into(),
format_args!("Static assertion error: {message}"),
);
} else if parameter_ty == Type::BooleanLiteral(false) {
self.context.report_lint(
&STATIC_ASSERT_ERROR,
call_expression.into(),
format_args!("Static assertion error: argument evaluates to `False`"),
);
} else if truthiness.is_always_false() {
self.context.report_lint(
&STATIC_ASSERT_ERROR,
call_expression.into(),
format_args!(
"Static assertion error: argument of type `{parameter_ty}` is statically known to be falsy",
parameter_ty=parameter_ty.display(self.db())
),
);
} else {
self.context.report_lint(
&STATIC_ASSERT_ERROR,
call_expression.into(),
format_args!(
"Static assertion error: argument of type `{parameter_ty}` has an ambiguous static truthiness",
parameter_ty=parameter_ty.display(self.db())
),
);
};
}
}
KnownFunction::Len => {
let Some(sized) = binding.one_parameter_type() else {
return call;
};
if let CallDunderLenOutcome::Call(_) = sized.__len__(self.db()) {
// TODO: Assert that the argument implements the `Sized` protocol (correctly).
}
}
_ => {}
}
call
}
fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
@@ -3567,8 +3679,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
};
if let CallDunderResult::CallOutcome(call)
| CallDunderResult::PossiblyUnbound(call) = operand_type.call_dunder(
if let CallDunderOutcome::Call(call) = operand_type.call_dunder(
self.db(),
unary_dunder_method,
&CallArguments::positional([operand_type]),