Skip to content

Laurel Language Enhancements#385

Open
fabiomadge wants to merge 294 commits intomainfrom
jverify-strata-backend
Open

Laurel Language Enhancements#385
fabiomadge wants to merge 294 commits intomainfrom
jverify-strata-backend

Conversation

@fabiomadge
Copy link
Contributor

Summary

Expands the Laurel language with additional operators, data types, and verification constructs. Improves formatting across multiple dialects.

Key Changes

Laurel Grammar & Translator

  • New operators: -, *, /, %, /t, %t (truncating), ==>, !, unary -
  • Array support: Array<T> type, indexing, length
  • Sequence operations: Seq.From, Seq.Take, Seq.Drop, Seq.Contains
  • Constrained types with constraint injection into quantifiers
  • Quantifiers: forall, exists
  • While loops with multiple invariants
  • Multiple requires/ensures clauses per procedure
  • Metadata (StmtExprMd, HighTypeMd) threaded through AST

Infrastructure

  • NewlineSepBy separator type for formatting preservation
  • Java codegen: list separator preservation, SourceRange overloads
  • Improved pretty printing for C_Simp, B3, Core

CLI Commands

  • laurelParse, laurelAnalyze, laurelToCore, laurelPrint

Bug Fixes

  • Fixed insideCondition flag not resetting after conditionals
  • Fixed SMT encoding for multi-argument functions and Map type
  • Added substFvarLifting for proper de Bruijn index handling

Tests

  • Reorganized Laurel tests T01-T17
  • Added regression test for assignment lifting

