Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
9a759e5
Rename LoweringStage into LoweringState, move to spy/ast.py
antocuni Aug 9, 2026
c73e3f3
Make it possible to specify in which state each AST node is valid by
antocuni Aug 9, 2026
f922ce8
some notes for when/how to write tests
antocuni Aug 9, 2026
6366d5c
add valid_state annotation to many AST nodes
antocuni Aug 9, 2026
fe3d40b
WIP: introduce the astcompile pass.
antocuni Aug 9, 2026
eafe451
s/lowering_state/lostate/g
antocuni Aug 9, 2026
6aa2050
add Module.lostate and FuncDef.lostate
antocuni Aug 9, 2026
b25b203
add sanity checks for the expected lostate for modules and functions
antocuni Aug 9, 2026
d8b37fd
rename LoweringState into LoweringStage AGAIN
antocuni Aug 9, 2026
bb5b84b
make TestBasic::test_NameError[interp] passing
antocuni Aug 9, 2026
fac4b63
forgot to commit these two files
antocuni Aug 9, 2026
075387c
TestBasic::test_binop_right_nested_same_precedence[interp] passes
antocuni Aug 9, 2026
63d3a6b
missing nodes
antocuni Aug 9, 2026
9c1d80d
TestBasic::test_local_variables[interp] passes
antocuni Aug 9, 2026
6817563
move the specialize_Assign/AssignExpr logic from ASTFrame to ASTCompi…
antocuni Aug 9, 2026
f9f7078
TestBasic::test_assignexpr_argument[interp] passes
antocuni Aug 9, 2026
865314b
astcompile support for NameOuterDirect and AssignOuterDirect. TestBas…
antocuni Aug 9, 2026
542cebe
add support for NameLocalCell, the last missing one; TestBasic::test_…
antocuni Aug 9, 2026
b385c9c
detect and handle assign-to-const errors. TestBasic::test_cannot_assi…
antocuni Aug 9, 2026
d64d809
TestBasic::test_int_float[interp] passes
antocuni Aug 9, 2026
fe1a287
TestBasic::test_BinOp_error[interp] passes
antocuni Aug 9, 2026
1c7ac1c
TestBasic::test_if_while_assert_cond_type_mismatch[interp] passes
antocuni Aug 9, 2026
79dcdf5
TestBasic::test_getitem_error_1[interp] passes
antocuni Aug 9, 2026
fc29eab
TestBasic::test_aug_assign[interp] passes
antocuni Aug 9, 2026
40e0ab8
TestBasic::test_redshift_nonglobal_function[interp] passes
antocuni Aug 9, 2026
d4888b4
TestBasic::test_setattr_error[interp] passes
antocuni Aug 9, 2026
2a7e4ab
TestBasic::test_automatic_forward_declaration[interp] passes, and fix…
antocuni Aug 9, 2026
e2c0a0a
TestBasic::test_forward_declaration_in_funcdef[interp] passes
antocuni Aug 9, 2026
b052dfa
TestBasic::test_call_module_attr[interp] passes
antocuni Aug 9, 2026
62346ab
TestBasic::test_decorator[interp]
antocuni Aug 9, 2026
e93d237
WIP: compile the For stmt, but we cannot run the test because 'range'…
antocuni Aug 9, 2026
013b90d
break and continue
antocuni Aug 9, 2026
a4d62df
no longer needed
antocuni Aug 9, 2026
3e79572
TestBasic::test_type_name_attributes[interp]
antocuni Aug 10, 2026
f4b2734
test_exception.py::TestException::test_raise[interp]
antocuni Aug 10, 2026
241f27a
test_itemop.py::TestItemop::test_setitem[interp]
antocuni Aug 10, 2026
c25baba
add support for AssignUnpack and enough to make TestTuple::test_liter…
antocuni Aug 10, 2026
1947fea
fix; TestBlueGeneric::test_generic_class[interp] passes
antocuni Aug 10, 2026
0d01f8d
fix print
antocuni Aug 10, 2026
280e83d
fix BlockExpr and test_linearize
antocuni Aug 10, 2026
ba3453f
these now pass
antocuni Aug 10, 2026
b9cb8d3
skip spdb tests for now
antocuni Aug 10, 2026
d8d733f
run astcompile on the synthetized struct functions
antocuni Aug 10, 2026
1195594
we need a slightly different mechanism to catch unsupported stmts in …
antocuni Aug 10, 2026
7ca1a48
rename. All interp tests pass 🎉
antocuni Aug 10, 2026
2750933
Shuffle tests around: we want "plain" assign tests to come earlier
antocuni Aug 10, 2026
c6eb2a7
start to fix doppler: AssignLocal
antocuni Aug 10, 2026
2d24d4e
implement reading/writing of Cells in the doppler. See the comments. …
antocuni Aug 10, 2026
2b3d5f4
implement this in doppler
antocuni Aug 10, 2026
eba1754
shuffle nodes around
antocuni Aug 10, 2026
2444be4
WIP: break everything again, try to reduce the code duplication betwe…
antocuni Aug 10, 2026
f9f4bd2
WIP: AssignCell
antocuni Aug 10, 2026
60e0814
unify AssignConstError with it's Expr equivalent
antocuni Aug 10, 2026
34ec05e
finally kill compile_assign_common
antocuni Aug 10, 2026
6f88e5a
fix
antocuni Aug 10, 2026
458e35b
fix AssignUnpack
antocuni Aug 10, 2026
c96cd58
fix the spy backend after the Assign refactoring
antocuni Aug 10, 2026
8f46f2d
fix test_aug_assign
antocuni Aug 10, 2026
346dd08
fix doppler for the new Assign/AssignExpr state; test_assignexpr_* no…
antocuni Aug 10, 2026
82413f5
WIP: fix force_inline
antocuni Aug 10, 2026
d4dbc29
add a .sym to AssignLocalExpr, kill the ugly _set_assignexpr_color, f…
antocuni Aug 10, 2026
4aca5f0
fix
antocuni Aug 10, 2026
b44c4bb
add a sanity check and catch a mistake in w_print; test_builtins now …
antocuni Aug 10, 2026
4573ba6
fix doppler for UnpackAssign and remove leftover
antocuni Aug 10, 2026
74e3134
start to fix the C backend
antocuni Aug 10, 2026
2aa4eac
start to fix test_basic.py -m C
antocuni Aug 10, 2026
f0dcf78
more
antocuni Aug 10, 2026
98e9b42
more
antocuni Aug 10, 2026
950e999
fix AssignUnpack in linearize and C
antocuni Aug 10, 2026
9a34e97
fix
antocuni Aug 10, 2026
deac0c5
fix test_function
antocuni Aug 10, 2026
c74cc0a
astcompile default arguments
antocuni Aug 10, 2026
cc45f6d
improve this diagram
antocuni Aug 10, 2026
f43c1f0
fix spy colorize: after astcompile we have a different AST, and thank…
antocuni Aug 10, 2026
fc3ac2c
fix the HTML backend tests
antocuni Aug 10, 2026
2ef62cf
implement spy astcompile --format spy
antocuni Aug 10, 2026
1c03c09
kill a bunch of leftovers
antocuni Aug 10, 2026
79ebfe2
implement spy astcompile -f html
antocuni Aug 10, 2026
7686b27
fix SPdb
antocuni Aug 10, 2026
4c8c3d8
kill
antocuni Aug 10, 2026
97255c9
fix spy redshift -x --linearize
antocuni Aug 10, 2026
025fb95
fix mypy
antocuni Aug 10, 2026
a4a9915
simplify
antocuni Aug 10, 2026
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: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,22 @@ spy -O 1 -g your_file.spy # With optimization and debug symbols
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.
31 changes: 18 additions & 13 deletions spy/analyze/importing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from spy import ast
from spy.analyze.scope import ScopeAnalyzer
from spy.astcompile import astcompile
from spy.errors import SPyError
from spy.fqn import FQN
from spy.parser import Parser
Expand All @@ -22,7 +23,7 @@
MODULE = Union[ast.Module, "W_Module", None]

