Repository: python/typing (language: python)

Issue:
Conformance tests: PEP 702
PEP 702's `@deprecated` has been [a part of the specs](https://typing.readthedocs.io/en/latest/spec/directives.html#deprecated) for a while now, yet there aren't any conformance tests for it. Is it simply because no one have volunteered to do so (in which case I'll be interested in submitting a PR), or are such tests not stored in this repository?



Discussion / hints:
Good catch. I didn't realize these were missing from the conformance suite.

The [@deprecated](https://typing.readthedocs.io/en/latest/spec/directives.html#deprecated) directive doesn't provide core type checking functionality. It's more of a linter or language server feature, but it is still part of the typing spec, so it makes sense to have tests for it.

AFAIK, pyright is currently the only one of the four major type checkers that implements PEP 702.

You're welcome to submit a PR for this, or I'm happy to do so. If you do it, please follow the guidance for conformance tests and use the existing tests as a template. The tests should stick to only the parts that are found in the typing spec, and comments in the test should quote the spec as appropriate.

Fix patch:
diff --git a/conformance/results/mypy/directives_deprecated.toml b/conformance/results/mypy/directives_deprecated.toml
new file mode 100644
index 00000000..9c7552b5
--- /dev/null
+++ b/conformance/results/mypy/directives_deprecated.toml
@@ -0,0 +1,20 @@
+conformant = "Unsupported"
+notes = """
+Does not support @deprecated.
+"""
+conformance_automated = "Fail"
+errors_diff = """
+Line 15: Expected 1 errors
+Line 23: Expected 1 errors
+Line 24: Expected 1 errors
+Line 29: Expected 1 errors
+Line 40: Expected 1 errors
+Line 41: Expected 1 errors
+Line 43: Expected 1 errors
+Line 46: Expected 1 errors
+Line 47: Expected 1 errors
+Line 57: Expected 1 errors
+Line 87: Expected 1 errors
+"""
+output = """
+"""
diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml
index 8f919ed4..b79e0a27 100644
--- a/conformance/results/mypy/version.toml
+++ b/conformance/results/mypy/version.toml
@@ -1,2 +1,2 @@
 version = "mypy 1.11.0"
-test_duration = 1.0
+test_duration = 1.4
diff --git a/conformance/results/pyre/directives_deprecated.toml b/conformance/results/pyre/directives_deprecated.toml
new file mode 100644
index 00000000..9c7552b5
--- /dev/null
+++ b/conformance/results/pyre/directives_deprecated.toml
@@ -0,0 +1,20 @@
+conformant = "Unsupported"
+notes = """
+Does not support @deprecated.
+"""
+conformance_automated = "Fail"
+errors_diff = """
+Line 15: Expected 1 errors
+Line 23: Expected 1 errors
+Line 24: Expected 1 errors
+Line 29: Expected 1 errors
+Line 40: Expected 1 errors
+Line 41: Expected 1 errors
+Line 43: Expected 1 errors
+Line 46: Expected 1 errors
+Line 47: Expected 1 errors
+Line 57: Expected 1 errors
+Line 87: Expected 1 errors
+"""
+output = """
+"""
diff --git a/conformance/results/pyre/directives_reveal_type.toml b/conformance/results/pyre/directives_reveal_type.toml
index 92a17994..ed30bc74 100644
--- a/conformance/results/pyre/directives_reveal_type.toml
+++ b/conformance/results/pyre/directives_reveal_type.toml
@@ -1,5 +1,5 @@
 conformant = "Partial"
-notes = """"
+notes = """
 Produces errors rather than warnings on `reveal_type`.
 """
 output = """
diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml
index 31ee493d..dc6693ab 100644
--- a/conformance/results/pyre/version.toml
+++ b/conformance/results/pyre/version.toml
@@ -1,2 +1,2 @@
 version = "pyre 0.9.22"
-test_duration = 2.5
+test_duration = 1.8
diff --git a/conformance/results/pyright/directives_deprecated.toml b/conformance/results/pyright/directives_deprecated.toml
new file mode 100644
index 00000000..20cf127a
--- /dev/null
+++ b/conformance/results/pyright/directives_deprecated.toml
@@ -0,0 +1,31 @@
+conformant = "Partial"
+notes = """
+Does not report error for deprecated magic methods.
+"""
+conformance_automated = "Fail"
+errors_diff = """
+Line 40: Expected 1 errors
+Line 41: Expected 1 errors
+Line 47: Expected 1 errors
+Line 33: Unexpected errors ['directives_deprecated.py:33:7 - error: The class "Ham" is deprecated']
+"""
+output = """
+directives_deprecated.py:15:44 - error: The class "Ham" is deprecated
+  Use Spam instead (reportDeprecated)
+directives_deprecated.py:23:9 - error: The function "norwegian_blue" is deprecated
+  It is pining for the fiords (reportDeprecated)
+directives_deprecated.py:24:13 - error: The function "norwegian_blue" is deprecated
+  It is pining for the fiords (reportDeprecated)
+directives_deprecated.py:29:9 - error: The function "foo" is deprecated
+  Only str will be allowed (reportDeprecated)
+directives_deprecated.py:33:7 - error: The class "Ham" is deprecated
+  Use Spam instead (reportDeprecated)
+directives_deprecated.py:43:6 - error: The getter for property "greasy" is deprecated
+  All spam will be equally greasy (reportDeprecated)
+directives_deprecated.py:46:6 - error: The setter for property "shape" is deprecated
+  Shapes are becoming immutable (reportDeprecated)
+directives_deprecated.py:57:9 - error: The function "lorem" is deprecated
+  Deprecated (reportDeprecated)
+directives_deprecated.py:87:13 - error: The method "foo" in class "Fooable" is deprecated
+  Deprecated (reportDeprecated)
+"""
diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml
index 3229acb5..4d7fa4a5 100644
--- a/conformance/results/pyright/version.toml
+++ b/conformance/results/pyright/version.toml
@@ -1,2 +1,2 @@
 version = "pyright 1.1.373"
-test_duration = 1.4
+test_duration = 1.2
diff --git a/conformance/results/pytype/directives_deprecated.toml b/conformance/results/pytype/directives_deprecated.toml
new file mode 100644
index 00000000..9fb6a4df
--- /dev/null
+++ b/conformance/results/pytype/directives_deprecated.toml
@@ -0,0 +1,26 @@
+conformant = "Unsupported"
+notes = """
+Does not support @deprecated.
+"""
+conformance_automated = "Fail"
+errors_diff = """
+Line 23: Expected 1 errors
+Line 24: Expected 1 errors
+Line 29: Expected 1 errors
+Line 40: Expected 1 errors
+Line 41: Expected 1 errors
+Line 43: Expected 1 errors
+Line 46: Expected 1 errors
+Line 47: Expected 1 errors
+Line 57: Expected 1 errors
+Line 87: Expected 1 errors
+Line 16: Unexpected errors ['File "directives_deprecated.py", line 16, in <module>: Can\\'t find module \\'_directives_deprecated_library\\'. [import-error]']
+Line 18: Unexpected errors ['File "directives_deprecated.py", line 18, in <module>: typing_extensions.deprecated not supported yet [not-supported-yet]']
+Line 66: Unexpected errors ['File "directives_deprecated.py", line 66, in <module>: typing.override not supported yet [not-supported-yet]']
+"""
+output = """
+File "directives_deprecated.py", line 15, in <module>: Can't find module '_directives_deprecated_library'. [import-error]
+File "directives_deprecated.py", line 16, in <module>: Can't find module '_directives_deprecated_library'. [import-error]
+File "directives_deprecated.py", line 18, in <module>: typing_extensions.deprecated not supported yet [not-supported-yet]
+File "directives_deprecated.py", line 66, in <module>: typing.override not supported yet [not-supported-yet]
+"""
diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml
index 16866283..cf447499 100644
--- a/conformance/results/pytype/version.toml
+++ b/conformance/results/pytype/version.toml
@@ -1,2 +1,2 @@
 version = "pytype 2024.04.11"
-test_duration = 32.7
+test_duration = 30.0
diff --git a/conformance/results/results.html b/conformance/results/results.html
index 04c1a6ab..fd3eec95 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -159,16 +159,16 @@ <h3>Python Type System Conformance Test Results</h3>
         <div class="table_container"><table><tbody>
 <tr><th class="col1">&nbsp;</th>
 <th class='tc-header'><div class='tc-name'>mypy 1.11.0</div>
-<div class='tc-time'>1.0sec</div>
+<div class='tc-time'>1.4sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pyright 1.1.373</div>
-<div class='tc-time'>1.4sec</div>
+<div class='tc-time'>1.2sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pyre 0.9.22</div>
-<div class='tc-time'>2.5sec</div>
+<div class='tc-time'>1.8sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pytype 2024.04.11</div>
-<div class='tc-time'>32.7sec</div>
+<div class='tc-time'>30.0sec</div>
 </th>
 </tr>
 <tr><th class="column" colspan="5">
@@ -972,6 +972,12 @@ <h3>Python Type System Conformance Test Results</h3>
 <th class="column col2 conformant">Pass</th>
 <th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not reject a call to "cast" with additional arguments.</p></span></div></th>
 </tr>
+<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;directives_deprecated</th>
+<th class="column col2 not-conformant"><div class="hover-text">Unsupported<span class="tooltip-text" id="bottom"><p>Does not support @deprecated.</p></span></div></th>
+<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not report error for deprecated magic methods.</p></span></div></th>
+<th class="column col2 not-conformant"><div class="hover-text">Unsupported<span class="tooltip-text" id="bottom"><p>Does not support @deprecated.</p></span></div></th>
+<th class="column col2 not-conformant"><div class="hover-text">Unsupported<span class="tooltip-text" id="bottom"><p>Does not support @deprecated.</p></span></div></th>
+</tr>
 <tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;directives_no_type_check</th>
 <th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@no_type_check` class decorator (allowed).</p><p>Does not reject invalid call of `@no_type_check` function.</p></span></div></th>
 <th class="column col2 conformant"><div class="hover-text">Pass*<span class="tooltip-text" id="bottom"><p>Does not honor `@no_type_check` class decorator (allowed).</p></span></div></th>


Test patch:
diff --git a/conformance/tests/_directives_deprecated_library.py b/conformance/tests/_directives_deprecated_library.py
new file mode 100644
index 00000000..efcc6dcf
--- /dev/null
+++ b/conformance/tests/_directives_deprecated_library.py
@@ -0,0 +1,44 @@
+"""
+Support module for directive_deprecated.
+"""
+
+from typing import Self, overload
+
+from typing_extensions import deprecated
+
+
+@deprecated("Use Spam instead")
+class Ham: ...
+
+
+@deprecated("It is pining for the fiords")
+def norwegian_blue(x: int) -> int: ...
+
+
+@overload
+@deprecated("Only str will be allowed")
+def foo(x: int) -> str: ...
+
+
+@overload
+def foo(x: str) -> str: ...
+
+
+def foo(x: int | str) -> str: ...
+
+
+class Spam:
+
+    @deprecated("There is enough spam in the world")
+    def __add__(self, other: object) -> Self: ...
+
+    @property
+    @deprecated("All spam will be equally greasy")
+    def greasy(self) -> float: ...
+
+    @property
+    def shape(self) -> str: ...
+
+    @shape.setter
+    @deprecated("Shapes are becoming immutable")
+    def shape(self, value: str) -> None: ...
diff --git a/conformance/tests/directives_deprecated.py b/conformance/tests/directives_deprecated.py
new file mode 100644
index 00000000..cbae6054
--- /dev/null
+++ b/conformance/tests/directives_deprecated.py
@@ -0,0 +1,116 @@
+"""
+Tests the warnings.deprecated function.
+"""
+
+# pyright: reportDeprecated=true
+
+# Specification: https://typing.readthedocs.io/en/latest/spec/directives.html#deprecated
+# See also https://peps.python.org/pep-0702/
+
+# > Type checkers should produce a diagnostic whenever they encounter a usage of an object
+# > marked as deprecated. [...] For deprecated classes and functions, this includes:
+
+# > * `from` imports
+
+from _directives_deprecated_library import Ham  # E: Use of deprecated class Ham
+import _directives_deprecated_library as library
+
+from typing_extensions import deprecated
+
+
+# > * References through module, class, or instance attributes
+
+library.norwegian_blue(1)  # E: Use of deprecated function norwegian_blue
+map(library.norwegian_blue, [1, 2, 3])  # E: Use of deprecated function norwegian_blue
+
+
+# > For deprecated overloads, this includes all calls that resolve to the deprecated overload.
+
+library.foo(1)  # E: Use of deprecated overload for foo
+library.foo("x")  # OK
+
+
+ham = Ham()  # OK (already reported above)
+
+
+# > * Any syntax that indirectly triggers a call to the function.
+
+spam = library.Spam()
+
+_ = spam + 1  # E: Use of deprecated method Spam.__add__
+spam += 1  # E: Use of deprecated method Spam.__add__
+
+spam.greasy  # E: Use of deprecated property Spam.greasy
+spam.shape  # OK
+
+spam.shape = "cube"  # E: Use of deprecated property setter Spam.shape
+spam.shape += "cube"  # E: Use of deprecated property setter Spam.shape
+
+
+class Invocable:
+
+    @deprecated("Deprecated")
+    def __call__(self) -> None: ...
+
+invocable = Invocable()
+invocable()  # E: Use of deprecated method __call__
+
+
+# > * Any usage of deprecated objects in their defining module
+
+@deprecated("Deprecated")
+def lorem() -> None: ...
+
+
+ipsum = lorem()  # E: Use of deprecated function lorem
+
+
+# > There are additional scenarios where deprecations could come into play.
+# > For example, an object may implement a `typing.Protocol`,
+# > but one of the methods required for protocol compliance is deprecated.
+# > As scenarios such as this one appear complex and relatively unlikely to come up in practice,
+# > this PEP does not mandate that type checkers detect them.
+
+from typing import Protocol, override
+
+
+class Fooable(Protocol):
+
+    @deprecated("Deprecated")
+    def foo(self) -> None: ...
+
+    def bar(self) -> None: ...
+
+
+class Fooer(Fooable):
+
+    @override
+    def foo(self) -> None:  # E?: Implementation of deprecated method foo
+        ...
+
+    def bar(self) -> None: ...
+
+
+def foo_it(fooable: Fooable) -> None:
+    fooable.foo()  # E: Use of deprecated method foo
+    fooable.bar()
+
+
+# https://github.com/python/typing/pull/1822#discussion_r1693991644
+
+class Fooable2(Protocol):
+
+    def foo(self) -> None: ...
+
+
+class Concrete:
+
+    @deprecated("Deprecated")
+    def foo(self) -> None: ...
+
+
+def take_fooable(f: Fooable2) -> None: ...
+
+
+def caller(c: Concrete) -> None:
+    take_fooable(c)  # E?: Concrete is a Fooable2, but only because of a deprecated method