Skip to content

Commit b5baab9

Browse files
Merge branch 'main' into more-hash-tests
2 parents a912053 + 40b929c commit b5baab9

File tree

9 files changed

+323
-77
lines changed

9 files changed

+323
-77
lines changed

.github/workflows/conformance.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Conformance
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
conformance:
12+
name: Run conformance suite
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v6
17+
18+
- name: Set up Python 3.12
19+
uses: actions/setup-python@v6
20+
with:
21+
python-version: "3.12"
22+
cache: "pip"
23+
24+
- name: Install uv
25+
run: |
26+
python -m pip install --upgrade pip
27+
python -m pip install uv
28+
29+
- name: Run conformance suite
30+
working-directory: conformance
31+
run: |
32+
uv sync --python 3.12 --frozen
33+
uv run --python 3.12 --frozen python src/main.py
34+
35+
- name: Assert conformance results are up to date
36+
run: |
37+
if [ -n "$(git status --porcelain -- conformance/results)" ]; then
38+
git status --short conformance/results
39+
git diff -- conformance/results
40+
echo "Conformance results are out of date. Run conformance/src/main.py and commit updated results."
41+
exit 1
42+
fi

conformance/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ by the scoring system.
7070

7171
To run the conformance test suite:
7272
* Clone the https://github.com/python/typing repo.
73-
* Create and activate a Python 3.12 virtual environment.
74-
* Switch to the `conformance` subdirectory and install all dependencies (`pip install -r requirements.txt`).
75-
* Switch to the `src` subdirectory and run `python main.py`.
73+
* Install [uv](https://docs.astral.sh/uv/) and ensure Python 3.12 is available.
74+
* Switch to the `conformance` subdirectory and install locked dependencies (`uv sync --python 3.12 --frozen`).
75+
* Run the conformance tool (`uv run --python 3.12 --frozen python src/main.py`).
7676

7777
Note that some type checkers may not run on some platforms. If a type checker fails to install, tests will be skipped for that type checker.
7878

@@ -92,7 +92,22 @@ If a test is updated (augmented or fixed), the process is similar to when adding
9292

9393
## Updating a Type Checker
9494

95-
If a new version of a type checker is released, re-run the test tool with the new version. If the type checker output has changed for any test cases, the tool will supply the old and new outputs. Examine these to determine whether the conformance status has changed. Once the conformance status has been updated, re-run the test tool again to regenerate the summary report.
95+
Type checker versions are locked in `uv.lock`.
96+
97+
To bump all supported checkers to their latest released versions:
98+
99+
```bash
100+
python scripts/bump_type_checkers.py
101+
```
102+
103+
After bumping, install the new lockfile and rerun conformance:
104+
105+
```bash
106+
uv sync --python 3.12 --frozen
107+
uv run --python 3.12 --frozen python src/main.py
108+
```
109+
110+
If checker output changes for any test cases, examine those deltas to determine whether the conformance status has changed. Once the conformance status has been updated, rerun the tool to regenerate the summary report.
96111

97112
## Automated Conformance Checking
98113

conformance/pyproject.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "typing-conformance"
3+
version = "0.1.0"
4+
requires-python = "==3.12.*"
5+
dependencies = [
6+
"mypy",
7+
"pyrefly",
8+
"pyright",
9+
"tomli",
10+
"tomlkit",
11+
"zuban",
12+
]
13+
14+
[tool.uv]
15+
package = false

conformance/requirements.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.

conformance/results/pyright/qualifiers_final_decorator.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
conformant = "Pass"
22
output = """
3-
qualifiers_final_decorator.py:8:6 - warning: Import "_qualifiers_final_decorator" could not be resolved from source (reportMissingModuleSource)
43
qualifiers_final_decorator.py:21:16 - error: Base class "Base1" is marked final and cannot be subclassed (reportGeneralTypeIssues)
54
qualifiers_final_decorator.py:56:9 - error: Method "method1" cannot override final method defined in class "Base2" (reportIncompatibleMethodOverride)
65
qualifiers_final_decorator.py:60:9 - error: Method "method2" cannot override final method defined in class "Base2" (reportIncompatibleMethodOverride)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Update the conformance checker versions in uv.lock to the latest releases.
4+
"""
5+
6+
from pathlib import Path
7+
from subprocess import CalledProcessError, run
8+
9+
10+
TYPE_CHECKERS = ("mypy", "pyright", "zuban", "pyrefly")
11+
12+
13+
def main() -> int:
14+
root_dir = Path(__file__).resolve().parents[1]
15+
lock_command = ["uv", "lock", "--python", "3.12"]
16+
for checker in TYPE_CHECKERS:
17+
lock_command.extend(["--upgrade-package", checker])
18+
19+
try:
20+
print("+", " ".join(lock_command))
21+
run(lock_command, cwd=root_dir, check=True)
22+
except CalledProcessError as exc:
23+
print(f"Failed with exit code {exc.returncode}")
24+
return exc.returncode
25+
return 0
26+
27+
28+
if __name__ == "__main__":
29+
raise SystemExit(main())

conformance/src/type_checker.py

Lines changed: 38 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from abc import ABC, abstractmethod
66
import json
77
from pathlib import Path
8-
import os
9-
import re
108
import shutil
119
from subprocess import PIPE, CalledProcessError, run
1210
import sys
@@ -25,8 +23,8 @@ def name(self) -> str:
2523
@abstractmethod
2624
def install(self) -> bool:
2725
"""
28-
Ensures that the latest version of the type checker is installed.
29-
Returns False if installation fails.
26+
Ensures that the type checker is available in the current environment.
27+
Returns False if it cannot be executed.
3028
"""
3129
raise NotImplementedError
3230

@@ -67,29 +65,24 @@ def install(self) -> bool:
6765
pass
6866

6967
try:
70-
# Uninstall any existing version if present.
71-
run(
72-
[sys.executable, "-m", "pip", "uninstall", "mypy", "-y"],
73-
check=True,
74-
)
75-
76-
# Install the latest version.
77-
run(
78-
[sys.executable, "-m", "pip", "install", "mypy"],
79-
check=True,
80-
)
81-
82-
# Run "mypy --version" to ensure that it's installed and to work
68+
# Run "mypy --version" to ensure that it's available and to work
8369
# around timing issues caused by malware scanners on some systems.
8470
self.get_version()
85-
8671
return True
87-
except CalledProcessError:
88-
print("Unable to install mypy")
72+
except (CalledProcessError, FileNotFoundError):
73+
print(
74+
"Unable to run mypy. Install conformance dependencies with "
75+
"'uv sync --frozen' from the conformance directory."
76+
)
8977
return False
9078

9179
def get_version(self) -> str:
92-
proc = run([sys.executable, "-m", "mypy", "--version"], stdout=PIPE, text=True)
80+
proc = run(
81+
[sys.executable, "-m", "mypy", "--version"],
82+
check=True,
83+
stdout=PIPE,
84+
text=True,
85+
)
9386
version = proc.stdout.strip()
9487

9588
# Remove the " (compiled)" if it's present.
@@ -137,29 +130,23 @@ def name(self) -> str:
137130

138131
def install(self) -> bool:
139132
try:
140-
# Uninstall any old version if present.
141-
run(
142-
[sys.executable, "-m", "pip", "uninstall", "pyright", "-y"],
143-
check=True,
144-
)
145-
146-
# Install the latest version.
147-
run(
148-
[sys.executable, "-m", "pip", "install", "pyright"],
149-
check=True,
150-
)
151-
152133
# Force the Python wrapper to install node if needed
153-
# and download the latest version of pyright.
134+
# and use the locked version of pyright.
154135
self.get_version()
155136
return True
156-
except CalledProcessError:
157-
print("Unable to install pyright")
137+
except (CalledProcessError, FileNotFoundError):
138+
print(
139+
"Unable to run pyright. Install conformance dependencies with "
140+
"'uv sync --frozen' from the conformance directory."
141+
)
158142
return False
159143

160144
def get_version(self) -> str:
161145
proc = run(
162-
[sys.executable, "-m", "pyright", "--version"], stdout=PIPE, text=True
146+
[sys.executable, "-m", "pyright", "--version"],
147+
check=True,
148+
stdout=PIPE,
149+
text=True,
163150
)
164151
return proc.stdout.strip()
165152

@@ -208,24 +195,17 @@ def name(self) -> str:
208195

209196
def install(self) -> bool:
210197
try:
211-
# Uninstall any existing version if present.
212-
run(
213-
[sys.executable, "-m", "pip", "uninstall", "zuban", "-y"],
214-
check=True,
215-
)
216-
217-
# Install the latest version.
218-
run(
219-
[sys.executable, "-m", "pip", "install", "zuban"],
220-
check=True,
221-
)
198+
self.get_version()
222199
return True
223-
except CalledProcessError:
224-
print("Unable to install zuban")
200+
except (CalledProcessError, FileNotFoundError):
201+
print(
202+
"Unable to run zuban. Install conformance dependencies with "
203+
"'uv sync --frozen' from the conformance directory."
204+
)
225205
return False
226206

227207
def get_version(self) -> str:
228-
proc = run(["zuban", "--version"], stdout=PIPE, text=True)
208+
proc = run(["zuban", "--version"], check=True, stdout=PIPE, text=True)
229209
return proc.stdout.strip()
230210

231211
def run_tests(self, test_files: Sequence[str]) -> dict[str, str]:
@@ -268,23 +248,17 @@ def name(self) -> str:
268248

269249
def install(self) -> bool:
270250
try:
271-
# Uninstall any existing version if present.
272-
run(
273-
[sys.executable, "-m", "pip", "uninstall", "pyrefly", "-y"],
274-
check=True,
275-
)
276-
# Install the latest version.
277-
run(
278-
[sys.executable, "-m", "pip", "install", "pyrefly"],
279-
check=True,
280-
)
251+
self.get_version()
281252
return True
282-
except CalledProcessError:
283-
print("Unable to install pyrefly")
253+
except (CalledProcessError, FileNotFoundError):
254+
print(
255+
"Unable to run pyrefly. Install conformance dependencies with "
256+
"'uv sync --frozen' from the conformance directory."
257+
)
284258
return False
285259

286260
def get_version(self) -> str:
287-
proc = run(["pyrefly", "--version"], stdout=PIPE, text=True)
261+
proc = run(["pyrefly", "--version"], check=True, stdout=PIPE, text=True)
288262
version = proc.stdout.strip()
289263
return version
290264

conformance/tests/generics_typevartuple_callable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def callback2(a: int, d: str) -> tuple[str]:
4242
assert_type(func2(callback2), tuple[str])
4343

4444

45-
def func3(*args: * tuple[int, *Ts, T]) -> tuple[T, *Ts]:
45+
def func3(*args: *tuple[int, *Ts, T]) -> tuple[T, *Ts]:
4646
raise NotImplementedError
4747

4848

0 commit comments

Comments
 (0)