Enable `require-build-hashes` for the Python projects and inline scripts
so isolated builds require declared hashes for their build dependencies.
Pin the build backends and their dependencies with hashes.
---------
Co-authored-by: zaniebot <242828183+zaniebot@users.noreply.github.com>
## Summary
Enable `truthiness-test-of-none-union` in the standalone scripts' inline
configuration and in the `py-fuzzer` and `ty_benchmark` projects,
following #28889.
The playground workflows disable dependency lifecycle scripts, but
ordinary local installs in the playground, its API, and the ty benchmark
project still enable them. Set `ignore-scripts` in each project's
`.npmrc` so the same default applies outside CI, while preserving the
existing seven-day release-age policy. Because `ignore-scripts` also
suppresses first-party pre/post hooks, the playground's `build`,
`dev:build`, and `start` commands invoke their required WASM preparation
explicitly. The datastore instructions now install from the API lockfile
and invoke its local Wrangler instead of allowing `npx` to resolve a
separate dependency tree.
Related: astral-sh/ruff-vscode#1151 and astral-sh/ty-vscode#525 apply
the same install-script default to the editor extensions.
astral-sh/ruff#27844 enforces the cooldown-supporting `npm` floor,
astral-sh/ruff#27842 hardens the native `prek` formatter environment,
and astral-sh/ruff#27838 locks the playground deployment toolchain.
---------
Co-authored-by: zaniebot <242828183+zaniebot@users.noreply.github.com>
Python tooling can currently build third-party source distributions. Set
`no-build` in the maintained Python projects and PEP 723 scripts,
retaining named exceptions for Git-pinned tooling and the local
ecosystem runner. Pin the isolated build dependencies for those
exceptions, including their transitive requirements.
Follows merged astral-sh/ruff#27856, which gives the legacy `ruff-lsp`
suite a dedicated, locked Python 3.12 test environment.
Follow-up to astral-sh/ruff#27843. Replaces astral-sh/ruff#27840, which
closed when the merged docs branch was deleted.
Co-authored-by: zaniebot <242828183+zaniebot@users.noreply.github.com>
Older `npm` releases silently ignore the seven-day dependency cooldown
added in #27796. Require `npm` 11.10.0 or newer in the playground,
playground API, and ty benchmark projects, and enable `engine-strict` so
unsupported clients fail during `npm install` or `npm ci`. The lockfile
changes only record that requirement; dependency versions and the
existing cooldown are unchanged.
Pin Node.js to `24.19.0` for the playground CI and publishing jobs,
which provides a compatible bundled `npm`. The separate script-default
changes are in #27837, and the locked playground deployment tool is in
#27838.
Related: astral-sh/ruff#27855 pins the remaining WebAssembly test and
publication runtimes. astral-sh/setup-uv#1026 and
astral-sh/ruff-action#411 enforce the same legacy-aware `npm` version
floor. astral-sh/uv#21193 applies it to uv's locked formatter toolchain.
---------
Co-authored-by: zaniebot <242828183+zaniebot@users.noreply.github.com>
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->
## Summary
Adds explicit 7d cooldowns to all of our PEP 723 scripts, as well as
similar cooldowns via `.npmrc`.
<!-- What's the purpose of the change? What does it do, and why? -->
## Test Plan
NFC.
<!-- How was it tested? -->
---
<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
## Summary
Regenerate the ty benchmark snapshots against the current ty commit and
latest version of each tool:
- ty: `ruff/0.15.20+28 (1d2041ca1 2026-06-26)`
- Pyrefly: `1.1.1`
- Pyright: `1.1.411`
- mypy: `2.1.0`
## Summary
This fixes an oversight from #26110, where we bumped the benchmark
Pyrefly pin to 1.1.0 but did not update the expected Pyrefly benchmark
snapshots.
## Summary
Update the ty benchmark harness to use Pyrefly 1.1.0.
This only changes the benchmark dependency pin and lockfile. Benchmark
outputs and diagnostic snapshots are intentionally left unchanged.
## Test Plan
- `cd scripts/ty_benchmark && uv lock --upgrade-package pyrefly`
- `cd scripts/ty_benchmark && uv run --python 3.14 pyrefly --version`
- `uvx prek run --files scripts/ty_benchmark/pyproject.toml
scripts/ty_benchmark/uv.lock`
## Summary
This PR bumps the project revisions used by `ty_benchmark` and refreshes
the expected CLI diagnostic snapshots against current upstream code.
The refreshed revisions require a few corresponding dependency changes.
Home Assistant now installs its full and test requirements separately
and excludes `dtlssocket`, which is pulled in by `pytradfri[async]` but
only distributed as an sdist that requires `autoconf`; pandas-stubs now
requires Python 3.11 and exposes its development dependencies through a
dependency group; and PyTorch's import environment is refreshed from its
upstream Pyrefly linter configuration while checker versions remain
controlled by this benchmark.
The benchmark harness now installs mypy only when a mypy suite is
selected, and uses a `uv` override to pin those runs to mypy 2.1.0 even
for projects that declare their own mypy development dependency. This
keeps ty and Pyrefly runs from inheriting mypy's Python-version and
dependency constraints while preserving one benchmark-managed mypy
version for mypy runs.
We also advance the dependency cutoff and update the benchmarked type
checkers to mypy 2.1.0, Pyright 1.1.410, and Pyrefly 1.0.0.
## Summary
Enable Ruff's own `S602` rule (`subprocess-popen-with-shell-equals-true`
from flake8-bandit) in `pyproject.toml` to enforce no-shell subprocess
calls going forward, and fix the two existing violations it catches.
**`pyproject.toml`** - adds `S602` to the lint `select` list so any
future `shell=True` usage in the scripts is caught automatically by the
linter.
**`scripts/setup_primer_project.py`** - `project.install_cmd` and
`project.deps` come from the mypy-primer project registry (an
externally-fetched third-party config). Both were joined into shell
strings and executed with `shell=True`, making them a supply-chain
injection vector. Fixed by tokenising with `shlex.split()` and dropping
`shell=True`.
**`scripts/ty_benchmark/src/benchmark/snapshot.py`** - `command.prepare`
was passed to `subprocess.run(..., shell=True)`. While no current caller
sets this field, it is a latent injection point. Fixed by tokenising
with `shlex.split()` and dropping `shell=True`; adds the missing `import
shlex`.
## Test Plan
Both scripts are developer-only utilities. The changes are semantically
equivalent for well-formed inputs - `shlex.split()` produces the same
argument list the shell would have constructed, while refusing to pass
metacharacters through to a shell process.
Verified no other `shell=True` uses remain in `scripts/` or `python/`
that would be newly flagged by S602.
## Summary
Part of https://github.com/astral-sh/ty/issues/1240
Stop unioning `Unknown` into the types of un-annotated container
literals.
We discussed perhaps continuing to union `Unknown` if the inferred type
is a singleton type like `None`. I'd like to explore this as a separate
change so we can see the ecosystem impact more clearly.
## Test Plan
Adjusted many mdtest expectations.
There's one test case that regresses with this change, because we don't
fully support union type contexts (it can require a lot of repeat
inference in pathological cases). So `x10: list[int | str] | list[int |
None] = [1, 2, 3]` previously passed only because we inferred the RHS as
`list[Unknown | int]` -- now we infer it as `list[int]` and the
assignment fails due to invariance. I've kept this test as a TODO since
it's not trivial to fix. Mypy errors in the same way we now do,
suggesting it's not necessarily a huge priority either.
## Ecosystem
This change is expected to cause new diagnostics and some false
positives, since we are replacing very-forgiving gradual types with
non-gradual inference heuristics.
Many of these issues could be solved or significantly mitigated by
https://github.com/astral-sh/ty/issues/1473, depending how far we are
able to go with that, and particularly whether we can afford to apply it
also to container literals which are not empty at construction. The
downside of broad application of this approach is that in some cases it
could cause us to widen container types when the user actually just made
a mistake and added the wrong thing to a container, and would prefer an
error at that location.
Some categories of new error that show up in the ecosystem report:
### Implicit TypedDicts
These are cases where the dictionary is heterogeneous and would ideally
be typed as a `TypedDict` but isn't, for example:
```py
def make_person(photo: bytes | None):
person = {"name": "Pat", age: 29}
if photo is not None:
person["photo"] = photo
```
We (and pyrefly, and pyright in strict mode) error on the last line here
because we already inferred `dict[str, str | int]`, so we can't add a
`bytes` value.
Mypy prefers common-base joins over union joins, so it infers `dict[str,
object]`, which avoids the error adding a `bytes` value. This means the
value type is less precise, which theoretically means potentially more
errors using values from the dict later. But in practice with this
heterogeneous pattern, either `object` or the union will cause similar
problems when using values from the dict -- in either case you'd
probably have to cast or narrow.
Pyright (in non-strict mode) has a special case where it falls back to
`Unknown` when it sees heterogenous value types, so it infers this as
`dict[str, Unknown]`.
I think we could consider either the mypy or pyright approaches here,
but we don't need to do it in this PR; we can file an issue and consider
it as a follow-up.
Another symptom of this same root cause is repetitive diagnostics
arising from a large union inferred as value type; the same fixes would
address this.
### Negative intersections, particularly with e.g. `~AlwaysFalsy` or
`~None`.
Example:
```py
class A: ...
def _(a: A | None) -> dict[str, A]:
if a:
d = {"a": a}
return d
return {}
```
We error on `return d` because "expected `dict[str, A]`, found
`dict[str, A & ~AlwaysFalsy]`". This is an issue specific to
intersection types, so no other type checker has this problem.
I think when we "promote literals" (we may need to give this operation a
broader name -- it's really "type promotion to give a better inferred
type when invariance means too-precise is bad") we should also eliminate
all negative types from intersections. I would prefer to do this as a
separate PR for easier review and better visibility of ecosystem impact,
but I think it's high priority to land soon after this PR (ideally
before a release).
### Overly-precise inference for singleton `None`
This did show up, to the tune of ~100 new diagnostics
([example](https://github.com/pytorch/ignite/blob/b73a4c20e991b3e14949f2a69651ed2a7219f2fd/tests/ignite/engine/test_engine.py#L158)),
so I think it is worth addressing as a follow-up.
## Summary
This PR sets up CI jobs to run ty from the `main` branch on the files
and subdirectories in our `scripts` directory
## Test Plan
Both these commands pass for me locally:
- `uv run --project=./scripts cargo run -p ty check --project=./scripts`
- `uv run --project=./scripts/ty_benchmark cargo run -p ty check
--project=./scripts/ty_benchmark`
## Summary
This currently doesn't work because the benchmark changes the working
directory. Also updates the process name to make it easier to compare
two local ty binaries.