Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
7890e9b
autoprobe
disconcision Dec 22, 2025
0ccc908
Restore full probe features for continued development
disconcision Jan 14, 2026
b4d4f5b
Remove right-click debug print_endline statements
disconcision Jan 14, 2026
efee531
Add ~single_line parameter to view_seg for multiline fold display
disconcision Jan 14, 2026
7b6220d
Merge branch 'probemoar' into probe-III
disconcision Jan 15, 2026
f91253b
Merge branch 'dev' of github.com:hazelgrove/hazel into probe-III
disconcision Jan 17, 2026
7ab35ae
fmt
disconcision Jan 17, 2026
5c1589b
Add hover tooltips to sample legend
disconcision Jan 20, 2026
be07d97
Render sample context menu vertically when environment is empty
disconcision Jan 20, 2026
902e0aa
Merge dev into probemoar-autoprobe, fix auto-probe data flow
disconcision Jan 20, 2026
7632a96
Fix auto-probe disappearing on whitespace/comments
disconcision Jan 20, 2026
ce5caa4
Auto-probe for test forms and let delimiters/patterns
disconcision Jan 20, 2026
4082984
Fix auto-probe for top-level let delimiters and whitespace
disconcision Jan 20, 2026
df024c8
Merge probemoar-autoprobe (auto-probe fixes) into probe-III
disconcision Jan 20, 2026
57a6918
auto mode for autoprobe integrated into flow. disable context menu au…
disconcision Jan 20, 2026
984f074
auto-update sample cursor when adding probes
disconcision Jan 20, 2026
bb290a6
fmt
disconcision Jan 20, 2026
959e0a8
Misc improvements: TyDi, AutoProbe, Elaborator, Abbreviate
disconcision Jan 21, 2026
a01e7e8
Sort-aware mold selection during insertion
disconcision Jan 21, 2026
e924554
Sort-specific expansion for delimiters
disconcision Jan 21, 2026
f62e55f
unwind now unnecessary special get in mold getter around multi-delimi…
disconcision Jan 21, 2026
745ee4d
rm unused
disconcision Jan 21, 2026
2793d6a
Fix auto-probe mode sample cursor not updating on cursor move
disconcision Jan 21, 2026
8d2fe9d
Merge branch 'dev' into probe-III
disconcision Jan 29, 2026
de8bcd3
Merge branch 'probe-III' into sorted-insertion
disconcision Jan 29, 2026
ddb267d
merge fix
disconcision Jan 29, 2026
07fa27d
merge fix
disconcision Jan 29, 2026
2ce5101
Merge branch 'dev' into probe-III
disconcision Feb 2, 2026
24a80ef
Merge branch 'probe-III' into sorted-insertion
disconcision Feb 2, 2026
b400423
Add closure cursor bar for call stack breadcrumbs
disconcision Feb 3, 2026
adfbf4c
Fix probe sample click failing with nth exception
disconcision Feb 3, 2026
c3f5a1d
Merge branch 'probe-III' of github.com:hazelgrove/hazel into probe-III
disconcision Feb 3, 2026
79048c9
Add dual click targets and styling improvements to closure cursor bar
disconcision Feb 3, 2026
2bd575e
Add function names to call_stack and wire up breadcrumb navigation
disconcision Feb 3, 2026
bf70a80
Add color coding and double chevron indicator to closure cursor bar
disconcision Feb 3, 2026
e2a3c65
Disable double chevron indicator for now
disconcision Feb 3, 2026
4909753
Add pin icon to closure cursor bar
disconcision Feb 3, 2026
8b1a217
Clean up pin icon styling - use white SVG directly
disconcision Feb 3, 2026
472fcb7
Minor tweaks and add deferred ideas documentation
disconcision Feb 3, 2026
0100763
Fix pin filtering to compare call stacks by ID only
disconcision Feb 3, 2026
c34b6ce
Add keyboard navigation and indicated highlighting to closure cursor bar
disconcision Feb 3, 2026
911279e
Add underline to focused lambda and update keyboard nav docs
disconcision Feb 3, 2026
57e59c2
Add note about separator click / sample indication sync issue
disconcision Feb 3, 2026
848f6b0
accidentally a
disconcision Feb 3, 2026
8905741
Merge branch 'dev' into probe-III
disconcision Feb 3, 2026
8afcec9
Merge branch 'probe-III' into sorted-insertion
disconcision Feb 3, 2026
1c4ecc4
plan move
disconcision Feb 3, 2026
0ce4847
Add argument value capture for probes on function applications
disconcision Feb 3, 2026
eaf747b
Merge probe-fixes into probe-III
disconcision Feb 4, 2026
e3359f6
Merge branch 'probe-fixes' into probe-III
disconcision Feb 4, 2026
74ee257
Merge branch 'probe-III' into sorted-insertion
disconcision Feb 4, 2026
336425b
rm plans
disconcision Feb 4, 2026
adc9feb
Merge branch 'dev' of github.com:hazelgrove/hazel into sorted-insertion
disconcision Feb 4, 2026
e6cea6b
Merge branch 'sorted-insertion' of github.com:hazelgrove/hazel into s…
disconcision Feb 4, 2026
7775a2a
Merge branch 'dev' into probe-III
disconcision Feb 5, 2026
a240860
Merge branch 'probe-III' into sorted-insertion
disconcision Feb 5, 2026
a1152cd
Add module implementation plan and session tracking
disconcision Feb 5, 2026
3b05bb8
Add module system foundation: Mod sort, forms, and parsing
disconcision Feb 5, 2026
cf0cdda
Add Mod→Exp expansion fallback and update documentation
disconcision Feb 5, 2026
45141f2
Update plan with detailed testing and implementation phases
disconcision Feb 5, 2026
2953971
Add module semantics and Menhir parser support
disconcision Feb 5, 2026
0ff518b
Add module system tests
disconcision Feb 5, 2026
c50e47d
Update plan with Phase 1 completion status
disconcision Feb 5, 2026
4c985fb
Module system Phase 1: bug fixes and documentation
disconcision Feb 5, 2026
dd3d8e3
Document ID preservation solution for cursor inspector
disconcision Feb 5, 2026
cb4ca5b
Module cursor inspector: ID preservation and semicolon absorption
disconcision Feb 5, 2026
3a8935d
Module semicolon ID handling: partial fix and documentation
disconcision Feb 5, 2026
b24e8ae
Make ModSeq semicolons chainable in Skel for flat n-ary structure
disconcision Feb 6, 2026
c8a9aa0
Complete semicolon ID collection and plan updates
disconcision Feb 6, 2026
5681e56
Phase 1.5A: Sig sort concrete syntax, testing, and bug fixes
disconcision Feb 7, 2026
55b1f45
fmt
disconcision Feb 7, 2026
1dd1fea
Fix module width checking and empty sig parsing
disconcision Feb 7, 2026
7a7a5e3
Add empty sig Menhir round-trip test
disconcision Feb 7, 2026
909d476
fmt
disconcision Feb 7, 2026
ac07f84
fix: resolve merge conflicts
disconcision Feb 10, 2026
33ae5d1
Sort-specific grout precedence for module editing stability
disconcision Feb 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
35 changes: 35 additions & 0 deletions claude.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Claude Worktree Info

