Skip to content

feat: support postconditions on Laurel functions#668

Open
fabiomadge wants to merge 2 commits intomainfrom
feat/function-postconditions
Open

feat: support postconditions on Laurel functions#668
fabiomadge wants to merge 2 commits intomainfrom
feat/function-postconditions

Conversation

@fabiomadge
Copy link
Copy Markdown
Contributor

@fabiomadge fabiomadge commented Mar 26, 2026

Adds support for function postconditions (both ensures clauses and constrained return types) via postcondition axioms and a new OrderedProgram intermediate representation.

Approach

Function postconditions are made available to callers via axioms rather than assume statements. This means postconditions work everywhere — including inside function bodies and quantifiers — without any call-site lifting.

A $check procedure is generated per function with postconditions to verify the body satisfies the postcondition.

Pipeline

Introduces OrderedProgram as an intermediate IR between Laurel.Program and Core.Program:

Program → toLaurelOrdered → generateAxioms → orderedToCore → Core.Program
  • toLaurelOrdered — structural analysis: SCC ordering, grouping, diagnostics
  • generateAxioms — populates postcondition and invokeOn axioms (can be skipped for a future testing mode)
  • orderedToCore — thin translation pass, no structural logic

Grammar

  • function f(): int ensures result > 0 { ... } — transparent, body visible to callers
  • opaque function f(): int ensures result > 0 { ... } — body hidden, callers only see postcondition via axiom
  • function f(): nat { ... } — constrained return type becomes a postcondition

AST change

  • Body.Transparent now carries a postconditions list

Key changes

  • OrderedProgram.lean / ToOrderedProgram.lean — new IR and structural analysis pass
  • FunctionPostcondCheck.lean — generates $check procedures (replaces old FunctionPostcondElim)
  • DeclGrouping.lean — renamed from CoreGroupingAndOrdering, no longer depends on Core types
  • LaurelToCoreTranslator.leanorderedToCore replaces monolithic translateLaurelToCore; op nodes annotated with function types at creation
  • All Laurel passes: preserve new Transparent postconditions field
  • Tests: new cases for opaque-calls-opaque, quantifier postconds, result shadowing

Known trade-off

Opaque function callers get "could not be proved" instead of counterexamples, since the symbolic evaluator cannot instantiate axioms as ground facts. The right fix is in the Core evaluator, which would benefit all frontends.

@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch 30 times, most recently from 258b65f to 65bc9af Compare March 26, 2026 07:09
@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch 7 times, most recently from 6305482 to 02d56a4 Compare March 31, 2026 03:16
@fabiomadge fabiomadge marked this pull request as ready for review March 31, 2026 03:27
@fabiomadge fabiomadge requested a review from a team March 31, 2026 03:27
Copy link
Copy Markdown
Contributor

@keyboardDrummer keyboardDrummer left a comment

Choose a reason for hiding this comment

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

Looking at the implementation:

  1. Inserting assume f$post(args, result) at call sites

I don't think that translation is executable. In general I think Laurel should not generate axioms or assume statements.

I think it's better if function postconditions are supported at the Core level, that way the Laurel->Core translation can remain back-end independent, and also then Core can handle everything related to postconditions, both for procedures and functions. There's an AWS internal ticket for adding them to Core.

@fabiomadge
Copy link
Copy Markdown
Contributor Author

How else do you propose to add postconditions than to assume them at the call site?

I need them to complete constrained types and think it's better to add them now and remove them later if we end up adding them to Core. Doing it at the Laurel level turned out to be reasonably simple.

@keyboardDrummer
Copy link
Copy Markdown
Contributor

keyboardDrummer commented Mar 31, 2026

How else do you propose to add postconditions than to assume them at the call site?

When the back-end has been decided, which is after translating to Core, I think that is the right thing to do.

think it's better to add them now and remove them later if we end up adding them to Core. Doing it at the Laurel level turned out to be reasonably simple.

Why not do the right thing right away? Why would it be easier to add them to Laurel than to Core? I think adding it to Laurel like you did it now breaks our executability promise.

I need them to complete constrained types

Do you have a particular end-target in mind?

@fabiomadge
Copy link
Copy Markdown
Contributor Author

When the back-end has been decided, which is after translating to Core, I think that is the right thing to do.

As of now, Core doesn't support postconditions on functions and there is no concrete plan on how to add them.

Why not do the right thing right away? Why would it be easier to add them to Laurel than to Core? I think adding it to Laurel like you did it now breaks our executability promise.

The kind of statement lifting this PR does could also work as an early Core pass, but I think it would be more consistent with the rest of the Core implementation to hold onto them, which makes it harder. I'm not sure how assuming something we've proven elsewhere is breaking any executability promises. I'd expect someone trying to execute the final Core program to ignore these assumptions, just like they would the postconditions.

Do you have a particular end-target in mind?

You mean what I would like to enable? Constrained types as return values in functions. You can see examples in the tests I added.

@keyboardDrummer
Copy link
Copy Markdown
Contributor

keyboardDrummer commented Apr 6, 2026

I'm not sure how assuming something we've proven elsewhere is breaking any executability promises. I'd expect someone trying to execute the final Core program to ignore these assumptions, just like they would the postconditions.

