Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .github/workflows/mypy.yml → .github/workflows/ty.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Mypy Checks
name: Ty Checks

on:
pull_request:
Expand All @@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true

jobs:
mypy:
ty:
runs-on: ubuntu-latest
timeout-minutes: 20

Expand All @@ -32,23 +32,20 @@ jobs:
run: |
python_files=()
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
if [[ $file == *.py ]]; then
if [[ $file == src/graph_sitter/*.py || $file == src/codemods/*.py || $file == src/gsbuild/*.py ]]; then
python_files+=("${file}")
fi
done
echo "python_files=${python_files[*]}" >> $GITHUB_ENV

- name: Add MyPy annotator
uses: pr-annotators/mypy-pr-annotator@v1.0.0

- name: Skip rust rewrite baseline type debt
if: ${{ env.python_files != '' && github.event.pull_request.head.ref == 'rust-rewrite' }}
run: |
echo "Skipping PR-wide mypy for the rust-rewrite baseline merge."
echo "Skipping PR-wide ty for the rust-rewrite baseline merge."
echo "Known type debt is documented in rust-rewrite/agent-handoff.md."

- name: Run mypy
- name: Run ty
if: ${{ env.python_files != '' && github.event.pull_request.head.ref != 'rust-rewrite' }}
run: |
echo "Running mypy on changed files: ${{ env.python_files }}"
uv run mypy --no-pretty --show-absolute-path ${{ env.python_files }}
echo "Running ty on changed files: ${{ env.python_files }}"
uv run ty check --output-format=github ${{ env.python_files }}
51 changes: 0 additions & 51 deletions mypy.ini

This file was deleted.

19 changes: 18 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ dev-dependencies = [
"pytest>=8.3.3",
"pytest-cov>=6.3.0,<6.3.1",
"ruff>=0.6.8",
"mypy[mypyc,faster-cache]>=1.13.0",
"pre-commit>=4.0.1",
"pytest-xdist>=3.6.1,<4.0.0",
"pytest-mock<4.0.0,>=3.14.0",
Expand Down Expand Up @@ -162,6 +161,7 @@ dev-dependencies = [
"pytest-lsp>=1.0.0b1",
"cython>=3.0.11",
"pytest-split>=0.10.0",
"ty>=0.0.51",
]

[tool.uv.workspace]
Expand Down Expand Up @@ -202,6 +202,23 @@ show_contexts = true
[tool.pyright]
pythonVersion = "3.12"
enableExperimentalFeatures = true

[tool.ty.environment]
python-version = "3.12"

[tool.ty.src]
include = ["src/graph_sitter", "src/codemods", "src/gsbuild"]
exclude = [
".idea/**",
".vscode/**",
"**/codegen_tests/**",
"tests/**",
"scripts/**",
"**/codemods/**/test_*/**",
"expected/**",
"tests/unit/skills/snapshots/**",
]

[tool.pytest.ini_options]
# addopts = -v
pythonpath = "."
Expand Down
2 changes: 1 addition & 1 deletion rust-rewrite/agent-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Hosted checks on the current branch are the stronger merge signal for large-repo
- Complete mutable expression object parity.
- Promise-chain async conversion parity.
- Rust backend readiness as a default backend for production without explicit rollout gates.
- Clean branch-wide mypy. The rust-rewrite baseline PR intentionally skips PR-wide mypy because the branch changes a large Python surface with known type debt; future focused PRs should restore normal mypy expectations for the files they touch.
- Clean branch-wide ty. The rust-rewrite baseline PR intentionally skips PR-wide ty because the branch changes a large Python surface with known type debt; future focused PRs should restore normal ty expectations for the files they touch.

## High-Value Fanout Lanes

Expand Down
6 changes: 3 additions & 3 deletions rust-rewrite/future-agent-brief.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This branch is intended to become the new development baseline, not the final Ru
- Public API stance: keep the Python shell for users and codemods.
- Backend stance: Rust compact mode is opt-in and covers the supported subset documented in `supported-subset.json`.
- Python backend stance: keep it after this merge. Delete it only after the deletion gates below pass.
- CI stance: fast Rust checks, extension builds, wheel smokes, docs/site checks, and large-repo opt-in checks are the meaningful signal for this baseline. Branch-wide mypy is intentionally skipped for the baseline PR because the branch carries known type debt across a large changed Python surface. The legacy GitHub fixture-push integration tests are also skipped only for the `rust-rewrite` baseline PR because they push branches to an external fixture repo and require a writable PAT; restore or replace that lane after merge. Current `pull_request_target` runs get this through test-level `GITHUB_HEAD_REF == "rust-rewrite"` skips because workflow edits in this PR are not used until they land on `develop`. General release wheels still build `macos-13` on tags/workflow calls, but PRs skip that queue-heavy duplicate and rely on `macos-latest` for macOS smoke.
- CI stance: fast Rust checks, extension builds, wheel smokes, docs/site checks, and large-repo opt-in checks are the meaningful signal for this baseline. Branch-wide ty is intentionally skipped for the baseline PR because the branch carries known type debt across a large changed Python surface. The legacy GitHub fixture-push integration tests are also skipped only for the `rust-rewrite` baseline PR because they push branches to an external fixture repo and require a writable PAT; restore or replace that lane after merge. Current `pull_request_target` runs get this through test-level `GITHUB_HEAD_REF == "rust-rewrite"` skips because workflow edits in this PR are not used until they land on `develop`. General release wheels still build `macos-13` on tags/workflow calls, but PRs skip that queue-heavy duplicate and rely on `macos-latest` for macOS smoke.

## What Future Agents Should Trust

Expand All @@ -27,7 +27,7 @@ This branch is intended to become the new development baseline, not the final Ru
- Full TypeScript type-system, namespace, JSX prop, and mutable expression-object parity.
- Python backend deletion readiness.
- Published-package `uvx graph-sitter ...` claims until a real released artifact is validated.
- Branch-wide mypy cleanliness.
- Branch-wide ty cleanliness.
- Legacy GitHub push integration coverage on the baseline PR; the Rust merge signal comes from unit, fast, extension, wheel, docs/site, and large-repo proof lanes.

## Python Backend Deletion Gates
Expand All @@ -40,7 +40,7 @@ The Python backend can be removed only after these are complete:
- [ ] Add full graph-wide parity harnesses for pinned Airflow and Next.js. Evidence: file, import, export, reference, dependency, external-reference, subclass, and deterministic ordering comparisons.
- [ ] Expand codemod parity beyond smoke flows. Evidence: real codemods on pinned large repos assert exact file-byte diffs, changed-file sets, wall time, and RSS.
- [ ] Replace or remove Python-only graph internals. Evidence: no required public path depends on `rustworkx.PyDiGraph`, eager `SourceFile._nodes`, persistent `tree_sitter.Node` wrappers, or Python object graph traversal.
- [ ] Restore normal mypy and external integration expectations. Evidence: remove the `rust-rewrite` PR skip in `.github/workflows/mypy.yml`, remove the baseline-only `integration-tests` skip in `.github/workflows/test.yml`, or replace the fixture-push tests with hermetic/local equivalents.
- [ ] Restore normal ty and external integration expectations. Evidence: remove the `rust-rewrite` PR skip in `.github/workflows/ty.yml`, remove the baseline-only `integration-tests` skip in `.github/workflows/test.yml`, or replace the fixture-push tests with hermetic/local equivalents.
- [ ] Validate released `uvx graph-sitter ...` package behavior. Evidence: `uvx graph-sitter doctor`, `parse`, `run`, and `transform` work from a clean environment with a published version.

## Highest-Value Fanout Work
Expand Down
6 changes: 3 additions & 3 deletions src/graph_sitter/compiled/resolution.pyi
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from dataclasses import dataclass, field
from functools import cached_property as cached_property
from typing import Generic

from typing_extensions import TypeVar

from graph_sitter.codebase.codebase_context import CodebaseContext
from graph_sitter.core.dataclasses.usage import UsageKind, UsageType
from graph_sitter.core.dataclasses.usage import UsageKind as UsageKind
from graph_sitter.core.dataclasses.usage import UsageType
from graph_sitter.core.interfaces.editable import Editable
from graph_sitter.core.interfaces.has_name import HasName

NodeType = TypeVar("NodeType")

@dataclass
class ResolutionStack(Generic[NodeType]):
class ResolutionStack[NodeType]:
"""Represents the resolution stack from a symbol to a usage

Symbol
Expand Down
19 changes: 19 additions & 0 deletions src/graph_sitter/compiled/sort.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from collections.abc import Iterable

from tree_sitter import Node as TSNode
from typing_extensions import TypeVar

from graph_sitter.core.interfaces.editable import Editable

E = TypeVar("E", bound=Editable)

def sort_editables(
nodes: Iterable[E | None] | Iterable[E],
*,
reverse: bool = False,
dedupe: bool = True,
alphabetical: bool = False,
by_file: bool = False,
by_id: bool = False,
) -> list[E]: ...
def sort_nodes(nodes: Iterable[TSNode | None] | Iterable[TSNode], *, reverse: bool = False, dedupe: bool = True) -> list[TSNode]: ...
2 changes: 2 additions & 0 deletions src/graph_sitter/compiled/utils.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ from functools import lru_cache as functools_lru_cache

from tree_sitter import Node as TSNode

TSNode = TSNode

def get_all_identifiers(node: TSNode) -> list[TSNode]:
"""Get all the identifiers in a tree-sitter node. Recursive implementation"""

Expand Down
54 changes: 34 additions & 20 deletions src/graph_sitter/core/autocommit/decorators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from graph_sitter.shared.logging.get_logger import get_logger
import functools
from collections.abc import Callable
from typing import TYPE_CHECKING, ParamSpec, TypeVar, Union, overload
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, Union, cast, overload

import wrapt

Expand Down Expand Up @@ -39,10 +39,18 @@ def writer(
commit: Whether to commit if there is an update. Do not set this to False unless you are absolutely sure the method can be retried with commit as True safely.
"""
if wrapped is None:
return functools.partial(writer, commit=commit)
return cast(
Callable[[Callable[P, T]], Callable[P, T]],
functools.partial(writer, commit=commit),
)

@wrapt.decorator(enabled=enabled)
def wrapper(wrapped: Callable[P, T], instance: "Editable", args, kwargs) -> T:
def wrapper(
wrapped: Callable[P, T],
instance: Any,
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> T:
if instance is None:
instance = args[0]
if instance.removed:
Expand All @@ -57,35 +65,39 @@ def wrapper(wrapped: Callable[P, T], instance: "Editable", args, kwargs) -> T:

@wrapt.decorator(enabled=enabled)
def remover(
wrapped: Callable[P, T],
wrapped: Callable[..., T],
instance: Union["Symbol", None] = None,
args: P.args = None,
kwargs: P.kwargs = None,
) -> Callable[P, T]:
args: tuple[Any, ...] = (),
kwargs: dict[str, Any] | None = None,
) -> T:
"""Indicates the node will be removed at the end of this method.

Further usage of the node will result in undefined behaviour and a warning.
"""
if instance is None:
instance = args[0]
instance = cast("Symbol", args[0])
if kwargs is None:
kwargs = {}
logger.debug("Removing node %r, %r", instance, wrapped)
with instance.ctx._autocommit.write_state(instance):
ret = wrapped(*args, **kwargs)
# instance.ctx._autocommit.set_pending(instance, REMOVED)
instance.removed = True
cast(Any, instance).removed = True
return ret


@wrapt.decorator(enabled=enabled)
def repr_func(
wrapped: Callable[P, T],
wrapped: Callable[..., T],
instance: Union["Editable", None] = None,
args: P.args = None,
kwargs: P.kwargs = None,
) -> Callable[P, T]:
args: tuple[Any, ...] = (),
kwargs: dict[str, Any] | None = None,
) -> T:
"""Indicates the method is use in debugging/logs."""
if instance is None:
instance = args[0]
instance = cast("Editable", args[0])
if kwargs is None:
kwargs = {}
autocommit = instance.ctx._autocommit
old_state = autocommit.enter_state(AutoCommitState.Special)
try:
Expand All @@ -97,19 +109,21 @@ def repr_func(

@wrapt.decorator(enabled=enabled)
def mover(
wrapped: Callable[P, tuple[NodeId, NodeId]],
wrapped: Callable[..., tuple[NodeId, NodeId]],
instance: Union["Symbol", None] = None,
args: P.args = None,
kwargs: P.kwargs = None,
) -> Callable[P, None]:
args: tuple[Any, ...] = (),
kwargs: dict[str, Any] | None = None,
) -> None:
"""Indicates the Node will be moved by the end of this method.

It should also return the node_id of itself and the new file
"""
if instance is None:
instance = args[0]
instance = cast("Symbol", args[0])
if kwargs is None:
kwargs = {}
with instance.ctx._autocommit.write_state(instance, move=True):
file_node_id, node_id = wrapped(*args, **kwargs)
instance.ctx._autocommit.set_pending(instance, node_id, file_node_id)
instance.removed = False
cast(Any, instance).removed = False
return None
Loading
Loading