Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions libs/@local/hashql/compiletest/src/suite/mir_interpret.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use hashql_core::r#type::environment::Environment;
use hashql_diagnostics::Diagnostic;
use hashql_mir::{
intern::Interner,
interpret::{CallStack, Inputs, Runtime, RuntimeConfig},
};

use super::{
RunContext, Suite, SuiteDiagnostic,
mir_pass_transform_post_inline::mir_pass_transform_post_inline,
mir_pass_transform_pre_inline::TextRenderer,
};

pub(crate) struct MirInterpret;

impl Suite for MirInterpret {
fn name(&self) -> &'static str {
"mir/interpret"
}

fn description(&self) -> &'static str {
"Run the interpreter on the MIR"
}

fn secondary_file_extensions(&self) -> &[&str] {
&["mir"]
}

fn run<'heap>(
&self,
RunContext {
heap,
diagnostics,
secondary_outputs,
..
}: RunContext<'_, 'heap>,
expr: hashql_ast::node::expr::Expr<'heap>,
) -> Result<String, SuiteDiagnostic> {
let mut environment = Environment::new(heap);
let interner = Interner::new(heap);

let mut buffer = Vec::new();

let (root, bodies, _) = mir_pass_transform_post_inline(
heap,
expr,
&interner,
TextRenderer::new(&mut buffer),
&mut environment,
diagnostics,
)?;

secondary_outputs.insert("mir", String::from_utf8_lossy_owned(buffer));

let inputs = Inputs::new();
let mut runtime = Runtime::new(RuntimeConfig::default(), &bodies, &inputs);
let callstack = CallStack::new(&runtime, root, []);

let output = runtime
.run(callstack, |_| unimplemented!())
.map_err(Diagnostic::generalize)
.map_err(Diagnostic::boxed)?;
Comment thread
indietyp marked this conversation as resolved.

Ok(format!("{output:#?}"))
}
}
4 changes: 3 additions & 1 deletion libs/@local/hashql/compiletest/src/suite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod hir_lower_normalization;
mod hir_lower_specialization;
mod hir_lower_thunking;
mod hir_reify;
mod mir_interpret;
mod mir_pass_analysis_data_dependency;
mod mir_pass_transform_administrative_reduction;
mod mir_pass_transform_cfg_simplify;
Expand Down Expand Up @@ -59,7 +60,7 @@ use self::{
hir_lower_normalization::HirLowerNormalizationSuite,
hir_lower_specialization::HirLowerSpecializationSuite,
hir_lower_thunking::HirLowerThunkingSuite, hir_reify::HirReifySuite,
mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
mir_interpret::MirInterpret, mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
mir_pass_transform_administrative_reduction::MirPassTransformAdministrativeReduction,
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
mir_pass_transform_dse::MirPassTransformDse,
Expand Down Expand Up @@ -163,6 +164,7 @@ const SUITES: &[&dyn Suite] = &[
&HirLowerTypeInferenceIntrinsicsSuite,
&HirLowerTypeInferenceSuite,
&HirReifySuite,
&MirInterpret,
&MirPassAnalysisDataDependency,
&MirPassTransformAdministrativeReduction,
&MirPassTransformCfgSimplify,
Expand Down
150 changes: 112 additions & 38 deletions libs/@local/hashql/eval/src/orchestrator/error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
//! Errors that occur while fulfilling [`GraphRead`] suspensions.
//! Error types for the orchestration layer.
//!
//! These are internal runtime errors: failures in compiled query execution,
//! row decoding, or parameter encoding. The user wrote HashQL, not SQL; if
//! the bridge fails, it indicates a bug in the compiler or runtime.
//! The orchestrator sits between the MIR interpreter and external data sources
//! (PostgreSQL). Errors fall into two families:
//!
//! - **Interpreter errors**: failures in the MIR interpreter itself (type invariant violations,
//! control flow errors, etc.). These are produced by the interpreter and forwarded through the
//! orchestrator.
//! - **Bridge errors**: failures while fulfilling [`GraphRead`] suspensions (query execution, row
//! decoding, parameter encoding). The user wrote HashQL, not SQL; if the bridge fails, it
//! indicates a bug in the compiler or runtime.
//!
//! [`OrchestratorDiagnosticCategory`] unifies both families under a single
//! category hierarchy so that downstream consumers (the eval crate) see one
//! coherent diagnostic type from the orchestration layer.
//!
//! [`GraphRead`]: hashql_mir::body::terminator::GraphRead

use alloc::string::String;
use alloc::{borrow::Cow, string::String};

use hashql_core::{
pretty::{Formatter, RenderOptions},
Expand All @@ -15,15 +25,15 @@ use hashql_core::{
r#type::{TypeFormatter, TypeFormatterOptions, TypeId, environment::Environment},
};
use hashql_diagnostics::{
Diagnostic, Label, category::TerminalDiagnosticCategory, diagnostic::Message,
severity::Severity,
Diagnostic, Label,
category::{DiagnosticCategory, TerminalDiagnosticCategory},
diagnostic::Message,
severity::Critical,
};
use hashql_mir::{
body::{basic_block::BasicBlockId, local::Local},
def::DefId,
interpret::error::{
InterpretDiagnostic, InterpretDiagnosticCategory, SuspensionDiagnosticCategory,
},
interpret::error::InterpretDiagnosticCategory,
};

use super::{Indexed, codec::JsonValueKind};
Expand Down Expand Up @@ -89,8 +99,64 @@ const VALUE_SERIALIZATION: TerminalDiagnosticCategory = TerminalDiagnosticCatego
name: "Value Serialization",
};

const fn category(terminal: &'static TerminalDiagnosticCategory) -> InterpretDiagnosticCategory {
InterpretDiagnosticCategory::Suspension(SuspensionDiagnosticCategory(terminal))
/// Type alias for orchestrator diagnostics.
///
/// The default severity kind is [`Critical`].
pub type OrchestratorDiagnostic<K = Critical> =
Diagnostic<OrchestratorDiagnosticCategory, SpanId, K>;
Comment thread
indietyp marked this conversation as resolved.

/// Diagnostic subcategory for errors that occur while fulfilling a suspension.
///
/// Wraps a [`TerminalDiagnosticCategory`] that identifies the specific bridge
/// failure (query execution, row hydration, parameter encoding, etc.).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct BridgeDiagnosticCategory(pub &'static TerminalDiagnosticCategory);

impl DiagnosticCategory for BridgeDiagnosticCategory {
fn id(&self) -> Cow<'_, str> {
Cow::Borrowed("bridge")
}

fn name(&self) -> Cow<'_, str> {
Cow::Borrowed("Bridge")
}

fn subcategory(&self) -> Option<&dyn DiagnosticCategory> {
Some(self.0)
}
}

/// Top-level diagnostic category for the orchestration layer.
///
/// Unifies interpreter errors (forwarded from the MIR interpreter) and bridge
/// errors (failures while fulfilling suspensions) under a single hierarchy.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum OrchestratorDiagnosticCategory {
/// An error produced by the MIR interpreter itself.
Interpret(InterpretDiagnosticCategory),
/// An error from the bridge while fulfilling a suspension.
Bridge(BridgeDiagnosticCategory),
}