You're right. I think I might have incorrectly assumed you were also deleting the body of the function, to make it opaque, but that's not the case. Keeping the body and adding the assume at the call-site seems good from an executability perspective.


Another comment though: given that you're inserting an assume statement for calls to functions with postconditions, I imagine this does not work for calls to functions that occur inside a function. Is that right? Could we make that work as well? I'm thinking that axioms are like assume statements that can be used from inside functions as well. What if we replace the postcondition function with a postcondition axiom? That way, you also wouldn't need to lift calls to functions any more since you're not inserting assume statements.

Secondly, generating an axiom (or assume statement) and generating the check procedure per function, is something that I think makes sense when doing symbolic verification, but not when testing, so I think the Laurel pipeline should have an option to enable or disable doing these transformations, even though we don't have a testing mode.

To implement the above requires another change though, since Laurel doesn't have axioms.

An existing issue that we run into is that the top level declarations in Core are ordered, and mutually recursive functions and datatypes must be grouped together, but Laurel has no way of representing top level declarations in that way, so the reordering and grouping is done in the LaurelToCore translation phase, making it thicker than it should be.

I suggest we resolve both those issues by introducing a CoreWithStmtExpr IR, that is similar to Core, so it has axioms and allow ordering and grouping top level decls, but only has procedures, not functions. The procedure body is a Laurel StmtExpr. It might seem a bit heavy handed but I think it'll be worth it.

If you want I can also make the change of introducing CoreWithStmtExpr as a separate PR.

@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch 4 times, most recently from d98fb07 to 290d8f3 Compare April 8, 2026 07:13
@fabiomadge
Copy link
Copy Markdown
Contributor Author

Good catch on the assume-based approach — it worked at call sites but not inside function bodies or quantifier bodies, since assumes are statements and those contexts only allow expressions. The lifting pass was a workaround for that limitation, and it was complex and fragile.

Reworked based on your suggestions:

Axioms instead of assumes. Replaced the lifting + $post + assume machinery with postcondition axioms on functions. Postconditions now work everywhere, including inside functions and quantifiers. Added tests for opaque-calls-opaque, quantifiers, and result shadowing.

Intermediate IR. Introduced OrderedProgram (your CoreWithStmtExpr idea). Pipeline is now:

Program → toLaurelOrdered → generateAxioms → orderedToCore → Core.Program

Ordering/grouping, axiom generation, and Core translation are each a separate pass. generateAxioms can be skipped for a future testing mode.

Other cleanups: groupDatatypes no longer depends on Core types; op nodes annotated at creation (eliminating annotateOps workaround); renamed FunctionPostcondElimFunctionPostcondCheck.

Known trade-off: The axiom approach means the symbolic evaluator can't produce counterexamples for failing assertions that depend on opaque function postconditions — it reports "could not be proved" instead of "does not hold" with a concrete model. This is because axioms are universally quantified and the evaluator doesn't instantiate them as ground facts. The right fix is in the Core evaluator, which would benefit all frontends.

@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch 2 times, most recently from f910ba9 to de5024a Compare April 9, 2026 16:39
Add a new Laurel-to-Laurel pass (FunctionPostcondElim) that eliminates
function postconditions from both explicit ensures clauses and
constrained return types.

For each function with postconditions, the pass generates:
- A postcondition function (f$post)
- A check procedure ($check_f) that verifies the body
- assume statements at call sites

Functions remain functions in Core with transparent bodies.
Opaque functions (opaque keyword) hide the body from callers.
@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch from de5024a to b53d945 Compare April 9, 2026 16:53
…Program IR

- Introduce OrderedProgram as intermediate IR between Laurel.Program and
  Core.Program with topologically ordered/grouped declarations and axiom slots
- Replace lifting + $post function + assume machinery with postcondition
  axioms on functions (works inside functions and quantifiers)
- Split pipeline: toLaurelOrdered (structural) → generateAxioms → orderedToCore
- Annotate op nodes with function types at creation (eliminates annotateOps)
- groupDatatypes no longer depends on Core types
- Rename FunctionPostcondElim → FunctionPostcondCheck
- Rename CoreGroupingAndOrdering → DeclGrouping
- Fix FilterPrelude for new Transparent postconditions parameter
- Add tests: opaque-calls-opaque, quantifier postconds, result shadowing
@fabiomadge fabiomadge force-pushed the feat/function-postconditions branch from b53d945 to 71d6819 Compare April 9, 2026 16:59
@github-actions
Copy link
Copy Markdown

👋 Hi, @fabiomadge,
I detected conflicts against the base branch 🙊
You'll want to sync 🔄 your branch with upstream!


This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there.

seebees added a commit to seebees/Strata that referenced this pull request Apr 10, 2026
Merges Strata PR strata-org#668: function postconditions support via axioms
and OrderedProgram IR.

Conflict resolution:
- DL/Imperative and Core semantics: take theirs (new statement types)
- DatatypeGrouping, LaurelFormat, DetToNondetCorrect: accept deletion
  (restructured by feature branch)
- ConstrainedTypeElim: keep our field type resolution in composites
- LaurelToCoreTranslator: take theirs (new OrderedProgram pipeline),
  re-add mkReadFuncAxioms with typed .op nodes for readInt32/16/8
- LaurelGrammar: take theirs (comment update)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants