From ba9781044798645288be11a9f4b6619bdd9ab495 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Sun, 4 Jan 2026 12:12:38 -0300 Subject: [PATCH] fix: `debug_verify_type` uses correct entrypoint for multi-def queries --- crates/plotnik-cli/src/commands/exec.rs | 2 +- crates/plotnik-cli/src/commands/trace.rs | 2 +- crates/plotnik-lib/src/engine/verify.rs | 47 +++++++---- crates/plotnik-lib/src/engine/verify_tests.rs | 78 ++++++++++--------- 4 files changed, 72 insertions(+), 57 deletions(-) diff --git a/crates/plotnik-cli/src/commands/exec.rs b/crates/plotnik-cli/src/commands/exec.rs index fc3bdbf0..2cf3c296 100644 --- a/crates/plotnik-cli/src/commands/exec.rs +++ b/crates/plotnik-cli/src/commands/exec.rs @@ -100,7 +100,7 @@ pub fn run(args: ExecArgs) { let colors = Colors::new(args.color); // Debug-only: verify output matches declared type - debug_verify_type(&value, &module, colors); + debug_verify_type(&value, entrypoint.result_type, &module, colors); let output = value.format(args.pretty, colors); println!("{}", output); diff --git a/crates/plotnik-cli/src/commands/trace.rs b/crates/plotnik-cli/src/commands/trace.rs index 0f9d5c02..e3424ffc 100644 --- a/crates/plotnik-cli/src/commands/trace.rs +++ b/crates/plotnik-cli/src/commands/trace.rs @@ -118,7 +118,7 @@ pub fn run(args: TraceArgs) { let value = materializer.materialize(effects.as_slice(), entrypoint.result_type); // Debug-only: verify output matches declared type - debug_verify_type(&value, &module, colors); + debug_verify_type(&value, entrypoint.result_type, &module, colors); let output = value.format(true, colors); println!("{}", output); diff --git a/crates/plotnik-lib/src/engine/verify.rs b/crates/plotnik-lib/src/engine/verify.rs index ccfa9979..24bc905e 100644 --- a/crates/plotnik-lib/src/engine/verify.rs +++ b/crates/plotnik-lib/src/engine/verify.rs @@ -14,18 +14,12 @@ use super::Value; /// /// Panics with a pretty diagnostic if the value doesn't match the expected type. /// This is a no-op in release builds. +/// +/// `expected_type` should be the `result_type` from the entrypoint that was executed. #[cfg(debug_assertions)] -pub fn debug_verify_type(value: &Value, module: &Module, colors: Colors) { +pub fn debug_verify_type(value: &Value, expected_type: QTypeId, module: &Module, colors: Colors) { let types = module.types(); let strings = module.strings(); - let entrypoints = module.entrypoints(); - - // Get the first entrypoint's result type for verification - if entrypoints.is_empty() { - return; - } - let entrypoint = entrypoints.get(0); - let expected_type = entrypoint.result_type; let mut errors = Vec::new(); verify_type( @@ -37,14 +31,20 @@ pub fn debug_verify_type(value: &Value, module: &Module, colors: Colors) { &mut errors, ); if !errors.is_empty() { - panic_with_mismatch(value, &errors, module, colors); + panic_with_mismatch(value, expected_type, &errors, module, colors); } } /// No-op in release builds. #[cfg(not(debug_assertions))] #[inline(always)] -pub fn debug_verify_type(_value: &Value, _module: &Module, _colors: Colors) {} +pub fn debug_verify_type( + _value: &Value, + _expected_type: QTypeId, + _module: &Module, + _colors: Colors, +) { +} /// Recursive type verification. Collects mismatch paths into `errors`. #[cfg(debug_assertions)] @@ -316,17 +316,30 @@ fn centered_header(label: &str, width: usize) -> String { /// Panic with a pretty diagnostic showing the type mismatch. #[cfg(debug_assertions)] -fn panic_with_mismatch(value: &Value, errors: &[String], module: &Module, colors: Colors) -> ! { +fn panic_with_mismatch( + value: &Value, + expected_type: QTypeId, + errors: &[String], + module: &Module, + colors: Colors, +) -> ! { const WIDTH: usize = 80; let separator = "=".repeat(WIDTH); let entrypoints = module.entrypoints(); let strings = module.strings(); - let type_name = if !entrypoints.is_empty() { - strings.get(entrypoints.get(0).name) - } else { - "unknown" - }; + + // Find the entrypoint name by matching result_type + let type_name = (0..entrypoints.len()) + .find_map(|i| { + let e = entrypoints.get(i); + if e.result_type == expected_type { + Some(strings.get(e.name)) + } else { + None + } + }) + .unwrap_or("unknown"); let config = Config { export: true, diff --git a/crates/plotnik-lib/src/engine/verify_tests.rs b/crates/plotnik-lib/src/engine/verify_tests.rs index 674314b0..22095ce5 100644 --- a/crates/plotnik-lib/src/engine/verify_tests.rs +++ b/crates/plotnik-lib/src/engine/verify_tests.rs @@ -1,15 +1,15 @@ //! Tests for debug type verification. -use crate::Colors; -use crate::QueryBuilder; -use crate::bytecode::Module; +use crate::bytecode::{Module, QTypeId}; use crate::emit::emit_linked; use crate::engine::value::{NodeHandle, Value}; +use crate::Colors; +use crate::QueryBuilder; use super::debug_verify_type; -/// Build a module from a query string. -fn build_module(query: &str) -> Module { +/// Build a module from a query string and return with its first entrypoint's result type. +fn build_module(query: &str) -> (Module, QTypeId) { let lang = plotnik_langs::javascript(); let query_obj = QueryBuilder::one_liner(query) .parse() @@ -18,7 +18,9 @@ fn build_module(query: &str) -> Module { .link(&lang); assert!(query_obj.is_valid(), "query should be valid"); let bytecode = emit_linked(&query_obj).expect("emit failed"); - Module::from_bytes(bytecode).expect("decode failed") + let module = Module::from_bytes(bytecode).expect("decode failed"); + let expected_type = module.entrypoints().get(0).result_type; + (module, expected_type) } fn make_node() -> Value { @@ -31,51 +33,51 @@ fn make_node() -> Value { #[test] fn verify_valid_node() { - let module = build_module("Q = (identifier) @id"); + let (module, expected_type) = build_module("Q = (identifier) @id"); let value = Value::Object(vec![("id".to_string(), make_node())]); // Should not panic - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_optional_present() { - let module = build_module("Q = (identifier)? @id"); + let (module, expected_type) = build_module("Q = (identifier)? @id"); let value = Value::Object(vec![("id".to_string(), make_node())]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_optional_null() { - let module = build_module("Q = (identifier)? @id"); + let (module, expected_type) = build_module("Q = (identifier)? @id"); let value = Value::Object(vec![("id".to_string(), Value::Null)]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_array() { - let module = build_module("Q = (identifier)* @ids"); + let (module, expected_type) = build_module("Q = (identifier)* @ids"); let value = Value::Object(vec![( "ids".to_string(), Value::Array(vec![make_node(), make_node()]), )]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_empty_array() { - let module = build_module("Q = (identifier)* @ids"); + let (module, expected_type) = build_module("Q = (identifier)* @ids"); let value = Value::Object(vec![("ids".to_string(), Value::Array(vec![]))]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_enum() { - let module = build_module("Q = [A: (identifier) @x B: (number) @y]"); + let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]"); let value = Value::Tagged { tag: "A".to_string(), data: Some(Box::new(Value::Object(vec![( @@ -84,54 +86,54 @@ fn verify_valid_enum() { )]))), }; - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_enum_void_variant() { - let module = build_module("Q = [A: (identifier) @x B: (number)]"); + let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number)]"); let value = Value::Tagged { tag: "B".to_string(), data: None, }; - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] fn verify_valid_string() { - let module = build_module("Q = (identifier) @id :: string"); + let (module, expected_type) = build_module("Q = (identifier) @id :: string"); let value = Value::Object(vec![("id".to_string(), Value::String("foo".to_string()))]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_node_is_string() { - let module = build_module("Q = (identifier) @id"); + let (module, expected_type) = build_module("Q = (identifier) @id"); // id should be Node, but we provide string let value = Value::Object(vec![("id".to_string(), Value::String("wrong".to_string()))]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_missing_required_field() { - let module = build_module("Q = {(identifier) @a (number) @b}"); + let (module, expected_type) = build_module("Q = {(identifier) @a (number) @b}"); // Missing field 'b' let value = Value::Object(vec![("a".to_string(), make_node())]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_array_element_wrong_type() { - let module = build_module("Q = (identifier)* @ids"); + let (module, expected_type) = build_module("Q = (identifier)* @ids"); // Array element is string instead of Node let value = Value::Object(vec![( @@ -139,37 +141,37 @@ fn verify_invalid_array_element_wrong_type() { Value::Array(vec![make_node(), Value::String("oops".to_string())]), )]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_non_empty_array_is_empty() { - let module = build_module("Q = (identifier)+ @ids"); + let (module, expected_type) = build_module("Q = (identifier)+ @ids"); // Non-empty array but we provide empty let value = Value::Object(vec![("ids".to_string(), Value::Array(vec![]))]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_enum_unknown_variant() { - let module = build_module("Q = [A: (identifier) @x B: (number) @y]"); + let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]"); let value = Value::Tagged { tag: "C".to_string(), // Unknown variant data: None, }; - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_enum_void_with_data() { - let module = build_module("Q = [A: (identifier) @x B: (number)]"); + let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number)]"); // Void variant B has data when it shouldn't let value = Value::Tagged { @@ -177,13 +179,13 @@ fn verify_invalid_enum_void_with_data() { data: Some(Box::new(Value::Object(vec![]))), }; - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_enum_non_void_missing_data() { - let module = build_module("Q = [A: (identifier) @x B: (number) @y]"); + let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]"); // Non-void variant A missing data let value = Value::Tagged { @@ -191,16 +193,16 @@ fn verify_invalid_enum_non_void_missing_data() { data: None, }; - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); } #[test] #[should_panic(expected = "TYPE MISMATCH")] fn verify_invalid_expected_object_got_array() { - let module = build_module("Q = (identifier) @id"); + let (module, expected_type) = build_module("Q = (identifier) @id"); // Expected object, got array let value = Value::Array(vec![make_node()]); - debug_verify_type(&value, &module, Colors::OFF); + debug_verify_type(&value, expected_type, &module, Colors::OFF); }