Skip to content

Conversation

@joscoh
Copy link
Contributor

@joscoh joscoh commented Feb 12, 2026

Issue #, if available: #398

Description of changes: Performs type unification on generated VCs from (already typechecked) procedure pre and post conditions, so that polymorphic type variables are instantiated before SMT encoding.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@joscoh
Copy link
Contributor Author

joscoh commented Feb 12, 2026

CC: @shigoel

@joscoh joscoh marked this pull request as ready for review February 12, 2026 20:39
@joscoh joscoh requested a review from a team as a code owner February 12, 2026 20:39
@shigoel shigoel enabled auto-merge February 12, 2026 20:45
@shigoel shigoel added this pull request to the merge queue Feb 12, 2026
let actual_tys := args.filterMap getExprType
let lhs_tys := lhs.filterMap (fun l =>
(E.exprEnv.state.findD l (none, .fvar () l none)).fst)
let input_constraints := actual_tys.zip input_tys
Copy link
Contributor

Choose a reason for hiding this comment

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

I asked Claude to review and it had this to say:

  1. (Correctness bug) filterMap + zip can misalign type constraints — StatementEval.lean:148-152

  This is the most important thing to check. In computeTypeSubst:

  let actual_tys := args.filterMap getExprType
  ...
  let input_constraints := actual_tys.zip input_tys

  filterMap drops any arg where getExprType returns none, shrinking the list. zip then pairs by position, so if arg i has no type, arg i+1's type gets paired with formal
  type i, producing an incorrect constraint. For example, if args = [a, b, c] and getExprType yields [some T1, none, some T3], the constraints become [(T1, input_0), (T3,
  input_1)] instead of the intended [(T1, input_0), (T3, input_2)]. The same issue applies to lhs_tys/output_constraints. A zipWith/pairwise approach that keeps index
  alignment (e.g., zip first, then filter pairs where the type is some) would be safer.

It's obvious to me why the zip is ok either.

let constraints := input_constraints ++ output_constraints
match Constraints.unify constraints SubstInfo.empty with
| .ok substInfo => substInfo.subst
| .error _ => Subst.empty
Copy link
Contributor

Choose a reason for hiding this comment

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

More Claude:

 2. (Design) Silent failure on unification error — StatementEval.lean:154-156

  | .error _ => Subst.empty

  If unification fails, the code silently returns an empty substitution, meaning type variables remain uninstantiated in VCs sent to the SMT solver. Verify this is the
  desired behavior vs. propagating a diagnostic. At minimum, consider whether a warning should be emitted.

@@ -119,6 +163,10 @@ def Command.evalCall (E : Env) (old_var_subst : SubstMap)
(md : Imperative.MetaData Expression) : Command × Env :=
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add some comments about the relationships between the variables in old_var_subst , lhs and E? It's a little unclear to me which typing context the various expressions are in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically, we are given a term lhs := p(xs) where p takes in arguments of type input_tys and returns arguments of types output_tys. E should contain the types for the free variables in LHS (guaranteed by the StatementEval execution). I believe that old_var_subst has to do with var -> expression maps, not type substitutions. I can add a comment explaining this.

Compute type substitution by unifying actual argument types with input types
and LHS types with output types.
-/
private def computeTypeSubst (input_tys output_tys: List LMonoTy)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private def computeTypeSubst (input_tys output_tys: List LMonoTy)
private def computeTypeSubst (input_tys output_tys : List LMonoTy)

@joscoh
Copy link
Contributor Author

joscoh commented Feb 12, 2026

To Joe's comments - the correctness of both of these rely on the fact that the typechecker has already run on the constituent parts so all terms are annotated with their types. The unification must succeed because we are given precondition (or postcondition) p : t1 -> ... -> tn -> t and arguments of types t1, .., tn with LHS of type t. Eventually, we will want to prove this.

Merged via the queue into main with commit 4a3b3f5 Feb 12, 2026
15 checks passed
@shigoel shigoel deleted the josh/poly-precond-2 branch February 12, 2026 21:09
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.

4 participants