Skip to main content

nappe

A syntax-guided test case reducer implementing the Perses algorithm (Sun et al., ICSE 2018).

Reference implementation: nnunley/bonsai (Rust).

What It Does

Given a failing test case and a program file that triggers a bug, nappe reduces the file to the smallest possible program that still reproduces the failure — while guaranteeing syntactic validity at every step.

It also provides a check command for static analysis of source files, detecting reducible patterns like dead code, unused assignments, and style issues.

How It Works

  1. Parse the input file into a concrete syntax tree using tree-sitter
  2. Systematically try to remove or simplify subtrees via a priority queue (largest first)
  3. Each candidate reduction is validated by reparsing — only syntactically valid reductions proceed
  4. Test each valid candidate against an "interestingness test" (any shell command that exits 0 when the bug is still present)
  5. Repeat until no further reductions are possible

Transforms

  • Delete — remove a node entirely
  • Unwrap — replace a node with one of its type-compatible children
  • Unify identifiers — rename bindings to canonical short forms (planned: requires scope data)
  • Dead definition removal — delete unreferenced definitions (planned: requires scope data)

Key Properties

  • All intermediate results are syntactically valid
  • Largest subtrees tried first (maximum reduction per test)
  • Test results cached (duplicate calls avoided)
  • Handles inputs with pre-existing parse errors

Supported Languages

Python, JavaScript, TypeScript, Rust, Go, C, C++ (via tree-sitter grammars).

Implementation

The project has two implementations:

  • Rust (primary) — native implementation in src/, built with cargo. This is the default and recommended implementation.
  • Python (reference) — pure Python implementation in src/nappe/, kept for comparison and as a reference for the algorithm. Accessible via the --legacy flag.

Why Rust?

The Rust implementation provides significantly better performance for large files and complex reduction scenarios, while maintaining full feature parity with the Python reference.

Installation

cargo build --release
cp target/release/nappe ~/.local/bin/

Python (reference)

uv sync

Usage

nappe reduce — Syntax-guided reduction (default)

# Reduce using a pytest interestingness test (recommended)
nappe reduce --test test_interesting.py::test_still_fails input.py

# Reduce using a shell command
nappe reduce --test-cmd "grep -q 'error'" input.py

# Auto-reduce to smallest valid program (no test needed)
nappe reduce --auto input.py

# Limit reduction time
nappe reduce --test test_interesting.py --max-time 30m --max-tests 1000 input.py

# Use Python implementation (legacy)
nappe --legacy reduce --test test_interesting.py::test_still_fails input.py

nappe check — Static analysis and fixes

# Check files for reducible patterns
nappe check src/**/*.py

# Apply safe fixes automatically
nappe check --fix src/**/*.py

# Apply all fixes (including unsafe ones like dead code removal)
nappe check --unsafe-fixes src/**/*.py

# Output as JSON
nappe check --output-format json src/**/*.py

# Filter by rule
nappe check --select RED200,RED201 src/**/*.py

nappe shrink — Shrinkray-compatible interface

nappe shrink "./check.sh" input.py

Check Rules

Code Description Safe?
RED100 Dead function (no callers) unsafe
RED101 Dead class (no instantiations) unsafe
RED102 Unused variable assignment unsafe
RED200 Constant expression simplification safe
RED201 Redundant parentheses safe
RED202 Unnecessary semicolon safe
RED203 Trailing whitespace safe
RED204 Redundant newline safe

Example: Reducing a Bug Trigger

Given a Python file that triggers a bug when fibonacci is called with print():

Before (input.py):

import sys

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

def unused_helper(x):
    return x * 2

def also_unused():
    result = unused_helper(5)
    return result

class MyClass:
    def __init__(self):
        self.value = 42

    def method(self):
        return self.value

if __name__ == "__main__":
    for i in range(10):
        print(fibonacci(i))

Write a pytest interestingness test (test_interesting.py):

import sys

def test_still_fails():
    """Exit 0 = candidate is still interesting (bug still present)."""
    candidate = sys.argv[1]
    content = open(candidate).read()
    assert "def fibonacci" in content and "print(" in content