## Quick Reference

**Worktree path**:
```
/Users/andrewblinn/.claude-worktrees/hazel/sorted-insertion-modules
```

**Browser URL**: http://localhost:8002

**To open in editor**:
```bash
code ~/.claude-worktrees/hazel/sorted-insertion-modules
# or
cursor ~/.claude-worktrees/hazel/sorted-insertion-modules
```

---

## Current Session

**Branch**: `sorted-insertion-modules` (based on `sorted-insertion`)

**Task**: Phase 1.1 - Module Syntax Foundation

**Status**: Starting

---

## Why This Setup Works

- **Separate `_build/` directories**: Each worktree has its own `_build/`, so Dune's lock files don't conflict.
- **Separate ports**: You use `make serve` (port 8000), I use port 8002.
- **Independent work**: Git worktrees share the same repo but have separate working directories.
203 changes: 203 additions & 0 deletions docs/modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Hazel Module System

## Overview

Hazel's module system provides ML-style module syntax as a **syntactic gloss over labeled tuples**. Modules use curly-brace syntax with `let` and `type` declarations, but are semantically equivalent to nested definitions producing a labeled tuple value. Module type annotations use **signature syntax** (`Sig` sort) in type position.

## User-Facing Syntax

### Module Expressions

```
{ let x = 1; let y = true } -- basic module
{ type T = Int; let x : T = 5 } -- with type alias
{ let a = 1; test a == 1 end } -- with side-effect expression
{ let m = { let x = 1 }; let y = m.x } -- nested modules
{} -- empty module
```

### Module Type Annotations (Signatures)

```
let m : { let x : Int; let y : Bool } = { let x = 1; let y = true }
let m : { type T = Int; let x : T } = { type T = Int; let x = 1 }
type MSig = { let x : Int }
let m : MSig = { let x = 1 }
```

### Field Access

```
let m = { let x = 1; let y = 2 } in m.x -- evaluates to 1
```

### What Works

