Skip to content

Latest commit

 

History

History
141 lines (113 loc) · 4.58 KB

File metadata and controls

141 lines (113 loc) · 4.58 KB

SPy Language - Dev Reference

General behavior of claude code

  • when asked to write a test, write just the test without trying to fix it

How to write comments

Avoid writing useless comments: if you need to write a comment, explain WHY the code does something instead of WHAT it does.

Examples of USELESS comments (they just describe what the code obviously does):

# Convert to py.path.local
path = py.path.local(str(path_item))

# Loop through all files
for file in files:
    ...

# Check if path is a directory
if path.check(dir=True):
    ...

Examples of USEFUL comments (they explain WHY or provide context):

This is USEFUL because it's not immediately obvious that capture_output=True influencese also stderr:

# Use capture_output=True to capture stdout and stderr separately
proc = subprocess.run(cmdline_s, capture_output=True)

This is USEFUL because it explains why decided to apply or not apply the conversion:

if w_typeconv is None:
    # no conversion needed, hooray
    return wam
elif self.redshifting:
    # we are performing redshifting: the conversion will be handlded
    # by FuncDoppler
    return wam
else:
    # apply the conversion immediately
    w_val = self.vm.fast_call(w_typeconv, [wam.w_val])

This is USEFUL because by just looking at exec_stmt it's not immediately obvious that we are talking about method definitions:

# execute method definitions
for stmt in self.classdef.body:
    self.exec_stmt(stmt)

When in doubt: if the comment can be removed and the code is still clear, remove it.

Imports

  • Organize imports by standard Python conventions
  • Prefer specific imports: from spy.errors import SPyError
  • Prefer module-level imports, unless there is a good reason to put them inside functions

For example:

def foo():
    # don't do this: put "import time" at the top level"
    import time

Common Commands

  • When running tests, always use the venv: e.g. `./venv/bin/pytest'
  • Run all tests: pytest
  • Run single test: pytest spy/tests/path/to/test_file.py::TestClass::test_function
  • Run backend-specific tests: pytest -m interp or -m C or -m doppler
  • Type checking: mypy

Compile SPy Code

spy your_file.spy                 # Execute (default)
spy -C your_file.spy              # Generate C code
spy -c your_file.spy              # Compile to executable
spy -O 1 -g your_file.spy         # With optimization and debug symbols

Code Style Guidelines

  • Use strict typing (mypy enforced)

  • Classes: PascalCase (CompilerTest)

  • Functions/methods: snake_case (compile_module())

  • Constants: SCREAMING_SNAKE_CASE (ALL_BACKENDS)

  • Tests inherit from CompilerTest base class

  • Use backend-specific decorators for test filtering (@only_interp, @skip_backends)

  • prefer using py.path.local over pathlib.Path. The only exception is cli.py, because typer has special logic to handle Path objects.

  • when using triple-quoted docstrings, always put the text in its own line. E.g.:

    def foo():
        """
        docstring here
        """
    
  • when writing tests, do NOT put a docstring to explain what the test does, especially if the test name is already explicit. E.g.:

    # this is BAD: the docstring is redundant
    def test_cache_preserves_symtable(self):
        """Test that symtable is preserved in cache"""
        ...
    
    # this is GOOD:
    def test_cache_preserves_symtable(self):
        ...
    
  • when writing tests, don't add a new class to group new tests in an existing file, unless it's necessary. Prefer adding tests to the existing class. The general rule is one Test* class per test_*.py file.

When to write unit tests

A unit test is worth writing only if the complexity of the thing being tested is high enough to justify it.

A test that just mirrors a declaration or configuration is a change-detector, not a safety net. If updating the code always means updating the test in lockstep, the test adds no value — it only adds maintenance burden. Tests should verify behavior that emerges from the interaction of multiple pieces, not re-state what a single declaration says.

If the logic is trivial AND it is exercised downstream by other tests (e.g. integration/compiler tests), it is fine to rely on those instead of writing dedicated unit tests. For example: if you put the wrong @astnode("...") spec on a node class, the compiler tests will catch it. A unit test that just asserts SomeNode._valid_states == frozenset({"parsed"}) adds nothing.

GH PR Guidelines

  • When creating a PR, describe what you did, but don't include the "test plan" section.