PY-84464 Support @property decorator when matching a protocol and its implementation

(cherry picked from commit fcbeeb57323336c7d921edad373ea595ef687d6b)

IJ-MR-174248

GitOrigin-RevId: 6c44a1a7af026faaf4a5e9c1242ed29f3fb94e0e
This commit is contained in:
evgeny.bovykin
2025-11-19 13:05:44 +00:00
committed by intellij-monorepo-bot
parent 59d7739bd6
commit 1331c92868
17 changed files with 629 additions and 198 deletions
@@ -112,7 +112,7 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
public void testFunctionReturnTypePy3() {
doTest();
}
public void testFunctionYieldTypePy3() {
doTest();
}
@@ -2658,7 +2658,7 @@ def foo(param: str | int) -> TypeGuard[str]:
compatible: MyCallable[[int], object] = MyCallable[[object], str]()
incompatible1: MyCallable[[object], object] = <warning descr="Expected type 'MyCallable[[object], object]', got 'MyCallable[[int], str]' instead">MyCallable[[int], str]()</warning>
incompatible2: MyCallable[[int], str] = <warning descr="Expected type 'MyCallable[[int], str]', got 'MyCallable[[object], object]' instead">MyCallable[[object], object]()</warning>
""");
""");
}
// PY-77541
@@ -3125,4 +3125,189 @@ def foo(param: str | int) -> TypeGuard[str]:
var: Template = Concrete("value", 42)
""");
}
// PY-76822
public void testProtocolWithPropertyAndConcreteWithAttribute() {
doTestByText("""
from typing import Protocol
class Template(Protocol):
@property
def val1(self) -> int:
...
class Concrete:
val1: int = 0
var: Template = Concrete()
""");
}
// PY-76822
public void testProtocolWithPropertyAndConcreteWithProperty() {
doTestByText("""
from typing import Protocol
class Template(Protocol):
@property
def val1(self) -> int:
...
class Concrete:
@property
def val1(self) -> int:
...
var: Template = Concrete()
""");
}
// PY-76822
public void testProtocolWithPropertySetterAndConcreteWithPropertyDeleter() {
doTestByText("""
from typing import Protocol
class Template(Protocol):
@property
def val1(self) -> int:
...
@val1.setter
def val1(self, val: int) -> None:
...
class Concrete:
@property
def val1(self) -> int:
...
@val1.deleter
def val1(self, val: int) -> None:
...
var: Template = <warning descr="Expected type 'Template', got 'Concrete' instead">Concrete()</warning>
""");
}
// PY-76822
public void testProtocolWithPropertySetterAndFrozenDataclass() {
doTestByText("""
from typing import Protocol
from dataclasses import dataclass
class Template(Protocol):
@property
def val(self) -> int:
...
@val.setter
def val(self, val: int) -> None:
...
@dataclass(frozen=True)
class Concrete:
val: int = 0
var: Template = <warning descr="Expected type 'Template', got 'Concrete' instead">Concrete()</warning>
""");
}
// PY-76822
public void testProtocolWithPropertyDeleterAndFrozenDataclass() {
doTestByText("""
from typing import Protocol
from dataclasses import dataclass
class Template(Protocol):
@property
def val(self) -> int:
...
@val.deleter
def val(self, val: int) -> None:
...
@dataclass(frozen=True)
class Concrete:
val: int = 0
var: Template = <warning descr="Expected type 'Template', got 'Concrete' instead">Concrete()</warning>
""");
}
// PY-76822
public void testOverloadedMethodInConcreteClass() {
doTestByText("""
from typing import Protocol, ClassVar, overload
class Template(Protocol):
def f(self, x: int) -> int: ...
class Concrete:
@overload
def f(self, x: str) -> int: ...
@overload
def f(self, x: int) -> int: ...
def f(self, x) -> int:
return 1
var: Template = Concrete()
""");
}
// PY-76822
public void testExplicitAnyInConcreteType() {
doTestByText("""
from typing import Protocol, Any
class Template(Protocol):
val: int
class Concrete:
val: Any
var: Template = Concrete()
""");
}
// PY-76822
public void testExplicitAnyInProtocol() {
doTestByText("""
from typing import Protocol, Any
class Template(Protocol):
val: Any
class Concrete:
val: int
var: Template = Concrete()
""");
}
// PY-76822
public void testExplicitAnyInBothProtocolAndConcreteType() {
doTestByText("""
from typing import Protocol, Any
class Template(Protocol):
val: Any
class Concrete:
val: Any
var: Template = Concrete()
""");
}
}
@@ -37,6 +37,82 @@ public class PyProtocolInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-76822
public void testProtocolWithPropertyAndConcreteWithAttribute() {
doTestByText("""
from typing import Protocol
class Template(Protocol):
@property
def val1(self) -> int:
...
class Concrete(Template):
val1: int = 0
""");
}
// PY-76822
public void testProtocolWithPropertyAndConcreteWithProperty() {
doTestByText("""
from typing import Protocol
class Template(Protocol):
@property
def val1(self) -> int:
...
class Concrete(Template):
@property
def val1(self) -> int:
...
""");
}
// PY-76822
public void testProtocolWithMutablePropertyAndClassAttribute() {
doTestByText("""
from typing import Protocol, Sequence
class Template(Protocol):
@property
def val(self) -> Sequence[float]:
...
@val.setter
def val(self, val: Sequence[float]) -> None:
...
class Concrete(Template):
val: Sequence[float] = [0]
""");
}
// PY-76822
public void testProtocolAndFrozenDataclass() {
doTestByText("""
from typing import Protocol
from dataclasses import dataclass
class Template(Protocol):
@property
def val(self) -> int:
...
@val.setter
def val(self, val: int) -> None:
...
@dataclass(frozen=True)
class Concrete(Template):
<warning descr="'val' is writable in protocol 'Template'">val</warning>: int = 0
""");
}
// PY-61857
public void testClassWithTypeParameterListNotReported() {
runWithLanguageLevel(LanguageLevel.PYTHON312, () -> {