[red-knot] Special case @abstractmethod for function type (#17591)

## Summary

This is required because otherwise the inferred type is not going to be
`Type::FunctionLiteral` but a todo type because we don't recognize
`TypeVar` yet:

```py
_FuncT = TypeVar("_FuncT", bound=Callable[..., Any])

def abstractmethod(funcobj: _FuncT) -> _FuncT: ...
```

This is mainly required to raise diagnostic when only some (and not all)
`@overload`-ed functions are decorated with `@abstractmethod`.
This commit is contained in:
Dhruv Manilawala 2025-04-24 03:54:52 +05:30 committed by GitHub
parent e897f37911
commit 1796ca97d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 18 deletions

View file

@ -5876,10 +5876,12 @@ bitflags! {
pub struct FunctionDecorators: u8 {
/// `@classmethod`
const CLASSMETHOD = 1 << 0;
/// `@no_type_check`
/// `@typing.no_type_check`
const NO_TYPE_CHECK = 1 << 1;
/// `@overload`
/// `@typing.overload`
const OVERLOAD = 1 << 2;
/// `@abc.abstractmethod`
const ABSTRACT_METHOD = 1 << 3;
}
}

View file

@ -585,6 +585,14 @@ impl<'db> Bindings<'db> {
}
}
Some(KnownFunction::AbstractMethod) => {
// TODO: This can be removed once we understand legacy generics because the
// typeshed definition for `abc.abstractmethod` is an identity function.
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(*ty);
}
}
Some(KnownFunction::GetattrStatic) => {
let [Some(instance_ty), Some(attr_name), default] =
overload.parameter_types()

View file

@ -1482,24 +1482,37 @@ impl<'db> TypeInferenceBuilder<'db> {
for decorator in decorator_list {
let decorator_ty = self.infer_decorator(decorator);
if let Type::FunctionLiteral(function) = decorator_ty {
if function.is_known(self.db(), KnownFunction::NoTypeCheck) {
// If the function is decorated with the `no_type_check` decorator,
// we need to suppress any errors that come after the decorators.
self.context.set_in_no_type_check(InNoTypeCheck::Yes);
function_decorators |= FunctionDecorators::NO_TYPE_CHECK;
continue;
} else if function.is_known(self.db(), KnownFunction::Overload) {
function_decorators |= FunctionDecorators::OVERLOAD;
continue;
match decorator_ty {
Type::FunctionLiteral(function) => {
match function.known(self.db()) {
Some(KnownFunction::NoTypeCheck) => {
// If the function is decorated with the `no_type_check` decorator,
// we need to suppress any errors that come after the decorators.
self.context.set_in_no_type_check(InNoTypeCheck::Yes);
function_decorators |= FunctionDecorators::NO_TYPE_CHECK;
continue;
}
Some(KnownFunction::Overload) => {
function_decorators |= FunctionDecorators::OVERLOAD;
continue;
}
Some(KnownFunction::AbstractMethod) => {
function_decorators |= FunctionDecorators::ABSTRACT_METHOD;
continue;
}
_ => {}
}
}
} else if let Type::ClassLiteral(class) = decorator_ty {
if class.is_known(self.db(), KnownClass::Classmethod) {
function_decorators |= FunctionDecorators::CLASSMETHOD;
continue;
Type::ClassLiteral(class) => {
if class.is_known(self.db(), KnownClass::Classmethod) {
function_decorators |= FunctionDecorators::CLASSMETHOD;
continue;
}
}
} else if let Type::DataclassTransformer(params) = decorator_ty {
dataclass_transformer_params = Some(params);
Type::DataclassTransformer(params) => {
dataclass_transformer_params = Some(params);
}
_ => {}
}
decorator_types_and_nodes.push((decorator_ty, decorator));