Compare commits

...

2 Commits

Author SHA1 Message Date
Micha Reiser
f898182d33 [red-knot] Reduce the CallOutcome variants by removing RevealedType, AssertType and StaticAssert 2025-02-13 11:23:24 +01:00
Micha Reiser
4d530a89bc Make CallBinding::callable_ty required 2025-02-13 10:31:51 +01:00
4 changed files with 108 additions and 196 deletions

View File

@@ -278,10 +278,10 @@ proper diagnostics in case of missing or superfluous arguments.
from typing_extensions import reveal_type
# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
reveal_type() # revealed: Unknown
reveal_type()
# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
reveal_type(1, 2) # revealed: Literal[1]
reveal_type(1, 2)
```
### `static_assert`
@@ -290,7 +290,6 @@ reveal_type(1, 2) # revealed: Literal[1]
from knot_extensions import static_assert
# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
# error: [static-assert-error]
static_assert()
# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"

View File

@@ -35,9 +35,7 @@ use crate::semantic_index::{
use crate::stdlib::{builtins_symbol, known_module_symbol, typing_extensions_symbol};
use crate::suppression::check_suppressions;
use crate::symbol::{Boundness, Symbol};
use crate::types::call::{
bind_call, CallArguments, CallBinding, CallDunderResult, CallOutcome, StaticAssertionErrorKind,
};
use crate::types::call::{bind_call, CallArguments, CallBinding, CallDunderResult, CallOutcome};
use crate::types::class_base::ClassBase;
use crate::types::diagnostic::INVALID_TYPE_FORM;
use crate::types::infer::infer_unpack_types;
@@ -1942,42 +1940,8 @@ impl<'db> Type<'db> {
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));
let mut binding = bind_call(db, arguments, function_type.signature(db), 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 +2016,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.
@@ -4074,10 +4030,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,6 +1,6 @@
use super::context::InferContext;
use super::diagnostic::{CALL_NON_CALLABLE, TYPE_ASSERTION_FAILURE};
use super::{Severity, Signature, Type, TypeArrayDisplay, UnionBuilder};
use super::{KnownFunction, Severity, Signature, Type, TypeArrayDisplay, UnionBuilder};
use crate::types::diagnostic::STATIC_ASSERT_ERROR;
use crate::Db;
use ruff_db::diagnostic::DiagnosticId;
@@ -12,23 +12,11 @@ mod bind;
pub(super) use arguments::{Argument, CallArguments};
pub(super) use bind::{bind_call, CallBinding};
#[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> {
Callable {
binding: CallBinding<'db>,
},
RevealType {
binding: CallBinding<'db>,
revealed_ty: Type<'db>,
},
NotCallable {
not_callable_ty: Type<'db>,
},
@@ -40,14 +28,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 +41,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>,
@@ -81,21 +53,10 @@ 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,
}
}
/// 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 +75,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()),
}
}
@@ -205,19 +161,94 @@ impl<'db> CallOutcome<'db> {
match self {
Self::Callable { binding } => {
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())),
);
if !binding.has_binding_errors() {
if let Type::FunctionLiteral(function_type) = binding.callable_type() {
if let Some(known_function) = function_type.known(context.db()) {
// TODO: Should we skip those diagnostics if there's any binding error?
match known_function {
KnownFunction::RevealType => {
if let Some(revealed_type) = binding.one_parameter_type() {
context.report_diagnostic(
node,
DiagnosticId::RevealedType,
Severity::Info,
format_args!(
"Revealed type is `{}`",
revealed_type.display(context.db())
),
);
}
}
KnownFunction::AssertType => {
if let [actual_ty, asserted_ty] = binding.parameter_types() {
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()),
),
);
}
}
}
KnownFunction::StaticAssert => {
if let Some((parameter_ty, message)) =
binding.two_parameter_types()
{
let truthiness = parameter_ty.bool(context.db());
if !truthiness.is_always_true() {
if let Some(message) = message
.into_string_literal()
.map(|s| &**s.value(context.db()))
{
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!(
"Static assertion error: {message}"
),
);
} else if parameter_ty == Type::BooleanLiteral(false) {
context.report_lint(
&STATIC_ASSERT_ERROR,
node,
format_args!("Static assertion error: argument evaluates to `False`"),
);
} else if truthiness.is_always_false() {
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())
),
);
} else {
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())
),
);
};
}
}
}
_ => {}
}
}
}
}
Ok(binding.return_type())
}
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
@@ -239,24 +270,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,73 +299,6 @@ 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())
}
}
}
}

View File

@@ -5,7 +5,7 @@ use crate::types::diagnostic::{
TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT,
};
use crate::types::signatures::Parameter;
use crate::types::UnionType;
use crate::types::{todo_type, UnionType};
use ruff_python_ast as ast;
/// Bind a [`CallArguments`] against a callable [`Signature`].
@@ -16,7 +16,7 @@ pub(crate) fn bind_call<'db>(
db: &'db dyn Db,
arguments: &CallArguments<'_, 'db>,
signature: &Signature<'db>,
callable_ty: Option<Type<'db>>,
callable_ty: Type<'db>,
) -> CallBinding<'db> {
let parameters = signature.parameters();
// The type assigned to each parameter at this call site.
@@ -138,7 +138,7 @@ pub(crate) fn bind_call<'db>(
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CallBinding<'db> {
/// Type of the callable object (function, class...)
callable_ty: Option<Type<'db>>,
callable_ty: Type<'db>,
/// Return type of the call.
return_ty: Type<'db>,
@@ -154,13 +154,17 @@ impl<'db> CallBinding<'db> {
// TODO remove this constructor and construct always from `bind_call`
pub(crate) fn from_return_type(return_ty: Type<'db>) -> Self {
Self {
callable_ty: None,
callable_ty: todo_type!("CallBinding::from_return_type"),
return_ty,
parameter_tys: Box::default(),
errors: vec![],
}
}
pub(super) fn callable_type(&self) -> Type<'db> {
self.callable_ty
}
pub(crate) fn set_return_type(&mut self, return_ty: Type<'db>) {
self.return_ty = return_ty;
}
@@ -189,8 +193,8 @@ impl<'db> CallBinding<'db> {
fn callable_name(&self, db: &'db dyn Db) -> Option<&str> {
match self.callable_ty {
Some(Type::FunctionLiteral(function)) => Some(function.name(db)),
Some(Type::ClassLiteral(class_type)) => Some(class_type.class.name(db)),
Type::FunctionLiteral(function) => Some(function.name(db)),
Type::ClassLiteral(class_type) => Some(class_type.class.name(db)),
_ => None,
}
}
@@ -201,6 +205,10 @@ impl<'db> CallBinding<'db> {
error.report_diagnostic(context, node, callable_name);
}
}
pub(super) fn has_binding_errors(&self) -> bool {
!self.errors.is_empty()
}
}
/// Information needed to emit a diagnostic regarding a parameter.