Semantic Refactoring with zero graph patch
This guide shows how to use zero graph patch for semantic refactoring: renaming functions, changing string literals, updating type fields, and bulk-editing programs. Unlike text-based search-and-replace, graph patches are checked by the compiler before they are applied.
Prerequisites
- Zerolang installed (
zero --version) - A
.0source file to work with
Create a Sample Program
Create refactor-demo.0:
fn greet(name: String) -> Void raises {
check world.out.write("Hello, " + name + "!\n")
}
pub fn main(world: World) -> Void raises {
greet("Alice")
greet("Bob")
}
Read the Graph
First, inspect the graph to find the node IDs you want to edit:
zero graph dump --json refactor-demo.0
The output includes all nodes with their IDs, kinds, and values. Look for the node you want to change — for example, the function name greet or a string literal "Hello, ".
Key fields to look for:
id— the node identifier (e.g.,#decl_abc123)kind— what kind of node it is (Function,Literal,Call, etc.)name— the declared namevalue— the literal value (for string/number literals)hash— the graph hash for--expect-graph-hash
Rename a Function
To rename greet to sayHello:
zero graph patch refactor-demo.0 \
--expect-graph-hash graph:YOUR_HASH \
--op 'rename node="#YOUR_GREET_DECL_ID" expect="greet" value="sayHello"'
The rename operation updates the function declaration name and all call sites. The compiler checks that:
- The node exists
- The current name matches
expect(if provided) - The new name is a valid identifier
- No naming conflicts are introduced
After the patch, the source is rewritten:
fn sayHello(name: String) -> Void raises {
check world.out.write("Hello, " + name + "!\n")
}
pub fn main(world: World) -> Void raises {
sayHello("Alice")
sayHello("Bob")
}
Change a String Literal
To change the greeting format:
zero graph patch refactor-demo.0 \
--expect-graph-hash graph:YOUR_HASH \
--op 'set node="#YOUR_LITERAL_ID" field="value" expect="Hello, " value="Hi, "'
The set operation updates a single field. The expect parameter is optional but recommended — it rejects the operation if the current value differs from what you expected.
Insert a New Node
To add a new function argument or expression:
zero graph patch refactor-demo.0 \
--expect-graph-hash graph:YOUR_HASH \
--op 'insert node="#patch001" kind="Literal" parent="#YOUR_CALL_ID" edge="arg" order="2" type="String" value="!\n"'
The insert operation creates a new node and connects it to a parent node with an ordered edge. This is useful for adding arguments, conditions, or new statements.
Delete a Node
To remove a node and its subtree:
zero graph patch refactor-demo.0 \
--expect-graph-hash graph:YOUR_HASH \
--op 'delete node="#patch001"'
The delete operation removes an owned subtree. If other nodes reference the deleted subtree, the operation is rejected.
Bulk Edits with Patch Files
For multiple edits, use a patch file:
Create refactor.patch:
zero-program-graph-patch v1
expect graphHash "graph:YOUR_HASH"
rename node="#YOUR_GREET_ID" expect="greet" value="sayHello"
set node="#YOUR_LITERAL_ID" field="value" expect="Hello, " value="Hi, "
Apply it:
zero graph patch refactor-demo.0 --patch-file refactor.patch
Or pass the patch text inline:
zero graph patch refactor-demo.0 \
--expect-graph-hash graph:YOUR_HASH \
--patch-text 'zero-program-graph-patch v1
expect graphHash "graph:YOUR_HASH"
rename node="#YOUR_GREET_ID" expect="greet" value="sayHello"'
Safety Mechanisms
Graph Hash Preconditions
Every patch should include --expect-graph-hash. This prevents:
- Editing a program that has changed since the agent last read it
- Race conditions in multi-agent workflows
- Applying patches to the wrong version of a file
If the hash does not match, the patch fails with a clear error.
Field Value Preconditions
The expect parameter in set and rename operations checks the current value before applying the change. This prevents:
- Overwriting a value that was already changed
- Renaming something that was already renamed
- Applying a patch meant for a different state
Type Checking
After applying patches, the compiler re-parses and re-typechecks the source. If a patch introduces a type error, the patch is rejected and the source is unchanged.
Agent Workflow
A typical agent refactoring workflow:
1. zero graph dump --json file.0
→ Read graphHash, find node IDs
2. zero graph patch file.0 \
--expect-graph-hash graph:HASH \
--op 'rename node="#ID" expect="old" value="new"'
→ Apply the edit
3. zero check --json file.0
→ Verify the edit is valid
4. If ok: continue. If not: read diagnostics, fix, retry.
Common Patterns
Rename a Variable
--op 'rename node="#BINDING_ID" expect="oldName" value="newName"'
Change a Type
--op 'set node="#BINDING_ID" field="type" expect="i32" value="i64"'
Make a Binding Mutable
--op 'set node="#BINDING_ID" field="mutable" expect="false" value="true"'
Change a Function's Return Type
--op 'set node="#FUNC_ID" field="type" expect="i32" value="i64"'
Toggle Public Visibility
--op 'set node="#DECL_ID" field="public" expect="false" value="true"'
Limitations
- Graph patches work on canonical
.0source without comments. If your source has comments, they may be lost during rewrite. - The
setoperation can only update scalar fields (name,type,value,public,mutable,static,fallible,exportC). - Complex structural changes (adding new functions, reordering statements) may require multiple
insert/deleteoperations. - Boolean fields (
public,mutable,fallible) accept onlytrueorfalse.
Further Reading
- ProgramGraph Reference — complete graph structure documentation
- CLI Reference — all
zero graphcommands - Let Agent Edit Your Code — the full agent editing loop