| Feature | Status |
|---------|--------|
| Module syntax (`{ let ... }`) | Works |
| Type inference (labeled tuple types) | Works |
| Field access via `.` | Works |
| Type aliases inside modules | Works |
| Bare expressions (side effects) | Works |
| Shadowing (last binding wins) | Works |
| Nested modules | Works |
| Signature syntax in type annotations | Works |
| Type-directed error attribution | Works |
| Cursor inspector for Mod/Sig sorts | Works |

### Known Limitations

- **Menhir multi-item modules**: `{ let x = 1; let y = 2 }` fails in the Menhir parser due to a shift-reduce conflict between expression-level `;` (Seq) and module `;` (ModSeq). The tile editor handles this correctly. 2 evaluator tests are skipped because of this.
- **Modules infer Prod types, not Sig types**: Modules currently infer labeled tuple types (`(x=Int, y=Bool)`), not signature types (`{ let x : Int; let y : Bool }`). Sig annotations are desugared to labeled tuples before type checking.
- **No width subtyping**: `let m : { let x : Int } = { let x = 1; let y = 2 }` doesn't restrict `m`'s type to just `{ let x : Int }`. The annotation is desugared to `(x=Int)` which requires exact match.
- **Lowercase module names only**: Capitalized identifiers are parsed as constructors. See `plans/modules.md` for design options.

---

## Architecture

### Sorts

Two sorts were added for the module system:

- **Mod** (`Sort.Mod`): Module items — `let x = 1`, `type T = Int`, bare expressions
- **Sig** (`Sort.Sig`): Signature items — `let x : Int`, `type T = Int`

Both follow the established sort patterns with forms, remolding, and MakeTerm parsing.

### Expansion Model

Modules are a **syntactic sugar**. They are expanded to standard Hazel expressions:

```
{ let a = 1; let b = 2 }
--> let a = 1 in let b = 2 in (a=a, b=b)
```

Expansion happens in two places:
1. **Statics** (`Statics.re`): On-demand expansion for type checking
2. **Elaborator** (`Elaborator.re`): Permanent expansion for evaluation

The expanded form uses nested `let`/`type` bindings with a final labeled tuple containing non-shadowed bindings.

### Type-Directed Expansion

When a module has a type annotation (ana type is a labeled tuple), the expansion adds type annotations to `let` patterns for proper error attribution:

```
let m : { let x : Int } = { let x = true }
-- Expansion with ana=(x=Int):
-- let (x : Int) = true in (x=x)
-- Error appears on `true` (type mismatch: Bool vs Int)
```

Without this, type errors would appear on the synthetic tuple node which has no surface representation, making them invisible to the user.

### Sig Desugaring

Sig types in annotations are desugared to labeled tuples via `Typ.desugar_sig`:

```
{ let x : Int; let y : Bool } --> (x=Int, y=Bool)
```

This is a targeted transformation that only converts Sig nodes to Prod, preserving Parens and other type structure. It replaces `Typ.normalize` in the Asc cases to avoid stripping Parens wrappers from non-Sig type annotations.

### ID Preservation

Module expansion carefully preserves tile IDs for cursor inspector integration:

- **Curly brace + semicolon IDs** → Module expression annotation (absorbed via `adopted_ids`)
- **ModLet/ModType tile IDs** → Expanded `Let`/`TyAlias` expressions (via `IdTagged.fast_copy`)
- **Synthetic tuple** → Fresh IDs (no surface counterpart)

This ensures clicking on any part of a module shows correct type information in the cursor inspector.

### Cursor Inspector

- **Mod items**: Show as "Let definition", "Type alias definition" etc. (Mod cls, not Exp cls)
- **Sig items**: Show as "Let signature", "Type alias signature" etc. (Sig cls via Secondary info)
- **Sort colors**: Both Mod and Sig have dedicated colors in the cursor inspector header, gamma icon, toggle switch, and dividers

---

## Key Files

### Core Implementation

| File | Purpose |
|------|---------|
| `src/language/term/Sort.re` | Sort enum with `Mod` and `Sig` |
| `src/language/term/Grammar.re` | `mod_term`, `sig_term`, `Module` in exp_term, `Sig` in typ_term |
| `src/language/term/Mod.re` | Mod term utilities |
| `src/language/term/Sig.re` | Sig term utilities |
| `src/language/term/Cls.re` | `Mod(Mod.cls)` and `Sig(Sig.cls)` variants for cursor inspector |
| `src/haz3lcore/lang/Form.re` | Module/Sig forms, `mk_pre_c'` helper |
| `src/haz3lcore/tiles/Mold.re` | `mk_pre'` for heterogeneous prefix forms |
| `src/haz3lcore/tiles/Segment.re` | `remold_mod`/`remold_sig` with fallback patterns |
| `src/haz3lcore/tiles/Skel.re` | ModSeq/SigSeq semicolons chainable, sort-specific grout precedence |
| `src/haz3lcore/zipper/action/Insert.re` | `effective_sort` with Mod→Exp / Sig→Typ fallback |
| `src/haz3lcore/lang/MakeTerm.re` | Module/Sig parsing with flattening |
| `src/language/statics/ExpandModule.re` | Module expansion to nested let/type + labeled tuple |
| `src/language/statics/Statics.re` | Module type checking, `desugar_sig` in Asc, Mod/Sig item info |
| `src/language/statics/Elaborator.re` | Module elaboration for dynamics |
| `src/language/statics/Info.re` | `sort_of` returns Mod for InfoExp with Mod cls |
| `src/language/term/Typ.re` | `desugar_sig` function |
| `src/haz3lcore/pretty/ExpToSegment.re` | Module and Sig pretty-printing to segments |
| `src/language/term/Abbreviate.re` | Module abbreviation for probe display |