Add HighTypeMd.sizeOf_val_lt lemma and use it to prove translateType
terminates. The remaining partial defs recurse through List StmtExprMd
which requires the same sizeOf bridging pattern that is a known issue
across the codebase (see PR #396).
Make translateTypeWithCT, translateSimpleExpr, translateSeqBounds,
translateExpr, collectConstrainedArrayAccesses, translateStmt, and
isPureExpr total using the match _h : e.val pattern with
StmtExprMd.sizeOf_val_lt + rw/simp in decreasing_by.

For list recursion (args.mapM, args.all, args.flatMap), use
args.attach.{mapM,all,flatMap} to provide membership proofs,
combined with List.sizeOf_lt_of_mem.

Only resolveBaseType remains partial (needs acyclicity assumption
on ConstrainedTypeMap).
…ion proofs

Replace 6 identical decreasing_by blocks with a shared tactic macro.
Also unify the two HighTypeMd decreasing_by proofs.
@fabiomadge fabiomadge marked this pull request as ready for review February 10, 2026 19:50
@fabiomadge fabiomadge requested a review from a team as a code owner February 10, 2026 19:50
Resolve conflicts from bab0508 'Add metadata to more places in Laurel':
- Adopt main's WithMetadata generic struct with HighTypeMd/StmtExprMd abbrevs
- Keep branch's richer AST: plural pre/postconditions/invariants, DivT/ModT/Implies ops
- Keep branch's Determinism removal (was dead code)
- Keep branch's .reverse bug fix in transformProcedureBody
- Update sizeOf_val_lt references to use WithMetadata.sizeOf_val_lt
- Add deriving Repr for HighType and WithMetadata
Resolve conflicts from c0409bb 'Add tactic for simplifying termination proofs':
- Use term_by_mem where applicable in Laurel termination proofs
- Keep manual proofs where term_by_mem can't handle the goal
  (HighType recursion, attach patterns)
inputs : List Parameter
outputs : List Parameter
precondition : WithMetadata StmtExpr
determinism : Determinism
Copy link
Contributor

Choose a reason for hiding this comment

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

What happened to determinism?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intentionally removed in 67931730 — it was only ever set to .nondeterministic and never pattern-matched on.

Copy link
Contributor

Choose a reason for hiding this comment

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

I know it wasn't used but that doesn't mean it shouldn't be there. The whole AST wasn't used when it was first merged. Can you bring it back?


theorem WithMetadata.sizeOf_val_lt {t : Type} [SizeOf t] (e : WithMetadata t) : sizeOf e.val < sizeOf e := by
cases e; grind
cases e <;> simp_wf <;> omega
Copy link
Contributor

Choose a reason for hiding this comment

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

Merge accident?

Copy link
Contributor Author

@fabiomadge fabiomadge Feb 12, 2026

Choose a reason for hiding this comment

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

Intentional. omega is simpler and more predictable than grind here, and it proves the goal.

| _ => none

/-- Collect Array.Get accesses with constrained element types -/
def collectConstrainedArrayAccesses (env : TypeEnv) (tcMap : TranslatedConstraintMap) (e : StmtExprMd) : List (StmtExprMd × StmtExprMd × TranslatedConstraint) :=
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the implementation of constraints be independent of the implementation of Arrays? If this is temporary code, can we mark it as such?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO. Array.Get is special because it's a built-in (SMT select), not a procedure call, so there's no contract to assume from. Other constrained type sources (procedure returns, field reads) get their assumes via contracts. If more built-ins return constrained types in the future, this should be generalized.

let belse ← match elseBranch with
| some e => do let (_, s) ← translateStmt ctMap tcMap env outputParams postconds e; pure s
| none => pure []
pure (env, arrayElemAssumes ++ [Imperative.Stmt.ite bcond bthen belse stmt.md])
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the translation of IfThenElse refer to arrayElemAssumes? That can't be right

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in fb9c7f60. Compound statements (Block, IfThenElse, While) no longer generate arrayElemAssumes at the top level. Their sub-statements handle their own assumes via recursive translateStmt calls.

(e', acc ++ ss)) (env, [])
(env', stmtsList)
| .LocalVariable name ty initializer =>
def translateStmt (ctMap : ConstrainedTypeMap) (tcMap : TranslatedConstraintMap) (env : TypeEnv) (outputParams : List Parameter) (postconds : List (String × Core.Expression.Expr)) (stmt : StmtExprMd) : Except String (TypeEnv × List Core.Statement) := do
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would you introduce assert statements related to postconditions? Core will already check that procedure bodies adhere to the postconditions of that procedure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These asserts are needed for early returns. Laurel's return is translated to result := value; assert postconditions; assume false. The assume false prevents fall-through but also makes the path vacuously true, so Core's VCG won't check postconditions on it.

Without the asserts, a postcondition-violating early return passes verification silently. I verified this by writing a Core program where the early return path sets result := -x (violating ensures result >= 0):

  • Without the assert before assume false: ✅ pass (unsound)
  • With the assert before assume false: ❌ fail (correct)

Copy link
Contributor

Choose a reason for hiding this comment

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

Laurel's return is translated to result := value; assert postconditions; assume false

Is that new? It's not mentioned in the PR. I think it would be better to encode a return using a jump than an assume false. With the assume false it would be incorrect when executing the code, since then the assume is ignored, and having to assert the postconditions introduces a lot of duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right that goto would be better. I tried implementing it (return valueresult := value; goto "\$return" with \$return: {} at the end of the body), and it works correctly for individual procedures.

However, I found a Core VCG issue: when a procedure has a goto inside an if branch, processIteBranches produces multiple EnvWithNext results, and these multiply across procedures via ProgramEval.flatMap. A second procedure's assertions get checked N times instead of once.

Filed as #419 with a proposed fix (merge paths by nextLabel in processIteBranches). Keeping assume false for now until that's resolved.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would rather not have the assert <postcondition>; assume false solution merged, for two reasons:

  • It duplicates the postconditions for each return so it breaks our goal of having transformations that are linear in size.
  • It unnecessarily complicates our code and the intermediate programs, slowing down development.

If we can't implement it with goto due to a Core bug, could you move the related feature to another PR so we can wait for the Core issue to be resolved?

fabiomadge and others added 5 commits February 12, 2026 21:06
…aints

- Don't generate arrayElemAssumes for compound statements (Block, IfThenElse,
  While). Their sub-statements handle their own assumes via recursive
  translateStmt calls. Previously, assumes for array accesses inside branches
  were incorrectly hoisted before the if/while statement.
- Add TODO comment explaining why constraint injection is array-specific:
  Array.Get is a built-in (SMT select), not a procedure call, so there's
  no contract to assume from.
Copy link
Contributor

@joehendrix joehendrix left a comment

Choose a reason for hiding this comment

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

I added a few suggestions, but this looks reasonable to me. I'll approve this if you don't like the suggestions.

- Parser.lean: use Id.run do + early return for comment parsing, replace
  startsWith with utf8ByteSize check (comment 6)
- Java/Gen.lean: match on DDM category name (Init.Num/Init.Decimal) instead
  of Java type string for builder parameter generation (comment 7)
- Format.lean: collapse comma/space/newline seq formatting into single
  sepBy arm, eliminating duplication (comment 8)
- Java/Gen.lean: consolidate getSeparator with SepFormat.toIonName by
  adding fromCategoryName? and moving Ion-specific functions to
  Strata.DDM.Ion (comment 9)
@fabiomadge fabiomadge force-pushed the jverify-strata-backend branch from 80b2cc2 to 88d1a43 Compare February 13, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants