Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5db9ffb
chore: add missing docs
indietyp Jan 22, 2026
69c7b13
chore: checkpoint
indietyp Jan 22, 2026
8bedac4
chore: outline
indietyp Jan 22, 2026
559f8df
feat: figure out what to do
indietyp Jan 22, 2026
174db34
feat: checkpoint
indietyp Jan 23, 2026
9b1eabf
feat: checkpoint
indietyp Jan 23, 2026
0b1e9fb
chore: checkpoint
indietyp Jan 25, 2026
1cdec20
feat: checkpoint
indietyp Jan 25, 2026
99bbd35
feat: checkpoint
indietyp Jan 25, 2026
567380b
feat: checkpoint
indietyp Jan 25, 2026
5d76257
chore: write down the thoughts
indietyp Jan 26, 2026
5654c80
chore: checkpoint
indietyp Jan 27, 2026
cd41f98
feat: shuffle things around
indietyp Jan 28, 2026
7606e72
feat: checkpoint
indietyp Jan 28, 2026
d902ac1
feat: checkpoint
indietyp Jan 28, 2026
a71cd7a
feat: checkpoint
indietyp Jan 28, 2026
5ac12e9
chore: checkpoint
indietyp Jan 28, 2026
638ff48
feat: checkpoint
indietyp Jan 29, 2026
edb4625
feat: checkpoint
indietyp Jan 29, 2026
021a3d5
feat: make everything pub
indietyp Jan 29, 2026
c24547f
chore: docs
indietyp Jan 29, 2026
5224677
feat: move to line buffer for text
indietyp Jan 29, 2026
0cb49cb
feat: pretty
indietyp Jan 29, 2026
689f2a3
chore: comments
indietyp Jan 29, 2026
ddfda35
feat: checkpoint
indietyp Jan 29, 2026
205be19
feat: tests
indietyp Jan 29, 2026
3474bc2
fix: add miri test
indietyp Jan 29, 2026
d8c2679
fix: test
indietyp Jan 29, 2026
c8081d6
fix: recursive guard
indietyp Jan 29, 2026
52ab709
fix: graph read support
indietyp Feb 2, 2026
1283429
chore: fix lints
indietyp Feb 2, 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
39 changes: 37 additions & 2 deletions .claude/skills/testing-hashql/references/mir-builder-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ body!(interner, env; <source> @ <id> / <arity> -> <return_type> {
})
```

**Important:** Only a single `decl` statement is supported. Declare all locals in one comma-separated list:

```rust
// βœ… Correct - single decl with all locals
decl env: (), vertex: Entity, x: Int, y: Int, result: Bool;

// ❌ Wrong - multiple decl statements will not compile
decl env: (), vertex: Entity;
decl x: Int, y: Int;
decl result: Bool;
```

### Header

| Component | Description | Example |
Expand Down Expand Up @@ -87,6 +99,7 @@ The `<id>` can be a numeric literal (`0`, `1`, `42`) or a variable identifier (`
| `(a: T1, b: T2)` | Struct types | `(a: Int, b: Bool)` |
| `[List T]` | List type (intrinsic) | `[List Int]`, `[List (Int, Bool)]` |
| `[fn(T1, T2) -> R]` | Closure types | `[fn(Int) -> Int]`, `[fn() -> Bool]` |
| `[Opaque path; T]` | Opaque type with symbol path | `[Opaque sym::path::Entity; ?]` |
| `\|types\| types.custom()` | Custom type expression | `\|t\| t.null()` |

