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
69e3fa9
chore: add missing docs
indietyp Jan 22, 2026
33cc3f4
chore: checkpoint
indietyp Jan 22, 2026
00ecafc
chore: outline
indietyp Jan 22, 2026
8b91ad0
feat: figure out what to do
indietyp Jan 22, 2026
a851607
feat: checkpoint
indietyp Jan 23, 2026
84ded8f
feat: checkpoint
indietyp Jan 23, 2026
dcdb805
chore: checkpoint
indietyp Jan 25, 2026
b44d150
feat: checkpoint
indietyp Jan 25, 2026
fbb678c
feat: checkpoint
indietyp Jan 25, 2026
e95636e
feat: checkpoint
indietyp Jan 25, 2026
5313a2d
chore: write down the thoughts
indietyp Jan 26, 2026
d48b6a8
chore: checkpoint
indietyp Jan 27, 2026
085ae98
feat: shuffle things around
indietyp Jan 28, 2026
1f3c15b
feat: checkpoint
indietyp Jan 28, 2026
72e55d4
feat: checkpoint
indietyp Jan 28, 2026
d99e870
feat: checkpoint
indietyp Jan 28, 2026
771003d
chore: checkpoint
indietyp Jan 28, 2026
39f7df6
feat: checkpoint
indietyp Jan 29, 2026
e7b70cf
feat: checkpoint
indietyp Jan 29, 2026
ae1561b
feat: make everything pub
indietyp Jan 29, 2026
0df80a3
chore: docs
indietyp Jan 29, 2026
4a2e300
feat: move to line buffer for text
indietyp Jan 29, 2026
23258b5
feat: pretty
indietyp Jan 29, 2026
8d47966
chore: comments
indietyp Jan 29, 2026
4e072d6
feat: checkpoint
indietyp Jan 29, 2026
1a8c424
feat: tests
indietyp Jan 29, 2026
c812216
fix: add miri test
indietyp Jan 29, 2026
affda04
fix: test
indietyp Jan 29, 2026
64a6ca1
fix: recursive guard
indietyp Jan 29, 2026
4ba813c
fix: graph read support
indietyp Feb 2, 2026
cc929a8
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