Skip to content

feat: rewrite signature definitions and call sites - #2047

Closed
vitali87 wants to merge 1 commit into
mainfrom
pr-split/split-pr1548-rebased/pr-4
Closed

vitali87 wants to merge 1 commit into
mainfrom
pr-split/split-pr1548-rebased/pr-4

Conversation

@vitali87

Copy link
Copy Markdown
Owner

Rewrite helpers plus postcondition contract expectation support.

Files changed

  • codebase_rag/editing/contract.py
  • codebase_rag/editing/signature_rewrite.py

Diff stats

+143 additions, -1 deletions (144 LOC)

Dependencies

This PR depends on: pr-1, pr-2, pr-3

Dependency graph

Merge in this order:

pr-1: feat: add change-signature types and constants
├── pr-2: feat: parse and render signature parameters
│   ├── pr-4: feat: rewrite signature definitions and call sites  <-- this PR
│   │   └── pr-5: feat: plan change-signature graph edits
│   │       └── pr-6: feat: expose change-signature via API, CLI, and MCP
│   │           ├── pr-7: test: cover change-signature units
│   │           │   ├── pr-8: test: cover change-signature acceptance flows
│   │           │   └── pr-9: test: cover change-signature regressions
│   │           └── pr-10: docs: document change-signature architecture
│   └── pr-5: feat: plan change-signature graph edits
│       └── pr-6: feat: expose change-signature via API, CLI, and MCP
│           ├── pr-7: test: cover change-signature units
│           │   ├── pr-8: test: cover change-signature acceptance flows
│           │   └── pr-9: test: cover change-signature regressions
│           └── pr-10: docs: document change-signature architecture
├── pr-3: feat: map change-signature call arguments
│   ├── pr-4: feat: rewrite signature definitions and call sites  <-- this PR
│   │   └── pr-5: feat: plan change-signature graph edits
│   │       └── pr-6: feat: expose change-signature via API, CLI, and MCP
│   │           ├── pr-7: test: cover change-signature units
│   │           │   ├── pr-8: test: cover change-signature acceptance flows
│   │           │   └── pr-9: test: cover change-signature regressions
│   │           └── pr-10: docs: document change-signature architecture
│   └── pr-5: feat: plan change-signature graph edits
│       └── pr-6: feat: expose change-signature via API, CLI, and MCP
│           ├── pr-7: test: cover change-signature units
│           │   ├── pr-8: test: cover change-signature acceptance flows
│           │   └── pr-9: test: cover change-signature regressions
│           └── pr-10: docs: document change-signature architecture
├── pr-4: feat: rewrite signature definitions and call sites  <-- this PR
│   └── pr-5: feat: plan change-signature graph edits
│       └── pr-6: feat: expose change-signature via API, CLI, and MCP
│           ├── pr-7: test: cover change-signature units
│           │   ├── pr-8: test: cover change-signature acceptance flows
│           │   └── pr-9: test: cover change-signature regressions
│           └── pr-10: docs: document change-signature architecture
└── pr-5: feat: plan change-signature graph edits
    └── pr-6: feat: expose change-signature via API, CLI, and MCP
        ├── pr-7: test: cover change-signature units
        │   ├── pr-8: test: cover change-signature acceptance flows
        │   └── pr-9: test: cover change-signature regressions
        └── pr-10: docs: document change-signature architecture

@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 8 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: vitali87/code-graph-rag/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 2cf1d8f8-6b94-4d17-80ec-bceb94abb78a

📥 Commits

Reviewing files that changed from the base of the PR and between f762173 and 7126da0.

📒 Files selected for processing (11)
  • codebase_rag/constants/ast_nodes.py
  • codebase_rag/constants/ast_python.py
  • codebase_rag/constants/cli.py
  • codebase_rag/constants/graph.py
  • codebase_rag/constants/mcp.py
  • codebase_rag/editing/contract.py
  • codebase_rag/editing/signature_calls.py
  • codebase_rag/editing/signature_literals.py
  • codebase_rag/editing/signature_params.py
  • codebase_rag/editing/signature_rewrite.py
  • codebase_rag/editing/signature_types.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

Not safe to merge until Python receiver-aware positional-only restoration is corrected and the required descriptive helper names are used. The comment-handling issues are non-blocking but should be addressed to avoid lost documentation and skipped valid call sites.

Findings

  1. P1 Keep method boundary aligned ▶
  2. P2 Keep signature comments ▶
  3. P2 Ignore argument comments ▶
  4. P2 Name source helper clearly ▶

Summary

This change adds signature definition and call-site rewriting. A Python method rewrite can move the positional-only marker and break existing keyword calls. The implementation also deletes inline signature comments and refuses valid call sites containing inline argument comments. The method contract regression and the repository naming requirement must be resolved before merging.

Reviews (1) · Last reviewed commit: "feat: rewrite signature definitions and ..."

