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
42 changes: 21 additions & 21 deletions crates/plotnik-lib/src/engine/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ use super::Value;

/// Debug-only type verification.
///
/// Panics with a pretty diagnostic if the value doesn't match the expected type.
/// Panics with a pretty diagnostic if the value doesn't match the declared type.
/// This is a no-op in release builds.
///
/// `expected_type` should be the `result_type` from the entrypoint that was executed.
/// `declared_type` should be the `result_type` from the entrypoint that was executed.
#[cfg(debug_assertions)]
pub fn debug_verify_type(value: &Value, expected_type: TypeId, module: &Module, colors: Colors) {
pub fn debug_verify_type(value: &Value, declared_type: TypeId, module: &Module, colors: Colors) {
let types = module.types();
let strings = module.strings();

let mut errors = Vec::new();
verify_type(
value,
expected_type,
declared_type,
&types,
&strings,
&mut String::new(),
&mut errors,
);
if !errors.is_empty() {
panic_with_mismatch(value, expected_type, &errors, module, colors);
panic_with_mismatch(value, declared_type, &errors, module, colors);
}
}

Expand All @@ -40,7 +40,7 @@ pub fn debug_verify_type(value: &Value, expected_type: TypeId, module: &Module,
#[inline(always)]
pub fn debug_verify_type(
_value: &Value,
_expected_type: TypeId,
_declared_type: TypeId,
_module: &Module,
_colors: Colors,
) {
Expand All @@ -50,16 +50,16 @@ pub fn debug_verify_type(
#[cfg(debug_assertions)]
fn verify_type(
value: &Value,
expected: TypeId,
declared: TypeId,
types: &TypesView<'_>,
strings: &StringsView<'_>,
path: &mut String,
errors: &mut Vec<String>,
) {
let Some(type_def) = types.get(expected) else {
let Some(type_def) = types.get(declared) else {
errors.push(format_error(
path,
&format!("unknown type id {}", expected.0),
&format!("unknown type id {}", declared.0),
));
return;
};
Expand All @@ -74,7 +74,7 @@ fn verify_type(
if !matches!(value, Value::Null) {
errors.push(format_error(
path,
&format!("expected void (null), found {}", value_kind_name(value)),
&format!("type: void, value: {}", value_kind_name(value)),
));
}
}
Expand All @@ -83,7 +83,7 @@ fn verify_type(
if !matches!(value, Value::Node(_)) {
errors.push(format_error(
path,
&format!("expected Node, found {}", value_kind_name(value)),
&format!("type: Node, value: {}", value_kind_name(value)),
));
}
}
Expand All @@ -92,7 +92,7 @@ fn verify_type(
if !matches!(value, Value::String(_)) {
errors.push(format_error(
path,
&format!("expected string, found {}", value_kind_name(value)),
&format!("type: string, value: {}", value_kind_name(value)),
));
}
}
Expand All @@ -101,7 +101,7 @@ fn verify_type(
if !matches!(value, Value::Node(_)) {
errors.push(format_error(
path,
&format!("expected Node (alias), found {}", value_kind_name(value)),
&format!("type: Node (alias), value: {}", value_kind_name(value)),
));
}
}
Expand All @@ -127,7 +127,7 @@ fn verify_type(
_ => {
errors.push(format_error(
path,
&format!("expected array, found {}", value_kind_name(value)),
&format!("type: array, value: {}", value_kind_name(value)),
));
}
}
Expand All @@ -140,7 +140,7 @@ fn verify_type(
if items.is_empty() {
errors.push(format_error(
path,
"expected non-empty array, found empty array",
"type: non-empty array, value: empty array",
));
}
for (i, item) in items.iter().enumerate() {
Expand All @@ -153,7 +153,7 @@ fn verify_type(
_ => {
errors.push(format_error(
path,
&format!("expected array, found {}", value_kind_name(value)),
&format!("type: array, value: {}", value_kind_name(value)),
));
}
}
Expand Down Expand Up @@ -191,7 +191,7 @@ fn verify_type(
_ => {
errors.push(format_error(
path,
&format!("expected object, found {}", value_kind_name(value)),
&format!("type: object, value: {}", value_kind_name(value)),
));
}
},
Expand Down Expand Up @@ -247,7 +247,7 @@ fn verify_type(
_ => {
errors.push(format_error(
path,
&format!("expected tagged union, found {}", value_kind_name(value)),
&format!("type: tagged union, value: {}", value_kind_name(value)),
));
}
},
Expand Down Expand Up @@ -318,7 +318,7 @@ fn centered_header(label: &str, width: usize) -> String {
#[cfg(debug_assertions)]
fn panic_with_mismatch(
value: &Value,
expected_type: TypeId,
declared_type: TypeId,
errors: &[String],
module: &Module,
colors: Colors,
Expand All @@ -333,7 +333,7 @@ fn panic_with_mismatch(
let type_name = (0..entrypoints.len())
.find_map(|i| {
let e = entrypoints.get(i);
if e.result_type == expected_type {
if e.result_type == declared_type {
Some(strings.get(e.name))
} else {
None
Expand All @@ -357,7 +357,7 @@ fn panic_with_mismatch(

panic!(
"\n{separator}\n\
TYPE MISMATCH: Query output does not match declared type\n\
BUG: Type and value do not match\n\
{separator}\n\n\
{type_str}\n\
{output_header}\n\n\
Expand Down
88 changes: 44 additions & 44 deletions crates/plotnik-lib/src/engine/verify_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ fn build_module(query: &str) -> (Module, TypeId) {
assert!(query_obj.is_valid(), "query should be valid");
let bytecode = emit_linked(&query_obj).expect("emit failed");
let module = Module::from_bytes(bytecode).expect("decode failed");
let expected_type = module.entrypoints().get(0).result_type;
(module, expected_type)
let declared_type = module.entrypoints().get(0).result_type;
(module, declared_type)
}

fn make_node() -> Value {
Expand All @@ -33,51 +33,51 @@ fn make_node() -> Value {

#[test]
fn verify_valid_node() {
let (module, expected_type) = build_module("Q = (identifier) @id");
let (module, declared_type) = build_module("Q = (identifier) @id");
let value = Value::Object(vec![("id".to_string(), make_node())]);

// Should not panic
debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_optional_present() {
let (module, expected_type) = build_module("Q = (identifier)? @id");
let (module, declared_type) = build_module("Q = (identifier)? @id");
let value = Value::Object(vec![("id".to_string(), make_node())]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_optional_null() {
let (module, expected_type) = build_module("Q = (identifier)? @id");
let (module, declared_type) = build_module("Q = (identifier)? @id");
let value = Value::Object(vec![("id".to_string(), Value::Null)]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_array() {
let (module, expected_type) = build_module("Q = (identifier)* @ids");
let (module, declared_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, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_empty_array() {
let (module, expected_type) = build_module("Q = (identifier)* @ids");
let (module, declared_type) = build_module("Q = (identifier)* @ids");
let value = Value::Object(vec![("ids".to_string(), Value::Array(vec![]))]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_enum() {
let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]");
let (module, declared_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![(
Expand All @@ -86,123 +86,123 @@ fn verify_valid_enum() {
)]))),
};

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_enum_void_variant() {
let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number)]");
let (module, declared_type) = build_module("Q = [A: (identifier) @x B: (number)]");
let value = Value::Tagged {
tag: "B".to_string(),
data: None,
};

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
fn verify_valid_string() {
let (module, expected_type) = build_module("Q = (identifier) @id :: string");
let (module, declared_type) = build_module("Q = (identifier) @id :: string");
let value = Value::Object(vec![("id".to_string(), Value::String("foo".to_string()))]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_node_is_string() {
let (module, expected_type) = build_module("Q = (identifier) @id");
let (module, declared_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, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_missing_required_field() {
let (module, expected_type) = build_module("Q = {(identifier) @a (number) @b}");
let (module, declared_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, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_array_element_wrong_type() {
let (module, expected_type) = build_module("Q = (identifier)* @ids");
let (module, declared_type) = build_module("Q = (identifier)* @ids");

// Array element is string instead of Node
let value = Value::Object(vec![(
"ids".to_string(),
Value::Array(vec![make_node(), Value::String("oops".to_string())]),
)]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_non_empty_array_is_empty() {
let (module, expected_type) = build_module("Q = (identifier)+ @ids");
let (module, declared_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, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_enum_unknown_variant() {
let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]");
let (module, declared_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, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_enum_void_with_data() {
let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number)]");
let (module, declared_type) = build_module("Q = [A: (identifier) @x B: (number)]");

// Void variant B has data when it shouldn't
let value = Value::Tagged {
tag: "B".to_string(),
data: Some(Box::new(Value::Object(vec![]))),
};

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
#[should_panic(expected = "BUG:")]
fn verify_invalid_enum_non_void_missing_data() {
let (module, expected_type) = build_module("Q = [A: (identifier) @x B: (number) @y]");
let (module, declared_type) = build_module("Q = [A: (identifier) @x B: (number) @y]");

// Non-void variant A missing data
let value = Value::Tagged {
tag: "A".to_string(),
data: None,
};

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}

#[test]
#[should_panic(expected = "TYPE MISMATCH")]
fn verify_invalid_expected_object_got_array() {
let (module, expected_type) = build_module("Q = (identifier) @id");
#[should_panic(expected = "BUG:")]
fn verify_invalid_object_vs_array() {
let (module, declared_type) = build_module("Q = (identifier) @id");

// Expected object, got array
// Type says object, value is array
let value = Value::Array(vec![make_node()]);

debug_verify_type(&value, expected_type, &module, Colors::OFF);
debug_verify_type(&value, declared_type, &module, Colors::OFF);
}