Skip to content

Commit 3b7f4db

Browse files
H-4747: HashQL: Specialize HIR Graph Expressions (#7433)
Co-authored-by: Tim Diekmann <[email protected]>
1 parent a76ee6a commit 3b7f4db

31 files changed

+1326
-44
lines changed

libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-nominal.stdout

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/result.stdout

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//@ run: pass
2+
//@ description: Test that the types are correctly extracted from a graph pipeline with inferred closure.
3+
[
4+
"::core::graph::tail::collect",
5+
[
6+
"::core::graph::body::filter",
7+
[
8+
"::core::graph::head::entities",
9+
["::core::graph::tmp::decision_time_now"]
10+
],
11+
[
12+
"fn",
13+
{ "#tuple": [] },
14+
{ "#struct": { "vertex": "_" } },
15+
"_",
16+
[
17+
"==",
18+
"vertex.id.entity_id.entity_uuid",
19+
[
20+
"::core::graph::types::knowledge::entity::EntityUuid",
21+
[
22+
"::core::uuid::Uuid",
23+
{ "#literal": "e2851dbb-7376-4959-9bca-f72cafc4448f" }
24+
]
25+
]
26+
]
27+
]
28+
]
29+
]

libs/@local/hashql/ast/tests/ui/lowering/type-extractor/graph-pipeline-inferred-closure.stdout

Lines changed: 112 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/@local/hashql/compiletest/src/suite/hir_lower_specialization.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,12 @@ impl Suite for HirLowerSpecializationSuite {
9494
let (mut residual, checking_diagnostics) = checking.finish();
9595
process_diagnostics(diagnostics, checking_diagnostics)?;
9696

97-
let mut specialisation =
98-
Specialization::new(&interner, &mut residual.types, residual.intrinsics);
97+
let mut specialisation = Specialization::new(
98+
&environment,
99+
&interner,
100+
&mut residual.types,
101+
residual.intrinsics,
102+
);
99103

100104
let node = match specialisation.fold_node(node) {
101105
Ok(node) => node,

libs/@local/hashql/core/src/type/kind/opaque.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,14 @@ impl<'heap> PrettyPrint<'heap> for OpaqueType<'heap> {
333333
env: &Environment<'heap>,
334334
boundary: &mut PrettyPrintBoundary,
335335
) -> RcDoc<'heap, anstyle::Style> {
336-
RcDoc::text(self.name.unwrap())
336+
// Remove the module from the name (if exists) this increases readability during
337+
// pretty-printing.
338+
let name = self.name.unwrap();
339+
let name = name
340+
.rsplit_once("::")
341+
.map_or_else(|| name, |(_, name)| name);
342+
343+
RcDoc::text(name)
337344
.append(RcDoc::text("["))
338345
.append(boundary.pretty_type(env, self.repr).group())
339346
.append(RcDoc::text("]"))

libs/@local/hashql/hir/src/lower/specialization/error.rs

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use alloc::borrow::Cow;
22

3-
use hashql_core::span::SpanId;
3+
use hashql_core::{
4+
pretty::{PrettyOptions, PrettyPrint as _},
5+
span::SpanId,
6+
r#type::environment::Environment,
7+
};
48
use hashql_diagnostics::{
59
Diagnostic,
610
category::{DiagnosticCategory, TerminalDiagnosticCategory},
@@ -10,13 +14,9 @@ use hashql_diagnostics::{
1014
severity::Severity,
1115
};
1216

13-
pub type SpecializationDiagnostic = Diagnostic<SpecializationDiagnosticCategory, SpanId>;
17+
use crate::node::Node;
1418

15-
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
16-
pub enum SpecializationDiagnosticCategory {
17-
UnsupportedIntrinsic,
18-
UnknownIntrinsic,
19-
}
19+
pub type SpecializationDiagnostic = Diagnostic<SpecializationDiagnosticCategory, SpanId>;
2020

2121
const UNSUPPORTED_INTRINSIC: TerminalDiagnosticCategory = TerminalDiagnosticCategory {
2222
id: "unsupported-intrinsic",
@@ -28,6 +28,30 @@ const UNKNOWN_INTRINSIC: TerminalDiagnosticCategory = TerminalDiagnosticCategory
2828
name: "Unknown intrinsic operation",
2929
};
3030

31+
const INVALID_GRAPH_CHAIN: TerminalDiagnosticCategory = TerminalDiagnosticCategory {
32+
id: "invalid-graph-chain",
33+
name: "Invalid graph operation chain",
34+
};
35+
36+
const NON_INTRINSIC_GRAPH_OPERATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory {
37+
id: "non-intrinsic-graph-operation",
38+
name: "Non-intrinsic function in graph operation",
39+
};
40+
41+
const NON_GRAPH_INTRINSIC: TerminalDiagnosticCategory = TerminalDiagnosticCategory {
42+
id: "non-graph-intrinsic",
43+
name: "Non-graph intrinsic in graph operation",
44+
};
45+
46+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47+
pub enum SpecializationDiagnosticCategory {
48+
UnsupportedIntrinsic,
49+
UnknownIntrinsic,
50+
InvalidGraphChain,
51+
NonIntrinsicGraphOperation,
52+
NonGraphIntrinsic,
53+
}
54+
3155
impl DiagnosticCategory for SpecializationDiagnosticCategory {
3256
fn id(&self) -> Cow<'_, str> {
3357
Cow::Borrowed("specialization")
@@ -41,6 +65,9 @@ impl DiagnosticCategory for SpecializationDiagnosticCategory {
4165
match *self {
4266
Self::UnsupportedIntrinsic => Some(&UNSUPPORTED_INTRINSIC),
4367
Self::UnknownIntrinsic => Some(&UNKNOWN_INTRINSIC),
68+
Self::InvalidGraphChain => Some(&INVALID_GRAPH_CHAIN),
69+
Self::NonIntrinsicGraphOperation => Some(&NON_INTRINSIC_GRAPH_OPERATION),
70+
Self::NonGraphIntrinsic => Some(&NON_GRAPH_INTRINSIC),
4471
}
4572
}
4673
}
@@ -113,3 +140,105 @@ pub(crate) fn unknown_intrinsic(span: SpanId, intrinsic_name: &str) -> Specializ
113140

114141
diagnostic
115142
}
143+
144+
/// Creates a diagnostic for an invalid graph operation chain.
145+
///
146+
/// This occurs when following a graph chain but encountering a specialized operation
147+
/// that is not a graph operation (e.g., a math operation that was already processed).
148+
pub(crate) fn invalid_graph_chain<'heap>(
149+
env: &Environment<'heap>,
150+
span: SpanId,
151+
node: Node<'heap>,
152+
) -> SpecializationDiagnostic {
153+
let mut diagnostic = Diagnostic::new(
154+
SpecializationDiagnosticCategory::InvalidGraphChain,
155+
Severity::Error,
156+
);
157+
158+
diagnostic
159+
.labels
160+
.push(Label::new(span, "Expected a graph operation here").with_order(0));
161+
162+
diagnostic.add_help(Help::new(format!(
163+
"{} is not a graph operation. Graph chains can only contain operations that work with \
164+
graph data, such as filtering, entity selection, or other graph transformations. \
165+
Operations like math, comparisons, or other non-graph functions cannot be part of a \
166+
graph chain.",
167+
node.pretty_print(env, PrettyOptions::default().with_max_width(60))
168+
)));
169+
170+
diagnostic.add_note(Note::new(
171+
"Graph operation chains work by passing graph objects through a sequence of \
172+
graph-specific operations. Each operation in the chain must accept a graph and return a \
173+
modified graph. Non-graph operations like math, comparisons, or boolean logic should be \
174+
used within closures or separate expressions, not as part of the main graph chain.",
175+
));
176+
177+
diagnostic
178+
}
179+
180+
/// Creates a diagnostic for using a non-intrinsic function in graph operations.
181+
///
182+
/// This occurs when a graph operation chain contains a function call that is not
183+
/// mapped to an intrinsic operation.
184+
pub(crate) fn non_intrinsic_graph_operation<'heap>(
185+
env: &Environment<'heap>,
186+
span: SpanId,
187+
function: Node<'heap>,
188+
) -> SpecializationDiagnostic {
189+
let mut diagnostic = Diagnostic::new(
190+
SpecializationDiagnosticCategory::NonIntrinsicGraphOperation,
191+
Severity::Error,
192+
);
193+
194+
let label_text = "This is not a graph intrinsic operation";
195+
196+
diagnostic
197+
.labels
198+
.push(Label::new(span, label_text).with_order(0));
199+
200+
diagnostic.add_help(Help::new(format!(
201+
"{} is not a valid graph operation. Graph operation chains can only contain intrinsic \
202+
functions that are part of the HashQL graph API. Higher-order functions (HOFs) and \
203+
user-defined functions are not supported yet. To track support for user-defined \
204+
functions see https://linear.app/hash/issue/H-4776/hashql-allow-user-defined-functions-in-graph-pipelines",
205+
function.pretty_print(env, PrettyOptions::default().with_max_width(60)))));
206+
207+
diagnostic.add_note(Note::new(
208+
"Graph intrinsics are built-in operations like `::core::graph::head::entities`, \
209+
`::core::graph::body::filter`, and `::core::graph::tail::collect` that can be optimized \
210+
during compilation. Only these predefined operations can be used to build graph query \
211+
chains.",
212+
));
213+
214+
diagnostic
215+
}
216+
217+
/// Creates a diagnostic for an unknown graph intrinsic operation.
218+
///
219+
/// This indicates a compiler bug where a graph intrinsic that should be mapped is missing.
220+
#[coverage(off)] // compiler bugs should never be hit
221+
pub(crate) fn non_graph_intrinsic(span: SpanId, intrinsic_name: &str) -> SpecializationDiagnostic {
222+
let mut diagnostic = Diagnostic::new(
223+
SpecializationDiagnosticCategory::NonGraphIntrinsic,
224+
Severity::Bug,
225+
);
226+
227+
diagnostic.labels.push(
228+
Label::new(span, format!("unknown graph intrinsic `{intrinsic_name}`")).with_order(0),
229+
);
230+
231+
diagnostic.add_help(Help::new(format!(
232+
"The graph intrinsic `{intrinsic_name}` is missing from the graph specialization phase \
233+
mapping. Add this intrinsic to the match statement in the `fold_graph_read` method to \
234+
resolve this compiler bug."
235+
)));
236+
237+
diagnostic.add_note(Note::new(
238+
"This error indicates that a new graph intrinsic was added to the standard library but \
239+
the graph specialization phase wasn't updated to handle it. The compiler should be kept \
240+
in sync with stdlib changes.",
241+
));
242+
243+
diagnostic
244+
}

0 commit comments

Comments
 (0)