spec.from_index != index for index, spec in enumerate(kept)
):
raise SignatureRefused(cs.SIGNATURE_POSITIONAL_ONLY_MOVED)
rendered.insert(receivers + boundary, "/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Keep method boundary aligned

For def m(self, a, /, b), the rewrite counts self in the positional-only boundary even though the requested parameter specs exclude the receiver. It rewrites the unchanged signature as def m(self, a, b, /), making b positional-only. Existing calls such as C().m(1, b=2) then raise TypeError.

Artifacts

Evidence from the check

  • Authored script that loads the checkout implementation, invokes rewrite_definition with unchanged specs, and executes both original and rewritten methods; it reproduces the contract change.

Command output from the check

  • Captured command output for the original `def m(self, a, /, b)` showing `C().m(1, b=2)` returned 3; the original contract permits the keyword.

Command output from the check

  • Captured command output after executing rewrite_definition showing `def m(self, a, b, /)` parses but `C().m(1, b=2)` raises TypeError; the rewrite changes the contract.

View artifacts

T-Rex Ran code and verified through T-Rex

rendered.append("*")
rendered.extend(_text(p.node) for p in keyword_only)
text = "(" + ", ".join(rendered) + ")"
patcher.replace_span(path, (params_node.start_byte, params_node.end_byte), text)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Keep signature comments

Rebuilding the complete parameter span drops comments between parameters even when every parameter is retained. For example, def f(a, # explanation\n b) becomes def f(a, b). This is non-blocking, but a routine signature edit silently removes user-authored documentation.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Artifacts

Evidence from the check

  • This script parses an in-memory Python definition, calls the production signature rewrite with both original parameters retained, and asserts that the comment disappears, proving the focused execution path.

Command output from the check

  • This captured command output shows the actual before and after definition, confirms both parameters were retained and the comment was absent afterward, and ends with a zero exit code.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +73 to +75
values: dict[int, tuple[str, bool]] = {}
for index, node in enumerate(positional):
values[index] = (_text(node), False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Ignore argument comments

An inline Python comment is assigned a positional argument index. For f(a, # note\n b), the mapper consumes a and the comment, then reports b as a surplus argument instead of rewriting the otherwise unchanged call. This is non-blocking, but it prevents signature changes from updating valid commented call sites.

Artifacts

Evidence from the check

  • This authored script parses both call forms with the checkout's Tree-sitter dependencies and invokes `_split_call_arguments` and `_map_arguments`, demonstrating the mapping outcome.

Command output from the check

  • The executed baseline command maps `f(a, b)` with unchanged two-parameter specs to `['a', 'b']` and exits zero, establishing expected behavior.

Command output from the check

  • The executed commented-call command shows the comment in `named_children` and `positional`, then returns the surplus-argument reason with passing assertions, proving the false refusal.

View artifacts

T-Rex Ran code and verified through T-Rex

Comment on lines +32 to +35
def _text(node: Node | None) -> str:
if node is None or node.text is None:
return ""
return node.text.decode(cs.ENCODING_UTF8, errors="replace")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Name source helper clearly

The new _text helper extracts and UTF-8-decodes tree-sitter node source, but its name does not describe that behavior or return value. This violates the repository directive requiring function names to clearly describe what they return and do. Rename it clearly, and apply the same requirement to similarly generic new helpers such as _call_at and _compatible. This repository requirement must be satisfied before merging.

Rule Used: Use descriptive function names that clearly indicate what the function returns and its purpose. Prefer names like _get_grouping_key over _find_matching_pattern when the function's primary purpose is to return a value for a specific use case. (source)

Learned From
vitali87/code-graph-rag#221

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps

greptile-apps Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.

  • P1 Method positional-only marker is restored after a formerly keyword-capable parameter ▶

    • Bug
      • For def m(self, a, /, b), rewrite_definition with unchanged parameter specs emits def m(self, a, b, /). The syntactically valid rewritten method changes the public contract: b becomes positional-only, so C().m(1, b=2) fails with TypeError.
    • Cause
      • _positional_only_count counts leading AST parameters including the Python receiver (self), while specs is indexed against positional, which excludes receivers. At line 177, adding both receivers and the receiver-inclusive boundary shifts / one parameter too far right.
    • Fix
      • Compute the positional-only boundary in the same receiver-excluding coordinate system as specs (or subtract the receiver count before validating and inserting), then insert / after the preserved positional-only non-receiver parameters.
  • P2 Definition signature rewrite drops inline parameter comments ▶

    • Bug
      • A definition retaining both a and b changes def f(a, # explanation b): into def f(a, b):; the comment is deleted despite neither parameter being removed.
    • Cause
      • rewrite_definition() builds a fresh text parameter span and line 70 replaces the full original parameter span, which includes comments that are not present in the rendered parameter-node text.
    • Fix
      • Preserve comment tokens when reconstructing the parameter list, or avoid replacing the complete parameter span and instead apply narrower edits to individual parameter nodes/separators.
  • P2 Inline comments are counted as positional arguments during signature mapping ▶

    • Bug
      • For f(a, # note b), Tree-sitter returns named children identifier(a), comment(# note), and identifier(b). _split_call_arguments returns all three as positional. With unchanged two-parameter specs, mapping consumes a and the comment, leaves b, and returns the site passes more arguments than the definition declares. This is a false unmapped/surplus result, not a corrupt source rewrite.
    • Cause
      • _map_arguments enumerates every node returned in positional at codebase_rag/editing/signature_calls.py:73-75; _split_call_arguments does not exclude Python comment nodes before adding them to that list.
    • Fix
      • Filter non-argument trivia such as Python comment nodes from _split_call_arguments (or before positional indexing in _map_arguments), and add a regression test for f(a, # note b).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

There is a confirmed correctness bug in Python positional-only (/) restoration when receiver parameters exist, which can refuse valid rewrites or place the separator incorrectly.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

This PR introduces the core helper modules for a forthcoming change_signature edit operation, including parameter spec parsing, definition/call-site rewrite helpers, and supporting constants, plus a small extension to the postcondition contract to record whether heuristic resolutions were allowed.

Changes:

  • Add new change_signature data types and parsing utilities (parameter specs, reporting types).
  • Add signature rewrite helpers for definitions and call sites (argument remapping, positional-only restoration, literal/type helpers).
  • Extend contract + constants to support change_signature expectations and MCP/CLI wiring.
File Description
codebase_rag/​editing/​contract.py Adds heuristic_allowed support to change_signature_expectation.
codebase_rag/​editing/​signature_rewrite.py New helpers to rewrite definitions and call sites based on parameter specs.
codebase_rag/​editing/​signature_types.py New public types and CLI/MCP parameter spec parsing for change_signature.
codebase_rag/​editing/​signature_params.py New parameter extraction/rendering logic, including Python positional-only handling.
codebase_rag/​editing/​signature_calls.py New call-site argument mapping logic for change_signature.
codebase_rag/​editing/​signature_literals.py New literal/type-compatibility helpers for default validation.
codebase_rag/​constants/​mcp.py Adds MCP tool/param constants for change_signature.
codebase_rag/​constants/​graph.py Adds graph keys for unmapped/verdict reporting.
codebase_rag/​constants/​cli.py Adds user-facing CLI error/status messages for change_signature.
codebase_rag/​constants/​ast_python.py Adds tree-sitter node constants for Python call-site splats.
codebase_rag/​constants/​ast_nodes.py Adds tree-sitter node constant for spread_element.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +168 to +177
boundary = _positional_only_count(node)
if boundary is None:
return
receivers = sum(1 for p in old if p.receiver)
kept = specs[:boundary]
if len(specs) < boundary or any(
spec.from_index != index for index, spec in enumerate(kept)
):
raise SignatureRefused(cs.SIGNATURE_POSITIONAL_ONLY_MOVED)
rendered.insert(receivers + boundary, "/")
Comment on lines +89 to 98
def change_signature_expectation(
unmapped: Iterable[str], heuristic_allowed: bool = False
) -> Expectation:
return Expectation(
operation=cs.CONTRACT_OP_CHANGE_SIGNATURE,
unmapped=tuple(sorted(set(unmapped))),
# A parameter added with a default changes no caller count.
caller_count_unchanged=True,
heuristic_allowed=heuristic_allowed,
)
Comment on lines +73 to +78
def rewrite_site(
patcher: Patcher,
row: graph_query.CallSiteRow | ResultRow,
owner_qn: str,
old_names: list[str],
specs: list[ParamSpec],
@vitali87

Copy link
Copy Markdown
Owner Author

Evidence that retargeting this PR to main is mechanical and safe, for whoever action it — I am not running gh pr edit --base here, since that command was denied in the session that owns this branch.

This PR targets feat/postcondition-contract. That branch's own PR, #1547, is already merged, but the branch still exists on the remote at 25ff67cf, so this PR is still computing its diff against it rather than against main.

The check that matters:

git merge-base --is-ancestor 25ff67cf37f973465d1d1b96c0d271353859095d origin/main  ->  true

The base branch tip is fully contained in main. So retargeting changes nothing about this PR's diff, its review surface, or what a bot would see — every commit in the current base is already an ancestor of main. It only stops the PR pointing at a merged branch.

Two consequences worth noting:

  • Any review artifact currently anchored to this head stays valid across the retarget, because the diff does not move. No re-review is needed on account of the base change alone.
  • mergeStateStatus is UNSTABLE at head 7126da08, which is a separate matter from the base and will need its own look.

@vitali87
vitali87 removed this pull request from stack #2055 September 22, 2026 22:08
@vitali87
vitali87 changed the base branch from feat/postcondition-contract to main September 22, 2026 22:09
@vitali87
vitali87 added this pull request to stack #2133 September 22, 2026 22:09
@vitali87

Copy link
Copy Markdown
Owner Author

Superseded: change_signature lands through the split of #1912, which replaces #1548, as the stack #2163 → #2161 → #2162 → #2166 → #2164 → #2169 → #2168 → #2165 → #2167 on main. Closing this split-pr1548 stack at the owner's direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants