[ty] Improve diagnostics when `NotImplemented` is called (#21523)

## Summary

Fixes https://github.com/astral-sh/ty/issues/1571.

I realised I was overcomplicating things when I described what we should
do in that issue description. The simplest thing to do here is just to
special-case call expressions and short-circuit the call-binding
machinery entirely if we see it's `NotImplemented` being called. It
doesn't really matter if the subdiagnostic doesn't fire when a union is
called and one element of the union is `NotImplemented` -- the
subdiagnostic doesn't need to be exhaustive; it's just to help people in
some common cases.

## Test Plan

Added snapshots
This commit is contained in:
Alex Waygood 2025-11-19 19:27:12 +00:00 committed by GitHub
parent ce06094ada
commit a8f7ccf2ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 1 deletions

View File

@ -200,6 +200,9 @@ isinstance("", t.Any) # error: [invalid-argument-type]
## The builtin `NotImplemented` constant is not callable
<!-- snapshot-diagnostics -->
```py
NotImplemented() # error: [call-non-callable]
raise NotImplemented() # error: [call-non-callable]
raise NotImplemented("this module is not implemented yet!!!") # error: [call-non-callable]
```

View File

@ -0,0 +1,47 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: builtins.md - Calling builtins - The builtin `NotImplemented` constant is not callable
mdtest path: crates/ty_python_semantic/resources/mdtest/call/builtins.md
---
# Python source files
## mdtest_snippet.py
```
1 | raise NotImplemented() # error: [call-non-callable]
2 | raise NotImplemented("this module is not implemented yet!!!") # error: [call-non-callable]
```
# Diagnostics
```
error[call-non-callable]: `NotImplemented` is not callable
--> src/mdtest_snippet.py:1:7
|
1 | raise NotImplemented() # error: [call-non-callable]
| --------------^^
| |
| Did you mean `NotImplementedError`?
2 | raise NotImplemented("this module is not implemented yet!!!") # error: [call-non-callable]
|
info: rule `call-non-callable` is enabled by default
```
```
error[call-non-callable]: `NotImplemented` is not callable
--> src/mdtest_snippet.py:2:7
|
1 | raise NotImplemented() # error: [call-non-callable]
2 | raise NotImplemented("this module is not implemented yet!!!") # error: [call-non-callable]
| --------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| Did you mean `NotImplementedError`?
|
info: rule `call-non-callable` is enabled by default
```

View File

@ -7912,6 +7912,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ty
});
if callable_type.is_notimplemented(self.db()) {
if let Some(builder) = self
.context
.report_lint(&CALL_NON_CALLABLE, call_expression)
{
let mut diagnostic = builder.into_diagnostic("`NotImplemented` is not callable");
diagnostic.annotate(
self.context
.secondary(&**func)
.message("Did you mean `NotImplementedError`?"),
);
diagnostic.set_concise_message(
"`NotImplemented` is not callable - did you mean `NotImplementedError`?",
);
}
return Type::unknown();
}
// Special handling for `TypedDict` method calls
if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() {
let value_type = self.expression_type(value);