Run the reducer:

nappe reduce --test test_interesting.py::test_still_fails input.py

After (input.py):

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))

Diff:

-import sys
-
 def fibonacci(n):
     if n <= 1:
         return n
     return fibonacci(n - 1) + fibonacci(n - 2)

-def unused_helper(x):
-    return x * 2
-
-def also_unused():
-    result = unused_helper(5)
-    return result
-
-class MyClass:
-    def __init__(self):
-        self.value = 42
-
-    def method(self):
-        return self.value
-
-if __name__ == "__main__":
-    for i in range(10):
-        print(fibonacci(i))
+print(fibonacci(10))

The reducer removed the unused import sys, the unused_helper and also_unused functions, the MyClass class, and simplified the if __name__ block — while preserving the core fibonacci function and the print() call that triggers the bug.

Example: Checking and Fixing Code

Input (demo.py):

import os

def greet(name):
    print(f"Hello, {name}")

def unused_helper(x):
    return x * 2

count = 0;;

class EmptyClass:
    pass

message = "hello"   

greet("world")

Run the checker:

nappe check demo.py

Output:

demo.py:14:17: RED203 Trailing whitespace
    14 | message = "hello"   
      ^^^^^^^^^^^^^^^^^ RED203

demo.py:9:10: RED202 Unnecessary semicolon
    9 | count = 0;;
     ^^^^^^^^^^^ RED202

demo.py:14:1: RED102 Unused variable assignment
    14 | message = "hello"   
      ^^^^^^^^^^^^^^^^^ RED102

demo.py:9:1: RED102 Unused variable assignment
    9 | count = 0;;
     ^^^^^^^^^^^ RED102

demo.py:6:1: RED100 Dead function (no callers)
    6 | def unused_helper(x):
     ^^^^^^^^^^^^^^^^^^^^^ RED100

demo.py:11:1: RED101 Dead class (no instantiations)
    11 | class EmptyClass:
      ^^^^^^^^^^^^^^^^^ RED101

Found 6 issues (2 safe, 4 unsafe).

Apply safe fixes:

nappe check --fix demo.py

After (demo.py):

import os

def greet(name):
    print(f"Hello, {name}")

def unused_helper(x):
    return x * 2

count = 0;

class EmptyClass:
    pass

message = "hello"

greet("world")

The --fix flag applies only safe fixes (RED200–RED204). Use --unsafe-fixes to also remove dead functions, dead classes, and unused assignments.

Development

Rust (primary)

cargo build --release      # build
cargo test                 # test
cargo clippy               # lint
cargo fmt                  # format

Python (reference)

uv run ruff check src/nappe/     # lint
uv run ruff format src/nappe/    # format
uv run ty check src/nappe/       # type check
uv run pytest tests/                    # test

License

MIT

References

Metadata

Release files for nappe 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nappe 0.1.0
File Size Uploaded Provenance Actions
nappe-0.1.0.tar.gz 19.0 kB With provenance Details for nappe-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for nappe 0.1.0
File Interpreter ABI Platform Actions
nappe-0.1.0-py3-none-any.whl Python 3 none any Details for nappe-0.1.0-py3-none-any.whl

Total release size: 43.9 kB

Release files / nappe-0.1.0.tar.gz

Download URL nappe-0.1.0.tar.gz
Size 19.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5d6dbf71ebf5f369b3ee00b86a7319f2f8f1b0c2943a2231301dc7fe69663157
BLAKE2b-256 checksum
How to use checksums
810894bb23700bbc076c13c242cf4dc71b3b5b23e498ef7d195a87b231bc5ebd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / nappe-0.1.0-py3-none-any.whl

Download URL nappe-0.1.0-py3-none-any.whl
Size 24.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a868bad565448381ddd9ba95d4fc5a7ef9a1f06f33575fdd9e2e0632bc5ead9b
BLAKE2b-256 checksum
How to use checksums
afd4d05eb3c8ec90fbe9efc5ff7fd3f2d3b81cb50d388a6e214d89358bf462d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Security for nappe 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for nappe-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page