### CSS

| File | What |
|------|------|
| `src/web/www/style/variables.css` | `--token-mod`, `--token-sig`, `--shard-mod`, `--shard-sig` color variables |
| `src/web/www/style/editor.css` | `.child-line.Mod`, `.child-line.Sig`, keyword bolding |
| `src/web/www/style/cursor-inspector.css` | Mod/Sig gamma, toggle-switch, header, divider colors |

### Tests

| File | What |
|------|------|
| `test/statics/Test_Statics_Modules.re` | 48 statics tests (well-typed, errors, sig annotations, limitations) |
| `test/evaluator/Test_Evaluator_Modules.re` | 11 evaluator tests (2 skipped for Menhir) |
| `test/Test_MakeTerm.re` | Module parsing tests including nested modules |
| `test/Test_Elaboration.re` | 4 module elaboration tests (module → labeled tuple) |
| `test/Test_ExpToSegment.re` | 6 module/sig roundtrip tests + 1 skipped (empty module structural diff) |
| `test/Test_Editing.re` | 4 module editing tests (brace insertion, let inside module) |
| `test/Test_Abbreviate.re` | 2 module abbreviation tests |
| `test/Test_Menhir.re` | 5 Sig round-trip tests |

---

## Technical Details

### Heterogeneous Prefix Forms (`mk_pre'` / `mk_pre_c'`)

ModLet, ModType, SigLet, and SigType need different out vs body sorts. For example, ModLet's out sort is Mod (the form is a module item) but its body sort is Exp (what comes after `=`). Standard `mk_pre` gives both nibs the same sort. `mk_pre'` in `Mold.re` and `mk_pre_c'` in `Form.re` allow heterogeneous sorts.

### Sort Fallback Patterns

**Mod→Exp**: Bare expressions are valid module items, so when in Mod context and no Mod form exists, try Exp. This affects remolding (`remold_mod`) and expansion (`effective_sort` in `Insert.re`).

**Sig→Typ**: Parallel fallback for signatures. Bare types in sig context fall back to Typ sort for robustness.

**Semicolon special case**: When `;` is typed in Mod/Sig context, prefer ModSeq/SigSeq over CellJoin (Exp-sort `;`).

### Singleton Labeled Tuple Bug Fix

Var patterns analyzed against singleton labeled tuple types (e.g., `(y=Int)`) were incorrectly elaborated. Fixed by checking if the pattern name matches the label before elaborating:
- `let a = (a=1) in a` → elaborate (destructuring) → `a : Int`
- `let m = (y=1) in m` → don't elaborate → `m : (y=Int)`

### Sort-Specific Grout Precedence

Concave grout (the placeholder left when an operator is deleted) normally has a tight precedence (34), which means it gets absorbed into the bodies of loose-binding forms like `fun`, `if`, and `let`. This is desirable in expression context — deleting a semicolon between `a; b` produces a multi-hole that the Elaborator converts to `Seq`.

In module context, this is problematic: deleting a semicolon between `let x = 1; let y = 2` would cause the grout to be absorbed into the first let's expression body, breaking statics for the second binding. To fix this, `Skel.mk` accepts an optional `~sort` parameter. When `sort` is `Mod` or `Sig`, concave grout uses the much looser `mod_seq` precedence (47) instead of `concave_grout` (34). This causes grout to separate module items the same way semicolons do.

The sort is threaded from `MakeTerm.re` where tile children are processed — the child sort is known from the tile's mold (`mold.in_`), so module body segments (`{...}`) get `sort=Mod` and signature bodies get `sort=Sig`. Expression bodies inside modules still get `sort=Exp`, so grout stays tight within function literals and other expression forms. All other `Segment.skel` callers default to `sort=Exp`.

### Module Semicolon Decoration

In `Arms.re`, module/sig semicolons render as lone shard hexagons (no arms to other pieces). Module/sig curly braces render as a pair with arm between them, filtering out semicolons.
Loading