Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0fcdc25
Add PR template warning about repository split (#1166)
atomb May 14, 2026
99b84e5
lake: build Strata lib as a test-driver dependency (#1138)
tautschnig May 15, 2026
8acaa4b
The small-step semantics of Imperative must have scoped var init, rem…
aqjune-aws May 15, 2026
a5d36ed
Support SMT string literals and common string ops in translateTerm (#…
tautschnig May 15, 2026
50b0e12
feat: introduce Provenance type and migrate metadata from FileRange (…
MikaelMayer May 15, 2026
1d47569
Fix bug: ADT constructors do not change `Map` to `Array` when using u…
thanhnguyen-aws May 18, 2026
75e806e
Core.formatProgram to produce round-trip-parseable output for all con…
MikaelMayer May 18, 2026
192b780
Abstract Solver Interface: Decouple Term Construction from SMT-LIB En…
MikaelMayer May 18, 2026
6835d65
Fix #1146: require command_datatypes to be non-empty (#1155)
tautschnig May 18, 2026
f6d195a
Split StrataMain.lean into importable library and thin executable roo…
MikaelMayer May 18, 2026
7c3a3f0
Add resolution diagnostic for multi-output procedure in expression po…
tautschnig May 19, 2026
42fc6e9
Reject singleton operand lists in leftAssocOp and leftAssocOpBitVec (…
tautschnig May 19, 2026
18878bd
ANFEncoder: iterate to fixpoint to eliminate nested duplicates (#1135)
tautschnig May 19, 2026
53a3df5
ci: extract install-cvc5, install-z3, restore-lake-cache into composi…
tautschnig May 19, 2026
04d5215
Core: Sequence bounds preconditions (#1100)
fabiomadge May 19, 2026
0851e73
Add dialect_option typecheck off to bypass DDM type checker (#1125)
joehendrix May 19, 2026
1af9382
Structured pipeline diagnostics with PipelineM, phase timing, and --m…
joehendrix May 19, 2026
4c66905
Remove internal benchmark CI (#1188)
aqjune-aws May 19, 2026
b61880f
Replace empty module name strings with validated ModuleName type (#1151)
joehendrix May 20, 2026
cd1cac7
Fix namespace collision and SMACK assert encoding in BoogieToStrata (…
PROgram52bc May 20, 2026
7b72423
Parallel solving (#1046)
MikaelMayer May 20, 2026
4693781
Fix multi-output calls in expression position (Python front-end) (#1117)
tautschnig May 20, 2026
792abcc
Fix scoping bug in HeapParam pass (#1113)
keyboardDrummer May 20, 2026
a845a65
InferHoleTypes: recover param types for datatype destructors/testers …
tautschnig May 20, 2026
dee8093
Merge branch 'main' into main2
atomb May 20, 2026
9606877
Update expected test output
atomb May 20, 2026
94a008e
Fix SMT encoding for polymorphic Sequence preconditions (#1201)
May 21, 2026
f631f32
Cross-reference computeTypeSubst from callSiteTypeSubst
May 22, 2026
7346fe8
Fix Boole tests after merge (#1209)
joscoh May 22, 2026
44d88da
Merge branch 'merge-with-main-2026-05-20' into htd/issue-1201-smt-enc…
May 22, 2026
f94fafa
Merge branch 'main2' into htd/issue-1201-smt-encoding
atomb May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion Strata/DL/Lambda/Preconditions.lean
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ def substitutePrecondition
let substitution := formals.zip actuals |>.map fun ((name, _), actual) => (name, actual)
LExpr.substFvarsLifting precond substitution

/--
Derive a type substitution at a call site for instantiating a polymorphic
precondition. Prefers concrete information from the arguments' types over the
operator's type annotation, because the operator's annotation may still be the
generic type when `PrecondElim` runs before type checking. Falls back to the
operator annotation when arguments carry no type information.

Returns `Subst.empty` for monomorphic functions or when no useful constraints
are available; the resulting unsubstituted precondition will be handled
downstream (the SMT encoder treats unresolved type variables as uninterpreted
sorts, yielding `unknown` rather than failing).

Note: this is structurally similar to `LFunc.computeTypeSubst` in `Factory.lean`,
but with the priority *reversed*. `computeTypeSubst` prefers `.op` annotations
because it is called after type inference, where the `.op` always carries the
instantiated arrow type. Here we run before type checking, so the `.op` may
still carry the generic type — argument types are the more reliable source.
The two helpers cannot be unified by a flag without destabilising the
`computeTypeSubst`-based proofs in `Semantics.lean` (e.g.
`computeTypeSubst_of_opTypeSubst`).
-/
def callSiteTypeSubst (fn : LFunc T) (callee : LExpr T.mono)
(args : List (LExpr T.mono)) : Subst :=
if fn.typeArgs.isEmpty then Subst.empty
else
let argConstraints := (args.zip fn.inputs.values).filterMap
(fun (arg, formal) => arg.typeOf.map (·, formal))
let argSubst :=
if argConstraints.isEmpty then none
else match Constraints.unify argConstraints SubstInfo.empty with
| .ok substInfo => some substInfo.subst
| .error _ => none
match argSubst with
| some s => s
| none => fn.opTypeSubst callee |>.getD Subst.empty
Comment thread
PROgram52bc marked this conversation as resolved.

/--
Collect all WF obligations from an expression by traversing it and finding
all calls to functions with preconditions.
Expand All @@ -71,12 +107,19 @@ where
-- A function call generates an obligation that the precondition is
-- satisfied under the current assumptions
let callObligations := match Factory.callOfLFunc F e with
| some (_op, args, func) =>
| some (op, args, func) =>
if func.preconditions.isEmpty then []
else
let md := e.metadata
-- Resolve polymorphic type variables in the precondition with the
-- type substitution implied by this call site. Without this, a
-- precondition expression like `(~Sequence.length : (Sequence %a) → int) s`
-- would carry the formal type variable `%a` into the obligation,
-- which the SMT encoder cannot resolve (issue #1201).
let tySubst := callSiteTypeSubst func op args
func.preconditions.map fun precond =>
let substedPrecond := substitutePrecondition precond.expr func.inputs args
let substedPrecond := substedPrecond.applySubst tySubst
{ funcName := func.name.name
obligation := wrapImplications implications substedPrecond
callSiteMetadata := md
Expand Down
10 changes: 9 additions & 1 deletion Strata/Languages/Core/SMTEncoder.lean
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,15 @@ def LMonoTy.toSMTType (E: Env) (ty : LMonoTy) (ctx : SMT.Context) (useArrayTheor
| .ftvar tyv => match ctx.tySubst.find? tyv with
| .some termTy =>
.ok (termTy, ctx)
| _ => .error f!"Unimplemented encoding for type var {tyv}"
| _ =>
-- Unresolved type variable from a polymorphic precondition
-- whose call-site type couldn't be inferred (e.g. inside a
-- polymorphic function body, or with polymorphic args).
-- Treat as a fresh uninterpreted sort so the obligation can
-- still be encoded; the solver will report `unknown` rather
-- than the encoder failing outright (issue #1201).
let ctx := ctx.addSort { name := tyv, arity := 0 }
.ok ((.constr tyv []), ctx)

def LMonoTys.toSMTType (E: Env) (args : LMonoTys) (ctx : SMT.Context) (useArrayTheory : Bool := false) :
Except Format ((List TermType) × SMT.Context) := do
Expand Down
39 changes: 39 additions & 0 deletions StrataTest/DL/Lambda/PreconditionsTests.lean
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,43 @@ info: [WFObligation(safeDiv, (∀ (bvar:int) ((~Bool.Implies : (arrow bool (arro
#eval collectWFObligations testFactory
esM[((λ (int): %0) ((~safeDiv a) b))]

/-! ### Polymorphic preconditions: type substitution at call site

A polymorphic function whose precondition mentions the type variable `%a`
must have that variable substituted with the call site's instantiated type.
Without this, downstream SMT encoding fails on unresolved type vars (issue #1201).
-/

-- A polymorphic function `polySel<a>(s : Sequence a) : a` whose precondition
-- contains an operator annotated with the type variable `%a`. This mirrors
-- `Sequence.select`'s `0 <= i && i < Sequence.length(s : Sequence %a)`
-- bound check, which uses an annotated `Sequence.length` op.
private def polySelFunc : LFunc TestParams :=
{ name := "polySel"
typeArgs := ["a"]
inputs := [("s", mty[Sequence %a])]
output := mty[%a]
-- precondition: ((~lenOf : (Sequence %a) → int) s)
-- The op carries a `%a` annotation that must be substituted at the call site.
preconditions := [⟨esM[((~lenOf : (Sequence %a) → int) s)], ()⟩]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This precondition doesn't seem to have type bool. Am I missing something? It seems like it would exercise the treatment of polymorphism, but not otherwise be a sensible precondition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it is testing specifically on type substitution under polymorphism, trying to show that the %a type variable in the precondition gets substituted successfully (into int in the two examples below)? Without doing type substitution on the pre-conditions, this test should fail.

}

private def polyFactory : Factory TestParams := .ofArray #[polySelFunc]

-- Call site annotates the operator with the instantiated arrow type
-- `Sequence int → int`. After value substitution alone, the op annotation
-- `(~lenOf : (Sequence %a) → int)` would still carry `%a`; the fix must also
-- apply the call-site type substitution `[%a → int]`.
/-- info: [WFObligation(polySel, ((~lenOf : (arrow (Sequence int) int)) myseq), ())] -/
#guard_msgs in
#eval collectWFObligations polyFactory
esM[((~polySel : (Sequence int) → int) myseq)]

-- Same expectation when the operator is unannotated: the argument-types
-- fallback unifies `Sequence int` against `Sequence %a` to derive `[%a → int]`.
/-- info: [WFObligation(polySel, ((~lenOf : (arrow (Sequence int) int)) (myseq : (Sequence int))), ())] -/
#guard_msgs in
#eval collectWFObligations polyFactory
esM[(~polySel (myseq : (Sequence int)))]

end Lambda
Loading