### Projections (Optional)
Expand All @@ -97,12 +110,18 @@ Declare field projections after `decl` to access struct/tuple fields as places:
@proj <name> = <base>.<field>: <type>, ...;
```

Supports nested projections:
**Field access modes:**

- Numeric index (e.g., `tup.0`) β†’ `ProjectionKind::Field`
- Named field (e.g., `entity.metadata`) β†’ `ProjectionKind::FieldByName`

Each `@proj` declaration supports only ONE field after the base. For deeper paths, chain through intermediate declarations:

```rust
let body = body!(interner, env; fn@0/0 -> Int {
decl tup: ((Int, Int), Int), result: Int;
@proj inner = tup.0: (Int, Int), inner_1 = tup.0.1: Int;
// inner uses tup as base, inner_1 uses inner as base
@proj inner = tup.0: (Int, Int), inner_1 = inner.1: Int;

bb0() {
result = load inner_1;
Expand All @@ -111,6 +130,22 @@ let body = body!(interner, env; fn@0/0 -> Int {
});
```

Named field projections for opaque types:

```rust
use hashql_core::symbol::sym;

let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool {
decl env: (), vertex: [Opaque sym::path::Entity; ?];
// Chain: vertex -> metadata -> archived
@proj metadata = vertex.metadata: ?, archived = metadata.archived: Bool;

bb0() {
return archived;
}
});
```

### Statements

| Syntax | Description | MIR Equivalent |
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ For Rust packages, you can add features as needed with `--all-features`, specifi

CRITICAL: For the files referenced below, use your Read tool to load it on a need-to-know basis, ONLY when relevant to the SPECIFIC task at hand:

- @.config/agents/rules/*.md
- .config/agents/rules/*.md

Instructions:

Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ hashql-syntax-jexpr.path = "libs/@local/hashql/syntax-jexpr"
type-system.path = "libs/@blockprotocol/type-system/rust"

# External dependencies
allocator-api2 = { version = "0.2.8", default-features = false }
annotate-snippets = { version = "0.12.8", default-features = false }
ansi-to-html = { version = "0.2.2", default-features = false }
anstream = { version = "0.6.21", default-features = false }
Expand Down
9 changes: 6 additions & 3 deletions libs/@local/hashql/compiletest/src/suite/mir_reify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use hashql_mir::{
context::MirContext,
def::{DefId, DefIdSlice, DefIdVec},
intern::Interner,
pretty::{D2Buffer, D2Format, TextFormat},
pretty::{D2Buffer, D2Format, TextFormatOptions},
};

use super::{RunContext, Suite, SuiteDiagnostic, SuiteDirectives, common::process_status};
Expand Down Expand Up @@ -87,12 +87,15 @@ pub(crate) fn mir_format_text<'heap>(
TypeFormatterOptions::terse().with_qualified_opaque_names(true),
);

let mut text_format = TextFormat {
let mut text_format = TextFormatOptions {
writer,
indent: 4,
sources: bodies,
types,
};
annotations: (),
}
.build();

text_format
.format(bodies, &[root])
.expect("should be able to write bodies");
Expand Down
2 changes: 1 addition & 1 deletion libs/@local/hashql/core/src/heap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl Heap {
strings.reserve(TABLES.iter().map(|table| table.len()).sum());

for &table in TABLES {
for &symbol in table {
for symbol in table {
assert!(strings.insert(symbol.as_str()));
}
}
Expand Down
9 changes: 9 additions & 0 deletions libs/@local/hashql/core/src/id/bit_vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,15 @@ impl<T: Id> DenseBitSet<T> {
new_word != word
}

#[inline]
pub fn set(&mut self, elem: T, value: bool) -> bool {
if value {
self.insert(elem)
} else {
self.remove(elem)
}
}

#[inline]
pub fn insert_range(&mut self, elems: impl RangeBounds<T>) {
let Some((start, end)) = inclusive_start_end(elems, self.domain_size) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
StandardLibrary,
std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule, core::option::option},
},
symbol::Symbol,
symbol::{Symbol, sym},
};

pub(in crate::module::std_lib) struct Entity {
Expand Down Expand Up @@ -106,7 +106,7 @@ impl<'heap> StandardLibraryModule<'heap> for Entity {
let entity_ty = lib.ty.generic(
[t_arg],
lib.ty.opaque(
"::graph::types::knowledge::entity::Entity",
sym::path::Entity,
lib.ty.r#struct([
("id", entity_record_id_ty),
("properties", t_param),
Expand Down
71 changes: 49 additions & 22 deletions libs/@local/hashql/core/src/symbol/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,67 +61,77 @@ macro_rules! symbols {
}

symbols![lexical; LEXICAL;
BaseUrl,
Boolean,
Dict,
E,
Err,
Integer,
Intersection,
List,
Never,
None,
Null,
Number,
Ok,
R,
Result,
Some,
String,
T,
U,
Union,
Unknown,
Url,
access,
add,
and,
archived,
archived_by_id,
bar,
BaseUrl,
bit_and,
bit_not,
bit_or,
bit_shl,
bit_shr,
bit_xor,
Boolean,
collect,
confidence,
core,
created_at_decision_time,
created_at_transaction_time,
created_by_id,
decision_time,
Dict,
div,
draft_id,
E,
edition,
edition_id,
encodings,
entity,
entity_edition_id,
entity_id,
entity_type_ids,
entity_uuid,
eq,
Err,
filter,
foo,
gt,
gte,
id,
index,
inferred,
input,
input_exists: "$exists",
Integer,
Intersection,
kernel,
left_entity_confidence,
left_entity_id,
left_entity_provenance,
link_data,
List,
lt,
lte,
math,
metadata,
mul,
ne,
Never,
None,
not,
Null,
null,
Number,
Ok,
option,
or,
pow,
properties,
provenance,
provided,
r#as: "as",
r#as_force: "as!",
r#else: "else",
Expand All @@ -136,11 +146,27 @@ symbols![lexical; LEXICAL;
r#true: "true",
r#type: "type",
r#use: "use",
R,
record_id,
Result,
right_entity_confidence,
right_entity_id,
right_entity_provenance,
Some,
special_form,
String,
sub,
T,
temporal_versioning,
then: "then",
thunk: "thunk",
transaction_time,
U,
Union,
Unknown,
unknown,
Url,
vectors,
web_id,
];

Expand Down Expand Up @@ -202,6 +228,7 @@ symbols![path; PATHS;
graph_head_entities: "::graph::head::entities",
graph_body_filter: "::graph::body::filter",
graph_tail_collect: "::graph::tail::collect",
Entity: "::graph::types::knowledge::entity::Entity"
];

pub(crate) const TABLES: &[&[&Symbol<'static>]] = &[LEXICAL, DIGITS, SYMBOLS, PATHS, INTERNAL];
Expand Down
Loading
Loading