impl DiagnosticCategory for OrchestratorDiagnosticCategory {
fn id(&self) -> Cow<'_, str> {
Cow::Borrowed("orchestrator")
}

fn name(&self) -> Cow<'_, str> {
Cow::Borrowed("Orchestrator")
}

fn subcategory(&self) -> Option<&dyn DiagnosticCategory> {
match self {
Self::Interpret(cat) => Some(cat),
Self::Bridge(cat) => Some(cat),
}
}
}

const fn category(terminal: &'static TerminalDiagnosticCategory) -> OrchestratorDiagnosticCategory {
OrchestratorDiagnosticCategory::Bridge(BridgeDiagnosticCategory(terminal))
}

/// Errors that occur while decoding a JSON value into a typed [`Value`].
Expand Down Expand Up @@ -356,7 +422,7 @@ pub enum BridgeError<'heap> {
}

impl<'heap> BridgeError<'heap> {
pub fn into_diagnostic(self, span: SpanId, env: &Environment<'heap>) -> InterpretDiagnostic {
pub fn into_diagnostic(self, span: SpanId, env: &Environment<'heap>) -> OrchestratorDiagnostic {
match self {
Self::QueryExecution { sql, source } => query_execution(span, &sql, &source),
Self::RowHydration { column, source } => row_hydration(span, column, &source),
Expand Down Expand Up @@ -388,8 +454,12 @@ impl<'heap> BridgeError<'heap> {
}
}

fn query_execution(span: SpanId, sql: &str, error: &tokio_postgres::Error) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&QUERY_EXECUTION), Severity::Bug).primary(
fn query_execution(
span: SpanId,
sql: &str,
error: &tokio_postgres::Error,
) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&QUERY_EXECUTION), Critical::BUG).primary(
Label::new(span, "compiled query was rejected by the database"),
);

