Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/overloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,18 @@ Using the `@abstractmethod` decorator requires that the class's metaclass is `AB
from it.

```py
from abc import ABCMeta

class CustomAbstractMetaclass(ABCMeta): ...

class Fine(metaclass=CustomAbstractMetaclass):
@overload
@abstractmethod
def f(self, x: int) -> int: ...
@overload
@abstractmethod
def f(self, x: str) -> str: ...

class Foo:
@overload
@abstractmethod
Expand Down Expand Up @@ -448,6 +460,52 @@ class PartialFoo(ABC):
def f(self, x: str) -> str: ...
```

#### `TYPE_CHECKING` blocks

As in other areas of ty, we treat `TYPE_CHECKING` blocks the same as "inline stub files", so we
permit overloaded functions to exist without an implementation if all overloads are defined inside
an `if TYPE_CHECKING` block:

```py
from typing import overload, TYPE_CHECKING

if TYPE_CHECKING:
@overload
def a() -> str: ...
@overload
def a(x: int) -> int: ...

class F:
@overload
def method(self) -> None: ...
@overload
def method(self, x: int) -> int: ...

class G:
if TYPE_CHECKING:
@overload
def method(self) -> None: ...
@overload
def method(self, x: int) -> int: ...

if TYPE_CHECKING:
@overload
def b() -> str: ...

if TYPE_CHECKING:
@overload
def b(x: int) -> int: ...

if TYPE_CHECKING:
@overload
def c() -> None: ...

# not all overloads are in a `TYPE_CHECKING` block, so this is an error
@overload
# error: [invalid-overload]
def c(x: int) -> int: ...
```

### `@overload`-decorated functions with non-stub bodies

<!-- snapshot-diagnostics -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ error[invalid-overload]: Overloads for function `func` must be followed by a non
9 | class Foo:
|
info: Attempting to call `func` will raise `TypeError` at runtime
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
info: Overloaded functions without implementations are only permitted:
info: - in stub files
info: - in `if TYPE_CHECKING` blocks
info: - as methods on protocol classes
info: - or as `@abstractmethod`-decorated methods on abstract classes
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
info: rule `invalid-overload` is enabled by default

Expand All @@ -58,7 +62,11 @@ error[invalid-overload]: Overloads for function `method` must be followed by a n
| ^^^^^^
|
info: Attempting to call `method` will raise `TypeError` at runtime
info: Overloaded functions without implementations are only permitted in stub files, on protocols, or for abstract methods
info: Overloaded functions without implementations are only permitted:
info: - in stub files
info: - in `if TYPE_CHECKING` blocks
info: - as methods on protocol classes
info: - or as `@abstractmethod`-decorated methods on abstract classes
info: See https://docs.python.org/3/library/typing.html#typing.overload for more details
info: rule `invalid-overload` is enabled by default

Expand Down
7 changes: 0 additions & 7 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1815,13 +1815,6 @@ impl<'db> ClassLiteral<'db> {
})
}

/// Determine if this is an abstract class.
pub(super) fn is_abstract(self, db: &'db dyn Db) -> bool {
self.metaclass(db)
.as_class_literal()
.is_some_and(|metaclass| metaclass.is_known(db, KnownClass::ABCMeta))
}

/// Return the types of the decorators on this class
#[salsa::tracked(returns(deref), cycle_initial=decorators_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
fn decorators(self, db: &'db dyn Db) -> Box<[Type<'db>]> {
Expand Down
23 changes: 19 additions & 4 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if implementation.is_none() && !self.in_stub() {
let mut implementation_required = true;

if let NodeWithScopeKind::Class(class_node_ref) = scope {
if function
.iter_overloads_and_implementation(self.db())
.all(|f| {
f.body_scope(self.db())
.scope(self.db())
.in_type_checking_block()
})
{
implementation_required = false;
} else if let NodeWithScopeKind::Class(class_node_ref) = scope {
let class = binding_type(
self.db(),
self.index
Expand All @@ -1113,7 +1122,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.expect_class_literal();

if class.is_protocol(self.db())
|| (class.is_abstract(self.db())
|| (Type::ClassLiteral(class)
.is_subtype_of(self.db(), KnownClass::ABCMeta.to_instance(self.db()))
&& overloads.iter().all(|overload| {
overload.has_known_decorator(
self.db(),
Expand All @@ -1140,8 +1150,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
&function_node.name
));
diagnostic.info(
"Overloaded functions without implementations are only permitted \
in stub files, on protocols, or for abstract methods",
"Overloaded functions without implementations are only permitted:",
);
diagnostic.info(" - in stub files");
diagnostic.info(" - in `if TYPE_CHECKING` blocks");
diagnostic.info(" - as methods on protocol classes");
diagnostic.info(
" - or as `@abstractmethod`-decorated methods on abstract classes",
);
diagnostic.info(
"See https://docs.python.org/3/library/typing.html#typing.overload \
Expand Down
Loading