Skip to content

Add Counterexamples to Refinement Errors#146

Open
rcosta358 wants to merge 9 commits intomainfrom
counterexamples
Open

Add Counterexamples to Refinement Errors#146
rcosta358 wants to merge 9 commits intomainfrom
counterexamples

Conversation

@rcosta358
Copy link
Collaborator

@rcosta358 rcosta358 commented Feb 10, 2026

This PR adds counterexamples to refinement errors by extracting the SMT model from Z3.

Changes

  • Added the getCounterexample method to TranslatorToZ3 that extracts variable assignments (for constants only)
  • In the SMTEvaluator, when the SMT returns SAT we get the SMT model and throw the TypeCheckError containing the counterexample
  • In the caller methods, instead of returning boolean it now returns Counterexample - if counterexample is null, then the check succeeded, otherwise it failed
  • In the RefinementError, we override the getDetails method to add the counterexamples as extra information in the error message. Also, it filters out fresh variables which don't even exist in the source code, and if the counterexample is the same as the found type (already included in the message), then the counterexample is not shown.
  • In TranslatorToZ3, we keep track of the function applications and expressions sent to Z3 so we can reconstruct them from the counterexample when dealing with uninterpreted ghost functions

Examples

image

@rcosta358 rcosta358 self-assigned this Feb 10, 2026
@rcosta358 rcosta358 added enhancement New feature or request error messages labels Feb 10, 2026
Copy link
Collaborator

@CatarinaGamboa CatarinaGamboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! I think we are almost there. I've just left a comment on changing from boolean -> Counterexample for the return value, and returning null's. This isn't the best practice, see if you understand the comment and can make that change

}

@Override
public String getDetails() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldnt this be getCounterExample?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's the specific method from LJDiagnostic to provide additional information in the diagnostic message, so it appears like this:

Error:
(...)
---> <string returned by getDetails>

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can split it and call the getCounterexampleString() in the getDetails() because we might want to add more hints besides the counterexample in the future and don't want to mix it up.

* @throws LJError
*/
public boolean smtChecks(Predicate expected, Predicate found, SourcePosition position) throws LJError {
public Counterexample smtChecks(Predicate expected, Predicate found, SourcePosition position) throws LJError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that we just send the Counterexample and if it fails is null :( Maybe we can create an object for SMTResult with fields of boolean checks and List of assignements and functions like .smtChecks or similar and .getCounterExample that can be empty or not. What do you think?
Using null's can lead to nullpointers that we havent anticipated so it would be good to avoid them

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it only fails if the counterexample is not null (no counterexample found).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But yeah, we should avoid using nulls. I'll try to improve that using your suggestion.

Symbol name = decl.getName();
Expr<?> value = model.getConstInterp(decl);
// Skip values of uninterpreted sorts
if (value.getSort().getSortKind() != Z3_sort_kind.Z3_UNINTERPRETED_SORT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are uninterpreted sorts do we allow them? can you give an example

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are the ones that Z3 returns as e.g. !val!0 in the SMT model and we want to skip them there and only deal with them below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request error messages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants