-
Notifications
You must be signed in to change notification settings - Fork 86
Proof checker #764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ezr-oflatt-refactor-proofs-4
Are you sure you want to change the base?
Proof checker #764
Conversation
* use Arc for SourceExpr variable names * Revert "use Arc for SourceExpr variable names" This reverts commit 9269488. * working on proof api test * new proof api test * get matches * sourcevar pubcrate * working on get matches * working on query_and_explain test * working on get matches * move to egraph_operations.rs * prelude fix * add todos * improve docs * small fix * working on new way to implement query * worked on get_matches test * working get_matches * fix build and new api endpoint
There are still issues with unbound variables though, I believe due to how primitives work.
Revert "use Arc for SourceExpr variable names" This reverts commit 9269488. working on proof api test get matches working on get matches working on query_and_explain test working on get matches move to egraph_operations.rs prelude fix add todos improve docs small fix working on new way to implement query worked on get_matches test working get_matches fix build and new api endpoint renames fix nits add proof checker adaptation proof checker draft new proof not supported code testing global insertion fix up check proof move code around prim validation some fixes small fixes remove old file Add helper functions for collecting query columns from PR #741 Added to_surface() and collect_subexprs_preorder() methods to ResolvedExprExt and collect_query_columns() function. These helpers collect all subexpressions (not just variables) from resolved query facts, which is useful for operations that need to capture the full structure of matched terms. This is based on the approach from PR #741 for the fresh! sugar feature. Add allow(dead_code) to new helper functions These helpers are intended for future use when we need to collect all subexpressions from queries, not just variables. Move proof_checker tests to separate file Moved all proof_checker tests from src/proof_checker.rs to src/proof_checker/tests.rs to improve code organization and readability. The main proof_checker module is now ~450 lines down from ~1400 lines. All 39 tests still pass. prove-query prove query, remove bad tests in tests.rs working on real proof checker new proof checker test rename add new literal prim macro working on test cleanup testing some fixes better prim resolve refactor again try again try again on literal macro imports another try at testing literal convertible tests refactor another try at checker fix build erros small fix proof checker cleaner duplicate validator detection Update get_matches documentation to clarify all variables are captured - Updated documentation to emphasize that ALL variables from both sides of equality constraints are captured in matches - Made it clear that variables like 'x' and 'y' from expressions within equality patterns are accessible in the results - Added explicit comments explaining where each variable comes from - Formatted code to pass make nits Use facts! macro in tests and remove ALL emphasis in docs Add comprehensive proof API tests and fix formatting Make proof format accessible in egglog API and fix formatting another cleanup current diff small changes small fix working on todos getting closer on prims small cleanup another improvement many improvements oops new tests test changes small changes test deletion with duplicate tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a comprehensive proof checking system for egglog, enabling validation of proofs generated during e-graph operations. The implementation adds infrastructure for registering validators for primitive operations, checking proofs at various levels (term proofs, equality proofs, rule applications), and provides a new prove-query command for generating and verifying proofs.
Key changes include:
- New proof checker with support for validating different proof types (PRule, PCong, PFiat, PRefl, PSym, PTrans)
- Automatic validator generation via the
add_literal_prim!macro for common primitive operations - Type inference system for proof validation
- Support for proof checking mode via
EGraph::with_proof_checking()
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_validators.rs | Comprehensive tests for primitive validators and the add_literal_prim macro |
| tests/prove_query_test.rs | Tests for the new prove_query functionality with various query patterns |
| tests/proof_checker_tests.rs | End-to-end tests for proof checker validation of rules, congruence, and primitives |
| tests/proof_api/tests/manual_proofs.rs | Tests for manual proof construction and validation |
| tests/proof_api/tests/query_and_explain.rs | Updated to reflect new behavior where equality constraint variables are included in matches |
| tests/proof_api/tests/query_proof.rs | Deleted file (functionality moved to prove_query_test.rs) |
| tests/files.rs | Refactored proof testing infrastructure with separate with_proofs and proof_checking modes |
| src/typechecking.rs | Extended PrimitiveWithId to support validators and output types; added validator registration methods |
| src/proof_checker.rs | Core proof checker implementation with proposition checking and rule validation |
| src/proof_check.rs | Standalone proof checking API that doesn't require running programs |
| src/proof_support.rs | Utilities for checking if programs support proof mode |
| src/proof_type_inference.rs | Type inference context for validating terms in proofs |
| src/literal_convertible.rs | Trait for converting between Rust types and egglog Literals |
| src/sort/i64.rs | Updated integer primitives to use add_literal_prim for automatic validator generation |
| src/sort/f64.rs | Updated float primitives to use add_literal_prim for automatic validator generation |
| src/sort/bool.rs | Updated boolean primitives to use add_literal_prim for automatic validator generation |
| src/sort/add_primitive/src/lib.rs | New macros: add_primitive_with_validator and add_literal_prim for validator support |
| src/lib.rs | Added proof_checking_enabled flag, ProveQuery command support, and new error types |
| src/egraph_operations.rs | Renamed get_proof to prove_query and updated documentation |
| src/core.rs | Updated SpecializedPrimitive field access and improved error messages for ambiguous resolution |
| src/prelude.rs | Added exports for LiteralConvertible, ProofStore, and proof types |
| src/serialize.rs | Removed unnecessary dead_code annotation |
| src/ast/*.rs | Added ProveQuery command throughout AST infrastructure |
| src/constraint.rs | Updated to use new PrimitiveWithId structure |
| egglog-bridge/src/termdag.rs | Added helper methods and explicit Default implementation |
| egglog-bridge/src/proof_format.rs | Extended ProofStore with accessor methods and added PFiat equality proof |
| egglog-bridge/src/proof_spec.rs | Minor formatting improvement |
| core-relations/src/query.rs | Removed unnecessary dead_code annotation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if resolved_call.is_empty() { | ||
| panic!( | ||
| "No resolution found for '{}' with types: {:?}", | ||
| head, | ||
| types.iter().map(|s| s.name()).collect::<Vec<_>>() | ||
| ); | ||
| } | ||
|
|
||
| if resolved_call.len() > 1 { | ||
| let mut msg = format!( | ||
| "Ambiguous resolution for '{}' with types: {:?}\n", | ||
| head, | ||
| types.iter().map(|s| s.name()).collect::<Vec<_>>() | ||
| ); | ||
| msg.push_str("Found multiple matching primitives/functions:\n"); | ||
| for rc in &resolved_call { | ||
| match rc { | ||
| ResolvedCall::Func(f) => { | ||
| msg.push_str(&format!(" - Function: {}\n", f.name)); | ||
| } | ||
| ResolvedCall::Primitive(p) => { | ||
| msg.push_str(&format!( | ||
| " - Primitive: {} (inputs: {:?}, output: {})\n", | ||
| p.primitive.primitive.name(), | ||
| p.input.iter().map(|s| s.name()).collect::<Vec<_>>(), | ||
| p.output.name() | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| panic!("{}", msg); | ||
| } |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic messages in this section (lines 129-159) are more detailed than typical panic messages, which is good. However, panicking on ambiguous resolution might be too aggressive - consider whether returning an error would be more appropriate for library code, allowing callers to handle the ambiguity gracefully.
src/egraph_operations.rs
Outdated
| for (target_var, target_sort) in query_vars { | ||
| let constructor_name = self.parser.symbol_gen.fresh("get_proof_ctor"); | ||
| let ruleset_name = self.parser.symbol_gen.fresh("get_proof_ruleset"); | ||
| let rule_name = self.parser.symbol_gen.fresh("get_proof_rule"); |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name get_proof has been renamed to prove_query, but the docstring still refers to the old name "get_proof_rule" in the function implementation. The variable rule_name is generated using get_proof_rule which is inconsistent with the new function name.
src/proof_checker.rs
Outdated
| /// An external proof checker would have trouble with this step, so we should consider adding type annotations | ||
| /// to the egglog |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment "An external proof checker would have trouble with this step" on line 805 suggests a limitation but doesn't explain why or what could be done about it. Consider expanding this comment to explain the issue more clearly, or remove it if it's not actionable.
| /// An external proof checker would have trouble with this step, so we should consider adding type annotations | |
| /// to the egglog | |
| /// Note: An external proof checker would have trouble with this step because type information for variables | |
| /// is inferred from the rule body, which may not be available or easily reconstructed outside of this context. | |
| /// To make external proof checking feasible, consider adding explicit type annotations to the egglog proofs | |
| /// or including variable type information in the proof format. |
| // TODO currently using Push and Pop to avoid touching the e-graph here. | ||
| // we should either make push and pop fast or rewrite to clean up the new table and rule. |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "TODO currently using Push and Pop to avoid touching the e-graph here." This suggests the implementation may be temporary or could be optimized. Consider filing an issue to track this work or implementing the optimization if feasible.
src/proof_checker.rs
Outdated
| GenericAction::Let(_, name, expr) => { | ||
| // Let creates a term | ||
| let _ = name; // Suppress unused warning |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic message on line 909 uses an underscore prefix for the name variable to suppress an unused warning, but then includes a comment explaining it. Consider either using the variable in a debug assertion or removing both the variable and comment for clarity.
| GenericAction::Let(_, name, expr) => { | |
| // Let creates a term | |
| let _ = name; // Suppress unused warning | |
| GenericAction::Let(_, _, expr) => { | |
| // Let creates a term |
| .map_err(|e| Error::BackendError(e.to_string()))?; | ||
| log::info!("Proof:\n{}", String::from_utf8_lossy(&buf)); | ||
| } else { | ||
| return Err(Error::BackendError("No proof found for query".to_string())); |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message format is inconsistent. Some error constructors use format! while others just use string literals. For example, line 1157 uses a raw string while line 1184 uses to_string(). Consider standardizing the error message format.
| return Err(Error::BackendError("No proof found for query".to_string())); | |
| return Err(Error::BackendError(format!("No proof found for query"))); |
| /// Register a validation function for a primitive. | ||
| /// This associates the validator with the LAST primitive added with the given name. | ||
| /// For overloaded primitives, use add_primitive_validator_typed instead to specify | ||
| /// the exact primitive by its input and output types. | ||
| /// Validators are used in proof-checking mode to verify primitive computations. | ||
| pub fn add_primitive_validator(&mut self, name: &str, validator: PrimitiveValidator) { | ||
| if let Some(primitives) = self.type_info.primitives.get_mut(name) { | ||
| if let Some(last) = primitives.last_mut() { | ||
| if last.validator.is_some() { | ||
| panic!( | ||
| "Duplicate validator for primitive '{}'. A validator is already registered.", | ||
| name | ||
| ); | ||
| } | ||
| last.validator = Some(validator); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for add_primitive_validator states it associates the validator with the "LAST primitive added" but doesn't explain what happens if called multiple times or what the expected usage pattern is. Consider adding examples or clarifying when this should be used versus add_primitive_validator_typed.
| /// Apply a substitution to an expression, replacing variables with terms | ||
| /// Unified expression evaluation that handles both variables and globals | ||
| /// The environment can contain both substituted variables and global definitions | ||
| /// This is used by queries, actions, and global evaluation |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment "This is used by queries, actions, and global evaluation" on line 167 is somewhat vague. Consider being more specific about what scenarios this unified function handles and why it's important that it's unified.
| /// This is used by queries, actions, and global evaluation | |
| /// This function is invoked during the evaluation of queries (e.g., when checking if a query holds), | |
| /// actions (such as let-bindings and unions in the proof script), and when processing global definitions. | |
| /// Unifying expression evaluation in this function ensures that all parts of the proof checker interpret | |
| /// expressions in a consistent manner, preventing subtle bugs that could arise from divergent evaluation logic. | |
| /// If this logic were duplicated or fragmented, inconsistencies could lead to incorrect proof validation. |
| if last.validator.is_some() { | ||
| panic!( | ||
| "Duplicate validator for primitive '{}'. A validator is already registered.", | ||
| name | ||
| ); | ||
| } |
Copilot
AI
Dec 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic on line 210 says "Duplicate validator" but the condition checks if a validator already exists. This will fire even on the first attempt to add a validator to a primitive that already has one (perhaps from a previous call). Consider whether this should be a panic or return an error, and clarify the error message to indicate this is about attempting to register a second validator.
CodSpeed Performance ReportMerging #764 will degrade performances by 5.2%Comparing Summary
Benchmarks breakdown
Footnotes
|
|
It says you requested a review from me but you dont want one yet right? |
|
Yeah it's a draft, no need to review yet (sorry for confusion) |
No description provided.