Expand All @@ -411,9 +481,9 @@ fn row_hydration(
value: column,
}: Indexed<ColumnDescriptor>,
source: &tokio_postgres::Error,
) -> InterpretDiagnostic {
) -> OrchestratorDiagnostic {
let mut diagnostic =
Diagnostic::new(category(&ROW_HYDRATION), Severity::Bug).primary(Label::new(
Diagnostic::new(category(&ROW_HYDRATION), Critical::BUG).primary(Label::new(
span,
format!("cannot decode result column {index} ({column})"),
));
Expand All @@ -429,7 +499,7 @@ fn row_hydration(

/// Adds notes describing a [`DecodeError`] to a diagnostic.
fn add_decode_error_notes(
diagnostic: &mut InterpretDiagnostic,
diagnostic: &mut OrchestratorDiagnostic,
source: &DecodeError<'_>,
env: &Environment<'_>,
) {
Expand Down Expand Up @@ -543,9 +613,9 @@ fn value_deserialization(
}: Indexed<ColumnDescriptor>,
source: &DecodeError<'_>,
env: &Environment<'_>,
) -> InterpretDiagnostic {
) -> OrchestratorDiagnostic {
let mut diagnostic =
Diagnostic::new(category(&VALUE_DESERIALIZATION), Severity::Bug).primary(Label::new(
Diagnostic::new(category(&VALUE_DESERIALIZATION), Critical::BUG).primary(Label::new(
span,
format!("cannot deserialize result column {index} ({column})"),
));
Expand All @@ -565,8 +635,8 @@ fn continuation_deserialization(
local: Local,
source: &DecodeError<'_>,
env: &Environment<'_>,
) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&CONTINUATION_DESERIALIZATION), Severity::Bug)
) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&CONTINUATION_DESERIALIZATION), Critical::BUG)
.primary(Label::new(
span,
format!("cannot deserialize continuation local {local} in definition {body}"),
Expand All @@ -582,9 +652,13 @@ fn continuation_deserialization(
diagnostic
}

fn invalid_continuation_block_id(span: SpanId, body: DefId, block_id: i32) -> InterpretDiagnostic {
fn invalid_continuation_block_id(
span: SpanId,
body: DefId,
block_id: i32,
) -> OrchestratorDiagnostic {
let mut diagnostic =
Diagnostic::new(category(&INVALID_CONTINUATION_BLOCK_ID), Severity::Bug).primary(
Diagnostic::new(category(&INVALID_CONTINUATION_BLOCK_ID), Critical::BUG).primary(
Label::new(span, "continuation returned an invalid block ID"),
);

Expand All @@ -599,8 +673,8 @@ fn invalid_continuation_block_id(span: SpanId, body: DefId, block_id: i32) -> In
diagnostic
}

fn invalid_continuation_local(span: SpanId, body: DefId, local: i32) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INVALID_CONTINUATION_LOCAL), Severity::Bug)
fn invalid_continuation_local(span: SpanId, body: DefId, local: i32) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INVALID_CONTINUATION_LOCAL), Critical::BUG)
.primary(Label::new(span, "continuation returned an invalid local"));

diagnostic.add_message(Message::note(format!(
Expand All @@ -618,9 +692,9 @@ fn parameter_encoding(
span: SpanId,
parameter: usize,
error: &(dyn core::error::Error + Send + Sync),
) -> InterpretDiagnostic {
) -> OrchestratorDiagnostic {
let mut diagnostic =
Diagnostic::new(category(&PARAMETER_ENCODING), Severity::Bug).primary(Label::new(
Diagnostic::new(category(&PARAMETER_ENCODING), Critical::BUG).primary(Label::new(
span,
format!(
"cannot encode parameter ${} for the database",
Expand All @@ -637,8 +711,8 @@ fn parameter_encoding(
diagnostic
}

fn query_lookup(span: SpanId, body: DefId, block: BasicBlockId) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&QUERY_LOOKUP), Severity::Bug).primary(
fn query_lookup(span: SpanId, body: DefId, block: BasicBlockId) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&QUERY_LOOKUP), Critical::BUG).primary(
Label::new(span, "no compiled query found for this data access"),
);

Expand All @@ -653,8 +727,8 @@ fn query_lookup(span: SpanId, body: DefId, block: BasicBlockId) -> InterpretDiag
diagnostic
}

fn incomplete_continuation(span: SpanId, body: DefId, field: &str) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INCOMPLETE_CONTINUATION), Severity::Bug)
fn incomplete_continuation(span: SpanId, body: DefId, field: &str) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INCOMPLETE_CONTINUATION), Critical::BUG)
.primary(Label::new(
span,
"continuation state is missing required columns",
Expand All @@ -671,8 +745,8 @@ fn incomplete_continuation(span: SpanId, body: DefId, field: &str) -> InterpretD
diagnostic
}

fn missing_execution_residual(span: SpanId, body: DefId) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&MISSING_EXECUTION_RESIDUAL), Severity::Bug)
fn missing_execution_residual(span: SpanId, body: DefId) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&MISSING_EXECUTION_RESIDUAL), Critical::BUG)
.primary(Label::new(
span,
"no execution residual found for this definition",
Expand All @@ -689,8 +763,8 @@ fn missing_execution_residual(span: SpanId, body: DefId) -> InterpretDiagnostic
diagnostic
}

fn invalid_filter_return(span: SpanId, body: DefId) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INVALID_FILTER_RETURN), Severity::Bug)
fn invalid_filter_return(span: SpanId, body: DefId) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&INVALID_FILTER_RETURN), Critical::BUG)
.primary(Label::new(span, "filter body returned a non-boolean value"));

diagnostic.add_message(Message::note(format!(
Expand All @@ -704,8 +778,8 @@ fn invalid_filter_return(span: SpanId, body: DefId) -> InterpretDiagnostic {
diagnostic
}

fn value_serialization(span: SpanId, error: &serde_json::Error) -> InterpretDiagnostic {
let mut diagnostic = Diagnostic::new(category(&VALUE_SERIALIZATION), Severity::Bug)
fn value_serialization(span: SpanId, error: &serde_json::Error) -> OrchestratorDiagnostic {
let mut diagnostic = Diagnostic::new(category(&VALUE_SERIALIZATION), Critical::BUG)
.primary(Label::new(span, "cannot serialize runtime value to JSON"));

diagnostic.add_message(Message::note(format!("serialization failed: {error}")));
Expand Down
Loading
Loading