Skip to content

Commit 065cb83

Browse files
committed
add staticmethod
1 parent c392444 commit 065cb83

5 files changed

Lines changed: 56 additions & 5 deletions

File tree

crates/ty_python_semantic/resources/mdtest/call/methods.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,33 @@ The `owner` argument takes precedence over the `instance` argument:
397397
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method <class 'C'>.f() -> Unknown
398398
```
399399

400+
### Accessing the staticmethod as a static member
401+
402+
```py
403+
from inspect import getattr_static
404+
405+
class C:
406+
@staticmethod
407+
def f(): ...
408+
```
409+
410+
Accessing the staticmethod as a static member. This will reveal the raw function, as staticmethod is
411+
transparent when accessed via `getattr_static`.
412+
413+
```py
414+
reveal_type(getattr_static(C, "f")) # revealed: def f() -> Unknown
415+
```
416+
417+
The `__get__` of a `staticmethod` object simply returns the underlying function. It ignores both the
418+
instance and owner arguments.
419+
420+
```py
421+
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: def f() -> Unknown
422+
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: def f() -> Unknown
423+
reveal_type(getattr_static(C, "f").__get__(C())) # revealed: def f() -> Unknown
424+
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: def f() -> Unknown
425+
```
426+
400427
### Classmethods mixed with other decorators
401428

402429
```toml

crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,19 @@ reveal_type(C.get_name()) # revealed: str
549549
reveal_type(C("42").get_name()) # revealed: str
550550
```
551551

552+
### Built-in `staticmethod` descriptor
553+
554+
```py
555+
class C:
556+
@staticmethod
557+
def helper(value: str) -> str:
558+
return value
559+
560+
reveal_type(C.helper("42")) # revealed: str
561+
c = C()
562+
reveal_type(c.helper("string")) # revealed: str
563+
```
564+
552565
### Functions as descriptors
553566

554567
Functions are descriptors because they implement a `__get__` method. This is crucial in making sure

crates/ty_python_semantic/resources/mdtest/overloads.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,30 +449,32 @@ from __future__ import annotations
449449
from typing import overload
450450

451451
class CheckStaticMethod:
452-
# TODO: error because `@staticmethod` does not exist on all overloads
453452
@overload
454453
def method1(x: int) -> int: ...
455454
@overload
456455
def method1(x: str) -> str: ...
457456
@staticmethod
457+
# error: [invalid-overload] "Overloaded function `method1` does not use the `@staticmethod` decorator consistently"
458458
def method1(x: int | str) -> int | str:
459459
return x
460-
# TODO: error because `@staticmethod` does not exist on all overloads
460+
461461
@overload
462462
def method2(x: int) -> int: ...
463463
@overload
464464
@staticmethod
465465
def method2(x: str) -> str: ...
466466
@staticmethod
467+
# error: [invalid-overload]
467468
def method2(x: int | str) -> int | str:
468469
return x
469-
# TODO: error because `@staticmethod` does not exist on the implementation
470+
470471
@overload
471472
@staticmethod
472473
def method3(x: int) -> int: ...
473474
@overload
474475
@staticmethod
475476
def method3(x: str) -> str: ...
477+
# error: [invalid-overload]
476478
def method3(x: int | str) -> int | str:
477479
return x
478480

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ impl<'db> Bindings<'db> {
279279
}
280280
_ => {}
281281
}
282+
} else if function.has_known_decorator(db, FunctionDecorators::STATICMETHOD)
283+
{
284+
overload.set_return_type(Type::FunctionLiteral(function));
282285
} else if let [Some(first), _] = overload.parameter_types() {
283286
if first.is_none(db) {
284287
overload.set_return_type(Type::FunctionLiteral(function));
@@ -314,6 +317,10 @@ impl<'db> Bindings<'db> {
314317

315318
_ => {}
316319
}
320+
} else if function
321+
.has_known_decorator(db, FunctionDecorators::STATICMETHOD)
322+
{
323+
overload.set_return_type(*function_ty);
317324
} else {
318325
match overload.parameter_types() {
319326
[_, Some(instance), _] if instance.is_none(db) => {

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,8 +1223,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
12231223
}
12241224
}
12251225

1226-
// TODO: Add `@staticmethod`
1227-
for (decorator, name) in [(FunctionDecorators::CLASSMETHOD, "classmethod")] {
1226+
for (decorator, name) in [
1227+
(FunctionDecorators::CLASSMETHOD, "classmethod"),
1228+
(FunctionDecorators::STATICMETHOD, "staticmethod"),
1229+
] {
12281230
let mut decorator_present = false;
12291231
let mut decorator_missing = vec![];
12301232

0 commit comments

Comments
 (0)