# Cache version: increment this when ast.Module or SymTable structure changes
SPYC_VERSION = 7
SPYC_VERSION = 8


@dataclass
Expand Down Expand Up @@ -219,7 +220,7 @@ def _save_spyc(self, mod: ast.Module, spyc: py.path.local) -> None:
if not self.vm.robust_import_caching:
raise

def parse_all(self) -> None:
def astcompile_all(self) -> None:
while self.queue:
modname = self.queue.popleft()

Expand All @@ -239,7 +240,7 @@ def parse_all(self) -> None:
if modname not in self.deps:
self.deps[modname] = OrderedSet()

mod = self.parse_one(modname, spyfile)
mod = self.astcompile_one(modname, spyfile)
self.mods[modname] = mod

# record implicit imports
Expand All @@ -256,9 +257,9 @@ def parse_all(self) -> None:
# we couldn't find .spy for this modname
self.mods[modname] = None

def parse_one(self, modname: str, spyfile: py.path.local) -> ast.Module:
def astcompile_one(self, modname: str, spyfile: py.path.local) -> ast.Module:
"""
Parse a module AND run ScopeAnalyzer on it.
Parse a module, run ScopeAnalyzer, run astcompile.
"""
# try to load from cache first
mod = None
Expand All @@ -270,14 +271,18 @@ def parse_one(self, modname: str, spyfile: py.path.local) -> ast.Module:
return mod

# no cache found, parse it
parser = Parser.from_filename(str(spyfile))
mod = parser.parse()
scopes = self.analyze_one(modname, mod)
mod.symtable = scopes.by_module()
parsed_mod = self.parse_one(spyfile)
scopes = self.analyze_one(modname, parsed_mod)
parsed_mod.symtable = scopes.by_module()
compiled_mod = astcompile(parsed_mod)

if self.use_spyc:
self._save_spyc(mod, spyc)
return mod
self._save_spyc(compiled_mod, spyc)
return compiled_mod

def parse_one(self, spyfile: py.path.local) -> ast.Module:
parser = Parser.from_filename(str(spyfile))
return parser.parse()

def analyze_one(self, modname: str, mod: ast.Module) -> ScopeAnalyzer:
scopes = ScopeAnalyzer(modname, mod)
Expand Down Expand Up @@ -316,7 +321,7 @@ def visit(modname: str) -> None:
def import_all(self) -> None:
from spy.vm.module import W_Module

assert self.mods, "call .parse_all() first"
assert self.mods, "call .astcompile_all() first"
import_list = self.get_import_list()
for modname in import_list:
mod = self.mods[modname]
Expand Down Expand Up @@ -435,7 +440,7 @@ def pp_tree(self) -> None:
├── b1
└── b2
"""
assert self.mods, "call .parse_all() first"
assert self.mods, "call .astcompile_all() first"

# Constants for tree formatting
# fmt: off
Expand Down
Loading
Loading