diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b42e1050f..30894f666 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -171,6 +171,31 @@ jobs: version: PATH project: ./pyrightconfig.testcases.json + run-pyrefly: + timeout-minutes: 10 + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.12'] + fail-fast: false + steps: + - uses: actions/checkout@v5 + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --no-dev --group pyrefly + + - name: Add venv to path + run: echo "$PWD/.venv/bin" >> $GITHUB_PATH + + - name: Run pyrefly on the test cases + run: pyrefly check tests/assert_type + matrix-test: timeout-minutes: 10 runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index f5a35f57f..53a4cba3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,10 +61,14 @@ tests = [ pyright = [ "pyright==1.1.407", ] +pyrefly = [ + "pyrefly==0.43.0", +] dev = [ {include-group = "tests"}, {include-group = "pyright"}, + {include-group = "pyrefly"}, ] [project.urls] diff --git a/pyrefly.toml b/pyrefly.toml new file mode 100644 index 000000000..8f1a9c94b --- /dev/null +++ b/pyrefly.toml @@ -0,0 +1,2 @@ +# Respect only `# pyrefly: ignore` (ignore `# type: ignore`) +enabled-ignores = ["pyrefly"] diff --git a/tests/assert_type/apps/test_config.py b/tests/assert_type/apps/test_config.py index 7f92760ae..3f5bf8f83 100644 --- a/tests/assert_type/apps/test_config.py +++ b/tests/assert_type/apps/test_config.py @@ -12,7 +12,7 @@ class BarConfig(AppConfig): name = "foo" @property - def default_auto_field(self) -> str: # type: ignore[override] + def default_auto_field(self) -> str: # type: ignore[override] # pyrefly: ignore[bad-override] return "django.db.models.BigAutoField" @@ -20,7 +20,7 @@ class BazConfig(AppConfig): name = "foo" @cached_property - def default_auto_field(self) -> str: # type: ignore[override] + def default_auto_field(self) -> str: # type: ignore[override] # pyrefly: ignore[bad-override] return "django.db.models.BigAutoField" diff --git a/tests/assert_type/db/models/test_constraints.py b/tests/assert_type/db/models/test_constraints.py index 484f3e54c..421ae45ee 100644 --- a/tests/assert_type/db/models/test_constraints.py +++ b/tests/assert_type/db/models/test_constraints.py @@ -7,13 +7,13 @@ UniqueConstraint(Lower("name").desc(), "category", name="unique_lower_name_category") UniqueConstraint(fields=["name"], name="unique_name") # There's no overload case for passing both expression and 'fields' -UniqueConstraint( # type: ignore[call-overload] +UniqueConstraint( # type: ignore[call-overload] # pyrefly: ignore[no-matching-overload] Lower("name"), fields=["name"], # pyright: ignore[reportArgumentType] name="unique_mess", ) -CheckConstraint( # type: ignore[deprecated] # pyright: ignore[reportDeprecated] +CheckConstraint( # type: ignore[deprecated] # pyright: ignore[reportDeprecated] # pyrefly: ignore[deprecated] name="less_than_constraint", check=LessThan[Any](F("months"), 1), ) diff --git a/tests/assert_type/db/models/test_enums.py b/tests/assert_type/db/models/test_enums.py index 90931315a..913d592b4 100644 --- a/tests/assert_type/db/models/test_enums.py +++ b/tests/assert_type/db/models/test_enums.py @@ -227,12 +227,26 @@ class Joker(TextChoices): # Note: Suppress errors from pyright as the mypy plugin narrows the type of labels if non-lazy. assert_type(VoidChoices.names, list[str]) assert_type(VoidChoices.labels, list[str]) # pyright: ignore[reportAssertTypeFailure] -assert_type(VoidChoices.values, list[Any | None]) # pyright: ignore[reportAssertTypeFailure] -assert_type(VoidChoices.choices, list[tuple[Any | None, str]]) # pyright: ignore[reportAssertTypeFailure] + +assert_type(VoidChoices.values, list[int | None]) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type( + VoidChoices.values, # pyrefly: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] + list[Any | None], +) + +assert_type(VoidChoices.choices, list[tuple[int | None, str]]) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type( + VoidChoices.choices, # pyrefly: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] + list[tuple[Any | None, str]], +) + assert_type(VoidChoices.ABYSS, Literal[VoidChoices.ABYSS]) assert_type(VoidChoices.ABYSS.name, Literal["ABYSS"]) assert_type(VoidChoices.ABYSS.label, str) # pyright: ignore[reportAssertTypeFailure] -assert_type(VoidChoices.ABYSS.value, Any) + +assert_type(VoidChoices.ABYSS.value, int) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type(VoidChoices.ABYSS.value, Any) # pyrefly: ignore[assert-type] + assert_type(VoidChoices.ABYSS.do_not_call_in_templates, Literal[True]) assert_type(VoidChoices.__empty__, str) # pyright: ignore[reportAssertTypeFailure] @@ -274,7 +288,10 @@ class Joker(TextChoices): # Assertions for mixing multiple choices types with consistent base types - only `TextChoices`. x1 = (Medal, Gender) -assert_type([member.label for choices in x1 for member in choices], list[_StrOrPromise]) + +assert_type([member.label for choices in x1 for member in choices], list[str]) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type([member.label for choices in x1 for member in choices], list[_StrOrPromise]) # pyrefly: ignore[assert-type] + assert_type([member.value for choices in x1 for member in choices], list[str]) # Assertions for mixing multiple choices types with different base types - `IntegerChoices` and `TextChoices`. @@ -284,8 +301,12 @@ class Joker(TextChoices): # Assertions for mixing multiple choices types with consistent base types - custom types. x3 = (Constants, Separator) -assert_type([member.label for choices in x3 for member in choices], list[_StrOrPromise]) -assert_type([member.value for choices in x3 for member in choices], list[Any]) + +assert_type([member.label for choices in x3 for member in choices], list[str]) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type([member.label for choices in x3 for member in choices], list[_StrOrPromise]) # pyrefly: ignore[assert-type] + +assert_type([member.value for choices in x3 for member in choices], list[bytes | float]) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] +assert_type([member.value for choices in x3 for member in choices], list[Any]) # pyrefly: ignore[assert-type] # Assertions for choices objects defined and aliased in a model. diff --git a/tests/assert_type/db/models/test_ordering.py b/tests/assert_type/db/models/test_ordering.py index 5890eb484..d1dee9ce1 100644 --- a/tests/assert_type/db/models/test_ordering.py +++ b/tests/assert_type/db/models/test_ordering.py @@ -35,15 +35,15 @@ ) # failure cases -qs.order_by(123) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] -qs.order_by(["username"]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] -qs.order_by({"username": "asc"}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] +qs.order_by(123) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] +qs.order_by(["username"]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] +qs.order_by({"username": "asc"}) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] -qs.extra(order_by=[123]) # type: ignore[list-item] # pyright: ignore[reportArgumentType] -qs.extra(order_by=["username", 456]) # type: ignore[list-item] # pyright: ignore[reportArgumentType] +qs.extra(order_by=[123]) # type: ignore[list-item] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] +qs.extra(order_by=["username", 456]) # type: ignore[list-item] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] -query.add_ordering(123) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] -query.add_ordering(["username"]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] +query.add_ordering(123) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] +query.add_ordering(["username"]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] query.add_extra( select=None, @@ -51,5 +51,5 @@ where=None, params=None, tables=None, - order_by=[123, "username"], # type: ignore[list-item] # pyright: ignore[reportArgumentType] + order_by=[123, "username"], # type: ignore[list-item] # pyright: ignore[reportArgumentType] # pyrefly: ignore[bad-argument-type] ) diff --git a/tests/assert_type/forms/test_add_error.py b/tests/assert_type/forms/test_add_error.py index 601e73ef3..59b74d044 100644 --- a/tests/assert_type/forms/test_add_error.py +++ b/tests/assert_type/forms/test_add_error.py @@ -27,9 +27,9 @@ assert_type(form.add_error("field", ValidationError(["error"])), None) assert_type(form.add_error(None, ValidationError({"field": "error"})), None) -form.add_error("field", {"field": "error"}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] -form.add_error("field", {"field": lazystr("error")}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] -form.add_error("field", {"field": ValidationError("error")}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] -form.add_error("field", {"field": ["error"]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] -form.add_error("field", {"field": [lazystr("error")]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] -form.add_error("field", {"field": [ValidationError("error")]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] +form.add_error("field", {"field": "error"}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] +form.add_error("field", {"field": lazystr("error")}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] +form.add_error("field", {"field": ValidationError("error")}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] +form.add_error("field", {"field": ["error"]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] +form.add_error("field", {"field": [lazystr("error")]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] +form.add_error("field", {"field": [ValidationError("error")]}) # type: ignore[call-overload] # pyright: ignore[reportCallIssue,reportArgumentType] # pyrefly: ignore[no-matching-overload] diff --git a/tests/assert_type/http/test_request.py b/tests/assert_type/http/test_request.py index 8544d0c4f..47f7457fa 100644 --- a/tests/assert_type/http/test_request.py +++ b/tests/assert_type/http/test_request.py @@ -27,7 +27,7 @@ # Test MutableQueryDict mut_q = QueryDict(mutable=True) mut_q["a"] = "3" -mut_q["a"] = ["1", "2"] # type: ignore[assignment] # pyright: ignore[reportArgumentType] +mut_q["a"] = ["1", "2"] # type: ignore[assignment] # pyright: ignore[reportArgumentType] # pyrefly: ignore[unsupported-operation] assert_type(mut_q.pop("a"), list[str]) assert_type(mut_q.pop("a", 12), list[str] | int) diff --git a/tests/assert_type/middleware/test_middleware.py b/tests/assert_type/middleware/test_middleware.py index 2a1679bc2..298bae762 100644 --- a/tests/assert_type/middleware/test_middleware.py +++ b/tests/assert_type/middleware/test_middleware.py @@ -16,7 +16,7 @@ class CustomCommonMiddleware(CommonMiddleware): class BrokenCustomCommonMiddleware(CommonMiddleware): - response_redirect_class = FileResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] + response_redirect_class = FileResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] # pyrefly: ignore[bad-assignment] class CustomLocaleMiddleware(LocaleMiddleware): @@ -24,7 +24,7 @@ class CustomLocaleMiddleware(LocaleMiddleware): class BrokenCustomLocaleMiddleware(CommonMiddleware): - response_redirect_class = FileResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] + response_redirect_class = FileResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] # pyrefly: ignore[bad-assignment] class CustomRedirectFallbackMiddleware(RedirectFallbackMiddleware): @@ -38,5 +38,5 @@ class CustomRedirectFallbackMiddleware2(RedirectFallbackMiddleware): class BrokenCustomRedirectFallbackMiddleware(RedirectFallbackMiddleware): - response_redirect_class = HttpResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] - response_gone_class = 12 # type:ignore[assignment] # pyright: ignore[reportAssignmentType] + response_redirect_class = HttpResponse # type:ignore[assignment] # pyright: ignore[reportAssignmentType] # pyrefly: ignore[bad-assignment] + response_gone_class = 12 # type:ignore[assignment] # pyright: ignore[reportAssignmentType] # pyrefly: ignore[bad-assignment] diff --git a/uv.lock b/uv.lock index 5dace6c96..fe6444c2d 100644 --- a/uv.lock +++ b/uv.lock @@ -249,6 +249,7 @@ dev = [ { name = "mypy" }, { name = "mysqlclient" }, { name = "psycopg2-binary" }, + { name = "pyrefly" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-mypy-plugins" }, @@ -256,6 +257,9 @@ dev = [ { name = "pytest-xdist" }, { name = "pyyaml" }, ] +pyrefly = [ + { name = "pyrefly" }, +] pyright = [ { name = "pyright" }, ] @@ -295,6 +299,7 @@ dev = [ { name = "mypy", specifier = "==1.18.2" }, { name = "mysqlclient", specifier = "==2.2.7" }, { name = "psycopg2-binary", specifier = "==2.9.11" }, + { name = "pyrefly", specifier = "==0.43.0" }, { name = "pyright", specifier = "==1.1.407" }, { name = "pytest", specifier = "==9.0.1" }, { name = "pytest-mypy-plugins", specifier = "==3.2.0" }, @@ -302,6 +307,7 @@ dev = [ { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pyyaml", specifier = "==6.0.3" }, ] +pyrefly = [{ name = "pyrefly", specifier = "==0.43.0" }] pyright = [{ name = "pyright", specifier = "==1.1.407" }] tests = [ { name = "django", specifier = "==5.2.8" }, @@ -707,6 +713,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pyrefly" +version = "0.43.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b7/a87bd2b109521a6c96788f40c76011911b6791b0700da8468610e1039884/pyrefly-0.43.0.tar.gz", hash = "sha256:8c6d93516be954bc4730aef96b61a654852f22db9b108cb817e3eff6793da13e", size = 3878331, upload-time = "2025-11-24T15:34:08.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/63/0fbf13a58e54cad93c96c3a0b928fd1e863dcf23cc89ed3d592a0c2eb1dc/pyrefly-0.43.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4814dad395522386788877ce1a5f3a88554328dc0763d62d61cc85478ecec631", size = 9772366, upload-time = "2025-11-24T15:33:49.232Z" }, + { url = "https://files.pythonhosted.org/packages/15/75/e83b8d40ad7227a9c44c9351626cd593fa8684c88cfc7757ef9d7f267171/pyrefly-0.43.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:618f4045cfc5311182f165433086866e34b517994457b6f84304c04034504570", size = 9384013, upload-time = "2025-11-24T15:33:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/7629a752eb820c45dece977a316a92f78588e30cb5fc80b1d791ea11b8be/pyrefly-0.43.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:133b34a68ca001cceea3882c156fcb9fce93b22cc13777e704147fba902cd489", size = 9632706, upload-time = "2025-11-24T15:33:55.016Z" }, + { url = "https://files.pythonhosted.org/packages/d4/51/dc26e0b9caaedb5519a1fe16b07a4b0a25c0599f398915b796b2a55c1041/pyrefly-0.43.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57235b55f867897fb918afb33ec7a117cf8cf076d682c6f717a9c2ffc9ae588", size = 10471216, upload-time = "2025-11-24T15:33:57.137Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e2/cfa3b95ad1d2a62a3553fe573c6c0db36d7318407df9941dcf53d424d51b/pyrefly-0.43.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad73190ec0f5b6a2e821f2f9e90f90a03681d2daa5a45c427131a5ab7b268a0d", size = 10081561, upload-time = "2025-11-24T15:33:59.158Z" }, + { url = "https://files.pythonhosted.org/packages/0d/93/df8acb1780ff507290d909d463ab3579047e521940646ad73c6405e9cabd/pyrefly-0.43.0-py3-none-win32.whl", hash = "sha256:faa585d1d5b97303affc77f2a656efb5067f7c48c1eeecc39deb47ba53edfec7", size = 9560294, upload-time = "2025-11-24T15:34:01.026Z" }, + { url = "https://files.pythonhosted.org/packages/63/7e/2313faecb9e3c7563512135d3e03fbf1871b767034744342ccd305b29086/pyrefly-0.43.0-py3-none-win_amd64.whl", hash = "sha256:c2f1132581ce210385ea735ab786544faa4ea11c5746ce1f9238defee0edbb93", size = 10180544, upload-time = "2025-11-24T15:34:03.719Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d2/cedd1c833d0030644271c32275958a2b58ba36d550829440456e5a2dea2c/pyrefly-0.43.0-py3-none-win_arm64.whl", hash = "sha256:c3023ff90c27cf158654980c37b40dd0dc8660d6241a170b3e6c7671966ed099", size = 9783906, upload-time = "2025-11-24T15:34:06.337Z" }, +] + [[package]] name = "pyright" version = "1.1.407"