Integer-indexed types - #1065
Conversation
Mechanical migration of every call site to the new
ty_params = {idxvars; tyvars} record and targs = {indices; types}
record introduced in b15e335. Repairs the broken gen_op/mk_op
bodies in ecDecl and the syntax error in ecInductive (c413f37 WIP).
No semantic change: indices are uniformly empty, tyvars carry the
existing behaviour. Phase-0 design choices and the rest of the
roadmap are documented in memory.md.
tindex equality and hashing now go through a canonical sum-of-monomials normalisation, so n+1 and 1+n (and (n+m)^2 and n^2+2nm+m^2) are recognised as equal. Coefficients are EcBigInt with the natural-number invariant; canonical_const refuses negative TIConst. ecUnify now compares indices (with the previous polarity bug fixed) and ecReduction.for_targs no longer skips them. Memory.md updated with the Phase-1 deliverables and what was deferred (no TIUnivar / UF participation yet — gated on Phase 3 needs).
tindex_subst is no longer a no-op. It consults fs_loc / sb_flocal (indices share the formula-locals namespace), reinterprets the bound formula as a polynomial via the new tindex_of_form recogniser, and panics if the binding is non-polynomial. targs_fv (and so ty_fv on Tconstr) now folds over indices, so the per-formula short-circuits in Fsubst correctly fire for types with TIVar occurrences. is_ty_subst_id now also checks fs_loc emptiness — a formula substitution that touches an int-typed local can affect any type whose Tconstr carries a TIVar of that local. Cost is a wider substitution walk; correctness comes first here. memory.md updated with deliverables, design choices (no eager re-canonicalisation, fs_eloc not consulted), and the remaining risk (audit f_bind_local callers for the polynomial invariant).
Tydecls, operators, predicates and axioms can now declare integer
index parameters; type-constructor applications can supply index
arguments.
type [n m] ('a, 'b) vec.
op f [n 'a] (xs : 'a vec<:n>) : 'a vec<:n+1>.
pred p [n 'a] : 'a vec<:n>.
axiom A [n 'a] : true.
Index binders use plain (no apostrophe) identifiers in `[...]`. Index
applications are framed by `<:...>` rather than `[...]` to avoid a
shift/reduce conflict with codepos brackets in module-update
syntax. Index expressions inside the framing are restricted to the
polynomial fragment (`+`, `*`, non-negative literals, identifiers).
Datatype/record indexed types and cloning of indexed declarations
are refused with clean errors — useful index-instantiation at op
call sites still requires the deferred TIUnivar / polynomial-with-
univars unification work, also flagged in memory.md.
A regression test lives at tests/indexed-types.ec.
Adds the TIUnivar machinery Phase 3 deferred. Indexed ops can now be
called: each idxvar of the op being applied is freshened to a TIUnivar
and unified against the call site via polynomial-normal-form equality.
op concat [n m 'a] (xs : 'a vec<:n>) (ys : 'a vec<:m>) : 'a vec<:n+m>.
op cons [n 'a] (x : 'a) (xs : 'a vec<:n>) : 'a vec<:n+1>.
op test [n m 'a] (x : 'a) (ys : 'a vec<:n>) (zs : 'a vec<:m>)
: 'a vec<:n+(1+m)> (* canonically equal to (n+1)+m *)
= concat (cons x ys) zs.
The typecheck of `test` works because `cons`'s `?u` unifies with `n`,
`concat`'s `?u_n` unifies with `(n+1)`, `?u_m` with `m`, and the
inferred return type `(n+1)+m` is canonically equal to the annotated
`n+(1+m)`.
MVP scope: handles "naked TIUnivar = arbitrary polynomial" (with
occurs check) and canonical equality after resolution. Refuses
genuine polynomial unification (e.g. `?u + 1 = n` would need
subtraction-inversion) with a clean IndexMismatch error.
Also fixes a lurking bug in `ty_subst` where the `Tconstr` case fell
through to `ty_map`, which preserves indices verbatim — silently
dropping op-application index substitution.
clone T as T2 with type [k] 'a vec = body The optional [k] mirrors the tydecl binder syntax. body may reference both k and the type binders; when vec<:e> appears in T, the substitution binds k to e in body. ty_override_def is widened to (idxvars, tyvars, body); subst gains sb_idxvar; subst_ty's Tconstr-with-tydef branch binds both source binder lists to the call-site indices/types before substituting through body. The previous CE_IndexedNotYetSupported is replaced with CE_IdxArgMism so the user gets a precise arity message. Three clone cases land in the regression: drop the index (= int), propagate (= 'a coll<:k>), and a polynomial of the binder (= 'a coll<:k+1>). 77 declarations now compile. Two gaps are flagged in memory.md but kept out of scope: reaching into a cloned theory's ops whose signature got touched (looks orthogonal to indexed types), and explicit index-instantiation syntax at op call sites.
The two assert (List.is_empty *.indices) panics in ecSmt.ml become
raise CanNotTranslate, and check / execute_task catch the exception
to skip the goal cleanly. The user now sees a warning
("SMT: skipped goal containing constructs not yet exported to Why3
(e.g. indexed types)") followed by "cannot prove goal", instead of
an anomaly crash.
Translating indexed types to Why3 stays out of scope per the
original Phase-0 punt.
Errors and transcripts now print 'a vec<:n> instead of 'a vec; pp_tindex handles variables, univars (?#N), constants, and the polynomial *- and +-forms with standard precedence. Two vestigial Phase-0 asserts become clean failures: - ecReduction: indexed-op heads in a user rewrite rule raise NotReducible instead of crashing (they just don't match). - ecMatching: Fop vs Fop in pattern matching uses tindex_equal plus a length check, failing cleanly rather than asserting. The two asserts in ecInductive's positivity check are intentional "should never happen" guards — Phase-3 Slice-A already refuses indexed binders on datatype/record, so they aren't reachable. Closes the roadmap in memory.md. Remaining gaps are documented (op call-site index instantiation syntax, SMT translation of indexed types, indexed datatypes) but out of scope.
E — extend index-binder support to abbreviations and notations. Both rules now accept mixed_tyvars_decl (the bracket binder list that splits into idxvars and tyvars). pabbrev / pnotation gain *_idx fields; ecHiNotations threads them via the new ~idxparams parameter on transtyvars. abbrev my_alias [n 'a] : 'a vec<:n+1> = ... . notation %"..."% [n 'a] (...) = ... . D — investigation of the Phase-4 "T2.make_vec unknown" report showed it was a misuse of the alias `=` operator instead of the inline `<-` operator (alias creates a new name; inline propagates the body). Both modes work correctly. While tracing, fresh_tparams was discovered to freshen tyvars but not idxvars — fix this so op_tparams alpha-renaming includes both.
op count [n 'a] : int. op test : int = count[:5]<:int>. New lexer token LBRACKETCOLON (matches `[:` glued). Grammar adds `f[:idx]`, `f[:idx]<:ty>`, alongside existing `f<:ty>`. Parsetree TVIunamed and ecUnify tvar_inst.TVIunamed are widened to (indices, types). Producers updated mechanically (PFrecord, PTHO_*, inductive constructors, scope's tycinstance loop, printer's op_symb resolution, transtvi). EcUnify.openidx now consumes user- supplied indices when given, falling back to fresh TIUnivars otherwise. select_op's filter validates either side independently when non-empty. Useful when an op's idxvar is unreachable by argument-type inference. Phase-3.5 inference still handles the common case where the index can be derived from an argument's type. The test file gets three new cases (size[:5] xs, count[:5]<:int>, inferred-only baseline). 91 declarations now compile.
Document the plan and effort estimates for the three documented-but- unscheduled gaps from the original A-F plan. Order: B (polynomial unification beyond naked TIUnivar) -> C (non-refining indexed datatypes/records) -> F (SMT translation via per-index monomorphize).
Generalise the index unifier so [?u + k = poly] is solved when ?u
appears with net coefficient ±1 and the residual stays non-negative.
Previously only the naked-univar special case [?u = poly] worked, so
e.g. [tail xs : 'a vec<:n>] (where tail expects [vec<:n+1>]) failed
to unify against a caller-supplied [vec<:5>].
The new tindex_solve_for_univar walks the signed difference of the
two canonical polynomials, accepting only equations where:
- exactly one TIUnivar has non-zero net coefficient,
- that coefficient is ±1,
- every monomial mixing univars with other variables (or with a
univar at degree > 1) cancels to zero on net,
- the resulting value of ?u has non-negative coefficient on every
remaining monomial and constant.
The MVP scope deliberately excludes multi-univar Diophantine and
cases like [?u + 1 = n] for free n (no symbolic guarantee n >= 1).
Lift the Phase-3 Slice-A refusal that blocked index binders on datatype/record declarations. trans_datatype and trans_record now take an optional ~idxparams; ecScope's tydecl path threads it through the existing ~idxparams plumbing of transtyvars. Constructor and projector signature construction in ecEnv builds the result type via tconstr ~indices ~tyargs so e.g. INil is registered as 'a vec<:n>, not 'a vec. The positivity checker drops its assert (List.is_empty args.indices); indices play no role in positivity since the recursion is on the type and indices carry no embedded type information. Match elaboration in trans_branch (ecTyping) and the matchfix twin in ecHiInductive previously broke for 0-field constructors of indexed datatypes: opentys would allocate fresh index univars that appeared in no unified type, leaving them dangling at closed-check time. Fix: prepend a hand-built result type to the opened list so the freshly allocated univars are anchored to a type that participates in the subsequent unification against the scrutinee. Index refinement on match is deliberately out of scope: a vec<:0> scrutinee still admits an ICons-shaped pattern at the type level. Matches OCaml/Haskell parametric ADT semantics. Test file grows to 139 declarations covering constructor application, plain match, matchfix, indexed records, and field projection. Non-indexed datatypes/records continue to work unchanged.
Constructors of an indexed datatype are universally quantified over the index just like over type variables: INil has type forall n 'a. 'a ivec<:n>. Per-constructor result indices (GADT style) require both per-ctor result-type syntax AND index refinement on match, the latter being a much bigger dependent-typing feature.
…phization Replace the two CanNotTranslate raise sites in ecSmt for indexed Tconstr / Fop with a monomorphisation path. New helper EcAst.tindex_to_int reduces a tindex to a closed integer when possible (no free vars, no leftover univars); the SMT pipeline uses it to key two new caches (te_ty_idx, te_op_idx) by "path<:i,j,...>". trans_pty_idx / trans_tydecl_idx substitute idxvars by TIConst via EcCoreSubst.f_subst_init ~idx and emit fresh Why3 sorts named <path>_<i>_<j>... For indexed datatypes / records, all per-index constructor and projector variants are populated as a side-effect. trans_op_idx checks op_kind first: constructors / projectors / record-makers force their carrying type's monomorphisation (which populates te_op_idx); plain indexed operators get a fresh abstract Why3 symbol via create_op_idx. Bodies of plain indexed ops are dropped in this MVP — sound (treats the op opaquely) but limits SMT's unfolding across index instances. Goals with free index variables (e.g. an axiom binding [n]) still hit CanNotTranslate, preserving the per-goal skip behaviour: the existing try/catch around init / make_task emits the warning and the lemma falls through to "no provers" without a crash. Verified via 4 new SMT-discharge lemmas (160 declarations total in tests/indexed-types.ec) and a smoke test confirming non-indexed SMT goals are unaffected.
Lock in that lemma headers accept index params via the existing [n 'a] mixed_tyvars_decl syntax (shared with op binders). Discovered no fix was needed — the original "lemma binders don't accept index params" finding was a syntax confusion: separate brackets ['a] [n] are not supported (only the combined ['a n] form), and <:n> is the type-application framing while [:n] is the op-call framing. Two new lemmas exercise: a parametric proof using [trivial], and a quantified form using [move => ; trivial]. SMT discharge of goals with bound (non-closed) indices remains correctly skipped.
The printers for type declarations, operators, predicates, abbreviations, axioms and added-ops only emitted [tparams.tyvars], silently dropping the index binders. So [type [n] word.] would print as [type word.], hiding the index parameter from the user. New helper [pp_paramsannot ppe fmt (idxvars, tyvars)] prints the combined-bracket form [n 'a] matching the input syntax. All five printers now consult both lists; the per-kind operator printers (pp_opdecl_op / _pr / _nt) take a [ty_params] record instead of a bare tyvars list, and the dispatch in pp_opdecl passes op.op_tparams. Verified against /tmp/print_idx.ec: [type [n] word.], [type [n m] 'a vec.], [op cons [n 'a] : ...], [pred ix_pr [n 'a]], and [axiom ix_ax [n 'a]] all print their index binders.
The op grammar accepts an optional bracket-before-name for the opacity tags (opaque, smt_opaque). When users write [op [n] "_.[_]" (w : word<:n>) : bool] hoping to bind an idxvar [n], the parser greedily consumed the [n] as tags and silently discarded it, leaving [n] unbound in the type signature. Add a [disambiguate_op_brackets] helper invoked from both operator rule alternatives. If every entry in the leading bracket is in the known-tag whitelist, treat as tags (existing behaviour preserved for [op [opaque] foo], [op [opaque smt_opaque] foo], etc.). Otherwise reinterpret the bracket as a pure idxvar binder; if both the leading bracket and an after-name binder are present, raise a clear parse error rather than guessing. This fixes the canonical infix-style indexed-op declaration: type [n] word. op [n] "_.[_]" (w : word<:n>) : bool. which now parses with [n] correctly bound as an idxvar. Verified: full regression (182 decls), the original report case, and theories/datatypes/FMap.ec (heavy [opaque] tag user) still compile unchanged.
Indices and type variables now use distinct bracket families:
- {n m} for index binders
- ['a 'b] for type-variable binders
Indices come first when both are present.
Why: the previous mixed bracket [n 'a] was overloaded with the
op-leading [opaque] tags bracket, requiring a content-based
disambiguation that confused users when an unrecognised tag was
silently rewritten as a binder. With braces vs. brackets, the parser
disambiguates lexically and there is no overlap with op tags.
Parser:
- idxvars_decl now matches LBRACE lident+ RBRACE.
- mixed_tyvars_decl and bucket_mixed are gone, replaced by a single
ix_ty_binder rule that takes idxvars_decl? then tyvars_decl? and
returns (idxvars, tyvars_opt).
- The Gap-fix disambiguate_op_brackets helper is removed; the op
rules now read tags from the leading [...] and the binder from
the after-name {...} [...] pair without any reinterpretation.
- All consumers (operator x2, pred x2, inductive, notation, abbrev,
lemma_decl) switched to ix_ty_binder.
Pretty-printer:
- pp_paramsannot emits {idx} for indices and ['a] for tyvars,
separated by a single space when both are non-empty.
- pp_typedecl uses curly braces for the leading idx binder.
Tests: tests/indexed-types.ec (159 declarations) migrated to the new
syntax. Round-trip print produces text that re-parses unchanged.
FMap.ec (heavy [opaque] tag user) still compiles.
When an axiom / lemma / op / pred / abbreviation / notation binds
an idxvar [n] via {n}, the same ident now also resolves as an
int-typed local in the body of that declaration. Previously [n]
was only reachable in tindex positions like vec<:n>; using it as
an integer term (e.g. mkseq f n) failed with "unknown variable n".
This realises the Phase-2 design choice that idxvars and
formula-locals share a namespace: an idxvar is exactly an integer
binding, and the indexer / formula machinery agree on the ident.
New helper EcTyping.bind_idx_locals env ue pulls the idxvars out
of the unienv's tparams and binds each as a (id, tint) local in
env. Called immediately after every transtyvars ~idxparams site:
ecScope.add_r (axiom/lemma), ecScope op processing,
ecHiPredicates.trans_preddecl_r, ecHiNotations.trans_notation_r,
and ecHiNotations.trans_abbrev_r.
Verified with the original report case and a new regression in
tests/indexed-types.ec exercising [size (id_bits[:n] v) = n + 0].
Two related fixes that together let `rewrite L` and `apply L` work on lemmas declared over indexed types. 1. PTGlobal carries indices. The proof-term head `PTGlobal of EcPath.path * (ty list)` becomes `PTGlobal of EcPath.path * (tindex list) * (ty list)`. The constructor `ptglobal` and the alias `paglobal` gain an optional `?idxs` argument (default `[]`, so non-indexed call sites are source-compatible). `EcEnv.Ax.instantiate` also gains `?idxs`, substituting the lemma's idxvars in the spec. Without this, the proof checker re-instantiated only tyvars and the residual idxvars in the body broke conversion against the goal. 2. Closing a unienv now substitutes index-univars too. New helper `EcUnify.UniEnv.close_subst : unienv -> f_subst` builds a complete `f_subst` carrying both `~tu` (type-univars, as before) and `~iu` (index-univars). Used at the axiom-saving and op-saving sites in ecScope. Previously `Tuni.subst (close ue)` left every `TIUnivar` in the saved AST untouched, so even after the unifier resolved `?u_n := n_lem` the operator-type signatures inside the axiom's body still carried `?u_n`. Two `bits w` nodes that printed identically had different fresh univars and failed `is_conv`. Supporting infra: - `EcUnify.UniEnv.openidx` is exposed in the .mli (was private). Both `pt_of_uglobal_r` and `process_named_pterm` in ecProofTerm now open the lemma's idxvars to fresh `TIUnivar`s alongside its tyvars and thread both maps through `f_subst_init` to substitute the spec. - `EcMatching.MEV.assubst` adds `~iu:(iu_assubst ue)` so concretize resolves index-univars in the proof term and its formula together. - `EcMatching` `Fop` matching uses `unify_idx` for index lists (exposed in `EcUnify`) instead of structural `tindex_equal`. Verified: the user's `bits_cat` rewrite, `exact (test_eq w)` apply, and a new regression in tests/indexed-types.ec all work; full regression (184 decls) passes; non-indexed lemmas/rewrites unchanged.
…p targs
Three intertwined fixes for the user's [bits_cat] case:
lemma catE {m n} (wm : word<:m>) (wn : word<:n>) (i : int) :
0 <= i < m + n
=> (wm ++ wn).[i] = if i < m then wm.[i] else wn.[i-m].
proof.
move=> rgi @/"_.[_]".
rewrite bits_cat.
1. Op application records call-site indices in Fop targs.
`EcUnify.openty_r` now returns `(subst, ixs, tvs)` (was
`(subst, tvs)`); `select_op` returns
`(path, idxs, tys) * top * subue * sbody` (was `(path, tys)`);
`EcTyping.OpSelect.opsel.\`Op` and `opmatch.\`Op` carry
`path * tindex list * ty list`. `form_of_opselect` builds
`f_op p ~indices:ixs ~tyargs:tys ty`. Without this the call-
site indices were lost on Fop nodes — every `Fop` carried
`targs.indices = []` regardless of how the op was applied.
2. Op unfolding substitutes both tyvars and idxvars in the body.
`EcEnv.Op.reduce` was substituting only `tparams.tyvars`; now
it builds an `f_subst` with both `~tv` and `~idx` maps so that
every nested `Fop` in the unfolded body has its `targs.indices`
and `f_ty` rewritten to use the call-site indices.
3. Matcher does not unify Fop indices on the head.
The polynomial-against-polynomial `bits` head match
(`?u_m + ?u_n` against `m + n`) is genuinely ambiguous when
considered in isolation — multiple multi-univar Diophantine
solutions. The Fop matcher now only unifies type arguments and
trusts the surrounding `Fapp` arg matching to constrain indices
via per-arg f_ty unification (matching `(++) wm wn` first sets
`?u_m := m, ?u_n := n` individually, then `bits`'s polynomial
head trivially matches by reduction).
Every consumer of the new triple form was updated: ecHiInductive,
ecPrinting, ecScope (3 sites), ecTyping (4 sites), ecUserMessages.
Verified: full regression (202 decls) + new `unfold_then_rewrite`
test exercising the bug pattern.
…apply
When a lemma or op binds [{n}] and uses [n] both as a tindex AND as
an int term in its body (e.g. [size_bits {n} (w : word<:n>) :
size (bits w) = n]), substitution must reach BOTH namespaces:
- the tindex side ([TIVar n_lem] in [bits]'s targs), and
- the formula-local side ([Flocal n_lem] on the RHS).
Without the second part, opening the lemma at index [m] leaves a
dangling [Flocal n_lem] in the rewrite RHS, the goal becomes
[size (bits wm) = Flocal n_lem] (printed misleadingly as [... = n]
since both n_lem and m_caller share the name "n"), and the proof
cannot close. Same issue for [Op.reduce] when an op's body uses an
idxvar as int.
Fixes:
1. New [EcCoreFol.f_of_tindex : tindex -> form] projects a tindex
into the int-formula world. [TIVar id -> Flocal id : int],
[TIConst k -> f_int k], [TIAdd/TIMul -> f_int_add / f_int_mul].
Asserts on residual [TIUnivar].
2. [EcEnv.Op.reduce] (op-unfolding) and [EcEnv.Ax.instantiate]
(lemma application) now also bind [n_lem -> f_of_tindex idx] in
[fs_loc] alongside the existing [fs_idx] binding.
3. [EcProofTerm.pt_env] gains a [pte_idx_link] field recording each
lemma's [(idxvar ident, fresh tindex univar uid)] pairs;
[concretize_env] uses it to bridge the two namespaces during
proof-term concretization (when [?u_pat] resolves to [TIVar
m_caller], the corresponding [Flocal n_lem] in the body gets
bound to [Flocal m_caller]).
Verified: the user's [size_bits] / [bits_cat] / [catE] proof works,
plus a new regression case in tests/indexed-types.ec covering the
"idxvar used as int term in lemma RHS" pattern (214 decls total).
The previous fix for [pt_of_uglobal_r] (the no-instantiation lemma opener) wasn't carried over to [process_named_pterm] (the explicit [lemma[:idx]] / [lemma<:ty>] opener). Result: [have := mkK[:m + n]] on a lemma whose body uses [n] as an int term left a dangling [Flocal n_lem], breaking the proof checker with InvalidGoalShape. [process_named_pterm] now mirrors [pt_of_uglobal_r]: - For idxvars whose [openidx] returned a concrete [tindex] (because the user supplied [[:idx]]), bind [Flocal n_lem -> f_of_tindex idx] in [fs_loc] directly. The substitution flows into the formula immediately. - For idxvars whose [openidx] returned a fresh [TIUnivar] (the no-instantiation case), record [(n_lem, ?u)] in [pte_idx_link] so [concretize_env] can synthesise the form binding once unification resolves the univar. Same mechanism as [pt_of_uglobal_r]. Verified with the user's [have := mkK[:m + n]] case, plus a new regression in tests/indexed-types.ec exercising the explicit-index [have :=] pattern (227 decls total).
The previous fix made the matcher's Fop case skip index unification entirely, since polynomial-against-polynomial unification with multiple univars (e.g. [bits[:?u_m + ?u_n]] vs [bits[:m + n]]) is genuinely ambiguous in isolation. But that broke the simpler single-univar case: [rewrite mkK] (where mkK has one bound idxvar) no longer constrains [?u_pat] from the [mk[:?u_pat]] head, so the univar stays unresolved and the matcher concludes "nothing to rewrite". Make the index unification best-effort: try to unify each pair, and if a particular pair fails (multi-univar case), silently continue and let arg matching constrain the residual univars later. The single-univar case (handled by Gap-B's naked-univar fast path) goes through normally. Type unification on Fop heads stays mandatory. Verified: the user's [rewrite mkK] case now works, alongside the earlier [bits_cat] / [catE] cases (240 decls).
…ivars [Ax.instantiate], [Op.reduce], and [process_named_pterm] all bind [Flocal n_lem -> f_of_tindex idx] alongside the [TIVar n_lem -> idx] tindex substitution. But the call site can supply an [idx] that still contains an unresolved [TIUnivar] — happens when the matcher invokes [Ax.instantiate] before the surrounding unification has pinned the univar (e.g. on a chain like [apply: inj_bits; rewrite bits_cat. rewrite bits_cat]). The asserting [f_of_tindex] then crashes with the Phase-2 assert. New [EcCoreFol.f_of_tindex_opt : tindex -> form option] returns [None] when [ti] still contains [TIUnivar]. The three substitution sites use it to silently skip the form-side binding in that case; the form-side then gets resolved later by [pte_idx_link] at [concretize_env] time, once the univar is pinned. The asserting variant [f_of_tindex] stays for callers that have proven all univars are resolved. Verified: the user's [catA] / [apply: inj_bits; rewrite bits_cat] proof works (255 decls in tests/indexed-types.ec).
[h_tvar] is a [ty_params] record carrying both [tyvars] and
[idxvars], but the goal/hyps printers only displayed the type
variables. Lemmas with index binders (e.g. [{m n}]) showed an empty
"Type variables: <none>" line and no clue that [m, n] were in scope
as int-typed indices.
[pp_goal1] and [pp_hyps] now register the idxvars in the printer
env (via [PPEnv.add_locals]) and emit an "Index variables: m, n"
line above the existing "Type variables:" line whenever there is
at least one idxvar. The line is omitted entirely when no idxvars
are bound, so non-indexed lemmas look unchanged.
Verified: full regression (255 decls) passes; the line appears
during interactive proofs of any lemma with [{...}] binders.
Idxvars are non-negative integers by Phase-2 design. Expose this in
proofs by wrapping the goal — at proof-start time, not at lemma-save
time — with one [0 <= n_i =>] implication per idxvar. The user
introduces them on demand via [move=> Hn_i].
The wrapping is pushed INSIDE the lemma's [pa_vars] forall (not at
the very top) so the existing auto-introduction of [pa_vars] still
fires. So [lemma foo {n} (xs : T<:n>) : P] still auto-intros [xs];
the [0 <= n =>] hypothesis appears next, available via [move=>].
The implications never leak into the saved [ax_spec]: only the
proof goal sees them. Lemma application by other lemmas does not
require discharging [0 <= n] — the indexed-type discipline
guarantees it.
Also extend [EcSmt.lenv_of_tparams_for_hyp] to register each idxvar
as a top-level int constant in [te_lc], so [smt()] can talk about
the bound idxvars (else [trans_app]'s [Flocal] case would hit
[oget None]).
Verified: full regression (271 decls) plus three new test cases —
[idx_ge0_simple] (no [pa_vars]), [idx_ge0_smt] (multi-idxvar,
discharged via [smt()]), and [idx_with_args] ([pa_vars] auto-intro
still works alongside the new implications).
Resolves the divergence between the indexed-types work (integer-indexed
type parameters: targs = {indices; types}, op/tydecl tparams as
{idxvars; tyvars} records, index unification) and origin/main's new
features (exceptions, subtypes, tyd_clinline inline-clone, PPAny match
patterns, classify_application-based op-selection diagnostics, hoare
hsi_m/hsi_inv split).
Conflict resolutions (12 files):
- ecTypes/ecUnify/ecTyping/ecTheoryReplay/ecScope/ecSection/ecHiGoal/
ecPrinting/ecParser/ecThCloning/ecPhlPrRw: combine HEAD's record-shaped
targs/ty_params + index threading with origin/main's new features.
- select_op result now carries call-site indices ((path, ixs, tvs), ...);
threaded through ecTyping constructor/record-projection paths.
- ecTheoryReplay: keep origin/main's fix-989 (ctor-remap HACK lifted out of
the Inline-only branch) atop the record-shaped params.
- ecPrinting try_pp_notations: use origin/main's result-typed op form
(f_op p ~tyargs:tv rty) rather than the function-typed variant, so infix
operators print correctly again.
- ecFol.proj_distr_ty: keep origin/main's lenient "unary constructor"
match so distr projections in outline/encryption theories don't assert.
- xop_override/preoperator `Direct payloads use EcIdent.t list (origin/main
semantics) now that EcDecl.ty_params is a record.
- op-application-errors.ec: one expect-fail case updated to the richer
diagnostic HEAD's unifier emits (shows the inferred `'a = int`).
Validated with -no-eco: dune build clean; stdlib 125/125; unit 86/86;
exception+circuit 21/21; tests/ 65/65; branch's indexed-types.ec passes.
Picks up origin/main's `defer failed-application classification (perf)` (e1a1e84) and auto-unfold of some Logic operators (88096fb). Only conflict was src/ecUnify.ml select_op_outcomes: origin/main split op-selection into a unify-based `select` (OK via mk_ok) plus a lazy `KO (lazy (classify ...))` for deferred error classification. Adopted that structure and threaded the indexed-types call-site indices through it (mk_ok now carries ixs into OK ((path, ixs, tvs), ...); classify uses op_tparams.tyvars). Validated with -no-eco: build clean; stdlib 125/125; tests/ 65/65; expect / op-application-errors / indexed-types pass.
|
It seems to me like unification of indexed types with unification variables in the indices is equivalent to solving systems of polynomial equations over the positive integers. This is an intractable problem in general. What are the limitations of the unification algorithm you implement? |
Add ambient natural-number index parameters to sections, mirroring
`declare type t` for type variables. `declare index {n}` introduces an
index `n` in scope for the rest of the section — usable binder-free in
`word<:n>` positions and as an int value — and generalized back to a `{n}`
binder on section close, on-demand (only on items that actually use it).
`index` is a keyword but is also admitted as an identifier (added to the
`_lident` rule), so `List.index` etc. keep working.
Because indices are ℕ, each proof in the section gets `0 <= n` injected as a
top-assumption of the goal (via the same `f_int_le f_i0` path the `+`
marker uses), for the indices the lemma uses; the proof intros it
explicitly. It never enters the saved `ax_spec`, so generalized statements
stay clean.
- EcEnv: `env_idxdecl` field + `declared_indices` / `lookup_declared_index`
/ `push_declared_index`.
- lexer/parser/parsetree: `index` keyword (also a lident); `declare index {n}`
-> `Gdeclidx`.
- EcCommands/EcScope: `Index.declare`; `start_lemma` augments the proof
`tparams` with the used ambient indices and injects their `0 <= n`.
- EcTyping.transtindex: fall back to the env's declared indices (rigid).
- EcSection: `SC_decl_idx` + `add_decl_index`; `add_declared_idx`,
`generalize_extra_idx`, `form_idx_fv` wired into generalize_{opdecl,axiom}.
No regression: stdlib 127/127, unit 89/89.
Length-indexed array and bit-word theories, indexed counterparts of
Array.ec and BitWord.eca, developed with `declare index {n}`:
- IArray: `'a array<:n>` by reflection to lists; full Array.ec lemma
surface (get/set, get_set(_if), set_out/neg/above, set_set_*,
eq_from_get, offun, map) at symbolic n.
- IWord: `word<:n>` = false-defaulting view of `bool array<:n>`, mirroring
BitWord.eca: bitwise ops, the `bwordE` hint set, and the Boolean ring
laws via wordP.
The derived layer lives in a `declare index {n}` section (ops/lemmas drop
their `{n}`; `0 <= n` is a top-assumption in each proof). tests/ exercise
symbolic n and concrete widths, plus a per-width Ring.BoolRing instance.
…x-univar fix Two related changes that let algebraic-structure instances (Ring/Field) be generalized over section-declared indices, so a single instance covers a whole family of nonzero widths. Index-parametric instances: - ecDecl: `ring` record gains `r_indices : tindex list` — the index arguments shared by every ring op (empty for non-indexed rings, so those are byte-identical). Threaded through ecSubst.subst_ring and ecTheoryReplay. - ecTyping.get_ring/get_field: derive the concrete `r_type`/`r_indices` from the matched carrier, using `close_subst` (resolves both type- and index-univars) so a stored `word<:n+1>` instance comes back at the goal's concrete width. - ecAlgebra.rapp / ecAlgTactic.inject_indices: emit ring ops at the carrier's indices when building reflected terms and instance axioms. - ecSection.generalize_instance: on section close, move the declared indices the carrier depends on into the instance's ty_params; on_instance no longer crashes on the synthetic `General` zmodule/ring/idomain cross-links (they point at non-existent paths). - ecScope.ring_of_symmap: capture the carrier's indices into `r_indices`. - EcAst.tindex_normalize: canonical (normal) form of an index. Predicate index-univar fix (ecHiPredicates.close_pr_body): - Predicate bodies were closed resolving only type-univars (`assubst`), leaving index-univars in the body. A `pred P (w : word<:n+1>) = w = onew` stored `onew<:?u>` instead of `onew<:n+1>`, so unfolding it produced a goal not convertible to the real `onew<:n+1>` (an InvalidGoalShape on apply/exact). Now closes with `close_subst` (type + index univars), like the operator path. No-op for non-indexed predicates. No regression: unit 89/89, stdlib 129/130 (DynMatrix:1047 is a pre-existing sandbox SMT flake, unaffected by these changes).
…bols
The indexed-SMT translation had only two modes: monomorphise a concrete
index to a fresh Why3 sort/symbol (Gap-F), or `raise CanNotTranslate` for
anything else — which aborted the whole task. A symbolic index had no
representation at all, so a single `w : word<:k>` in scope skipped an
otherwise-pure-int goal ("constructs not yet exported to Why3").
Replace that with a uniform encoding where the index is a proper symbol:
- tindex -> an int term. `trans_app`'s `Fop` case maps each index through
`EcCoreFol.f_of_tindex` and feeds it to `trans_form`, so idxvars resolve
through the same `te_lc`/`le_lv` int-symbol machinery as any int local.
- indexed type `T<:i>` -> one Why3 sort per (path, tyvars); the index is
carried at the term level, not the sort level. `trans_ty` drops it.
- indexed op `f<:i>` -> one Why3 function with the indices as explicit
leading `int` arguments (`create_op` prepends them; `apply_wop` gains an
`~idx` parameter that supplies the concrete index terms). Indexed ops are
kept opaque (no body) — sound.
- a polymorphic lemma's idxvars -> universally-quantified Why3 int
variables (`trans_axiom` binds them; `add_axiom` `?qvars` closes over
them). Type variables keep relying on Why3's type polymorphism.
This subsumes the concrete case, so Gap-F is removed
(`trans_{pty,tydecl,op}_idx`, `create_op_idx`, `idx_key`, `idx_suffix`,
`tindices_to_ints`, and the `te_ty_idx`/`te_op_idx` caches): -206 lines net.
Sound abstraction: abstract sorts + uninterpreted functions add no axioms,
every width-dependent operator threads its index, and a cross-width
equation is ill-typed in EC so never reaches Why3. A false symbolic-width
goal (`to_uint w = 0`) stays unprovable.
Symbolic-width `smt()` now works (e.g. `to_sint_cmp`, `to_uint w < 2^k`,
pure-int goals with an indexed local in scope). No regression: unit +
prelude + stdlib 220/220.
Develop theories/datatypes/IWord.ec into a Tier-1 machine-word surface: - bitwise `orw` + simp lemmas; - arithmetic ℤ/2ⁿ ring: ops `(+)`/`oppa`/`(*)`, the `to_uint` homomorphism laws (to_uintD/M/N), the `of_int` laws, and a `ComRingDflInv` clone `WRingA` over `word<:n+1>`; - unsigned/signed comparisons `\ule`/`\ult`/`\sle`/`\slt` + Ngt/Nge; - shifts/rotates `>>>`/`<<<`/`sar`/`ror`/`rol` with bit-laws; - shift <-> arithmetic: `int_bitMP`/`int_bitDP` (reusable pure-integer bit-shift lemmas), `shlMP`/`shrDP`, and `to_uint_shl`/`to_uint_shr`. Tests: iword_num (numeric round-trips, smt-through-symbolic-index), iword_arith (arithmetic/comparison/shift surface), iword_ring (boolean ring tactic at symbolic/concrete widths); drop the superseded iword_ring64.
|
It would be good to allow |
|
Should the operator added messages and operator printing show indices? |
|
This error message could be more specific. |
|
And this is pretty confusing: |
|
At the moment, there is no way to override indexed operators in cloning. We need: |
# Conflicts: # src/ecProofTerm.ml # src/ecProofTerm.mli # src/ecScope.ml # src/ecSubst.ml # src/ecTheoryReplay.ml # src/ecTyping.ml
- adapt main's new sty_subst call sites to record-shaped ty_params (.tyvars/.types), missed from the merge commit - drop now-unused 'open EcTypes' in ecHiPredicates (fatal under --profile=ci)
Allow naming index arguments at instantiation sites, independently of
the positional/named choice made for type arguments:
op f {n m} ['a, 'b] : 'a -> 'b -> bool.
op g = f[:n = 3, m = 4]<:'a = int, 'b = real>.
- parsetree/unify: the index side of an instantiation becomes its own
positional/named sum (IXunamed/IXnamed), carried by both TVIunamed
and TVInamed; the parser's 'cannot mix explicit indices with
named-tyvar syntax' restriction is gone (all four combinations are
legal).
- named index instantiation may be partial: unnamed idxvars fall back
to fresh index univars and are inferred (positional instantiation
still requires the full arity).
- unknown names now error instead of being silently ignored:
opentvi/openidx raise on a name binding no formal (safety net;
surface paths validate earlier), op selection filters candidates by
index-name subset, and pf_check_tvi checks lemma instantiations
('unknown index variable', 'wrong number of index parameters') the
same way it already checked type variables.
Requested by Alley Stoughton in #1065 (comment 1); the partial form
also gives a principled route around the confusing partial-positional
error (comment 4).
Tests: tests/named-index-instantiation.ec (all four combinations,
out-of-order names, partial op/lemma inference, five expect-fail
diagnostics). No regression: unit + prelude + stdlib green; ci
profile (warnings-as-errors) clean.
Done in 9460bc0: index and type instantiation can each be positional or named, in any combination. Named indices may be partial ( |
Explicit index instantiations were dropped when printing operator references: `op g = f[:3, 4]<:int, real>` printed back as `f<:int, real>` (PR #1065, Alley's comment 2). - pp_opname_with_tvi gains an index component: prints `f[:3, 4]<:int, real>`. - pp_opapp threads the Fop/Eop indices (callers pass the full targs record instead of .types). - suppression mirrors the type-argument policy: a new ixs_dominated (over a free-idxvar-of-type collector) hides indices inferable from the printed arguments' types; the showtvi pragma forces them. Everything prints through pp_form -> pp_opapp (pp_expr converts to a form), so goals, bodies, print and search output are all covered. Declaration printing (print f / added-operator messages) already showed {n m} binders. Tests: expect-by-print assertions in tests/named-index-instantiation.ec (shown / suppressed-inferable / printed-non-inferable). No regression: unit + prelude + stdlib green (incl. the print-asserting tests expect.ec / print-proc.ec / clone-type-inline.ec); ci profile clean.
Yes — fixed in 4908019: |
PR #1065, Alley's comments 3 and 4. An explicit instantiation incompatible with an operator produced "unknown variable or constant: `f'" (the tvi check ran as a silent candidate pre-filter, so the failed-application classifier never saw the candidate), and omitted indices died at declaration close as "this operator type contains free type variables". - ecUnify.select_op_outcomes: the tvi compatibility check moves from the EcEnv.Op.all pre-filter into the selection loop; an incompatible candidate now yields a classified KO (new op_failure variants OF_idx_arity / OF_idx_unknown / OF_tv_arity / OF_tv_unknown) before open/unify (openidx's arity fallback would otherwise let a wrong-arity candidate unify). Selection semantics unchanged. - op_instance becomes {oi_tys; oi_ixs}: application failures now report "where the index parameters were inferred as: n = 3" alongside the type parameters; failure-report types resolve BOTH univar kinds and normalize indices (display independent of constraint-solve order). pp_tindex joins the PrinterAPI. - uninferred indices are reported as such: UniEnv.closed splits into closed_tv/closed_iu; the close-check sites (op/pred/formula in ecScope, clone overrides in ecTheoryReplay, tyerror sites in ecProofTyping via new FreeIndexVariables) say "cannot infer all index parameters ...; supply them explicitly (e.g. `f[:n = 3]')" when only the index side is open. Tests: 6 new expect-fail assertions (omitted / partial-positional / unknown-named index, unknown-named / wrong-count tyargs, inferred-index report); the unknown-named-index assertion updates from the old "unknown variable or constant" text. No regression: unit + prelude + stdlib green (op-application-errors.ec byte-identical); ci profile clean.
tests/op-application-errors.ec is the file dedicated to asserting operator-application diagnostics; move the five UnappliedOp assertions from the named-index feature test there (own ixop/ivcat fixtures). named-index-instantiation.ec keeps the feature tests and the non-application diagnostics (duplicate named index, lemma-path pf_check_tvi checks, uninferred-indices close error). Also drop the index-normalization pass from resolve_ty_for_report: it was inert — ty hashconsing compares indices canonically, so a rebuilt vec<:8> IS the interned vec<:3+5> node and the display spelling is whichever got interned first (deterministic per file). Keep the real part (index univars resolved so reports never show ?#N) and document the hashconsing behaviour.
Both fixed in f2074ba. Omitted indices now report and an incompatible instantiation is classified per candidate instead of degenerating to "unknown variable or constant" — (same treatment for unknown named arguments and wrong type-argument counts). Application failures also list inferred indices alongside inferred type parameters. |
PR #1065, Alley's comment 5. Operator and predicate override clauses in `clone with' now accept index binders, in both alias and inline modes: clone U as U' with type {n} 'a foo = 'a vec<:n>, op f {n} ['a] (x : 'a) (xs : 'a vec<:n>) = cons[:n] x xs, pred p {n} ['a] (xs : 'a vec<:n>) = nonempty xs. Surface plumbing: idxvars_decl on the OP/PRED override productions (nonneg markers rejected), opov_idxvars/prov_idxvars parsetree fields, replay via transtyvars ~idxparams + bind_idx_locals, and a NotSameNumberOfIdxParam incompatibility (index arity of an override must match the overridden declaration). The work exposed three latent index bugs in shared infrastructure, all fixed: - EcEnv.Ty.unfold substituted only type parameters: unfolding an indexed type alias leaked its formal index variable, so `foo<:3>' failed to unify with its own unfolding `vec<:3>' (independent of cloning). - Compatible.for_ty renamed only the reference declaration's tyvars onto the override's; it now renames idxvars too. - EcSubst.open_oper (and get_open_oper/get_open_pred) opened operators at type arguments only; it now takes ~indices. Tests: tests/clone-indexed-override.ec (type-only, type+op+pred alias with conversion/print checks, inline mode with axiom-inlining assertion, index-arity-mismatch rejection) and tests/indexed-type-alias.ec (alias unfolding at concrete/symbolic indices, index arithmetic through the alias). No regression: unit + prelude + stdlib green; ci profile clean.
IWord.ec's msbE closed with smt(to_uint_cmp half_modulus) under the local z3 but not under the CI prover set (deterministic failure on every CI stdlib run; CI's cvc5 answers are unparsed by why3, and its z3 rejects the exponential goal). Rework the proof so no prover ever sees an exponential: discharge the 2^(n-1) facts with half_modulus/gt0_half while concrete, generalize the power to an opaque constant, and close the remaining linear-plus-division goals with explicit IntDiv hints (divz_ge0, ltz_divLR).
Right: the general problem (univars on both sides) subsumes solving polynomial systems over ℕ, so we do not attempt it. What is implemented is a small deterministic fragment, and the split matters. Checking index equality is complete: indices are compared by canonical polynomial normal form, which is decidable. Solving is restricted to (a) naked-univar assignment with an occurs-check, and (b) single-univar affine equations with net coefficient ±1 whose solution has non-negative coefficients, e.g. |
… sort
Review findings 11 and 3 (both with confirmed derivations of false).
The Why3 translation erases indices at the sort level; the old
justification ('a cross-width equation is ill-typed in EC') is
insufficient: an axiom quantifying over ONE width, e.g.
forall (c : bool array<:0>), size (ofarr c) = 0, loses its width
restriction and constrains the whole erased sort — collapsing
array<:1> and deriving false. Independently, lemma idxvars were
quantified as an unguarded forall n:int, asserting more than EC
proved (smt(ge0_index) proved 0 <= n for every integer).
Make the index recoverable at the term level for VALUES, as it
already is for operations:
- per-family width observers size_k : ('a,..) t -> int (one per index
position, polymorphic, declared on first use);
- quantifiers over head-indexed types are relativized:
forall (x : t<:i>), P ==> forall x, size_k x = i => P
(conjunct for exists; lambdas assert nothing). Guards are exact.
Where an indexed constructor occurs where observers cannot reach
(nested under another constructor / tuple / arrow, or as a type
argument of an indexed head) the binder raises CanNotTranslate and
takes the existing sound trans_gen fallback;
- lemma idxvars get 0 <= n premises (at the EC level, riding the
normal translation); goal idxvars get 0 <= n facts;
- goal locals at head-indexed types carry size_k x = i facts;
- every indexed operator gets a typing-justified result-width axiom
forall i xs, 0 <= i => size_k (f i xs) = e_k(i) — this is what keeps
guarded lemmas usable, not just sound (the 0 <= i premises keep the
union-of-widths model satisfiable at out-of-range indices; EC types
are inhabited so every natural-width carrier is non-empty).
Soundness: interpret the erased sort as the disjoint union of the
per-width carriers and size_k as the width tag; every EC model
extends, so Why3 proofs under guards are EC-valid.
Tests: tests/indexed-smt-guards.ec turns both reviewer exploits into
fail-smt regressions and checks the positive direction (width-0
lemmas prove width-0 goals through the guards; symbolic-width smt
still fires). No regression: unit + prelude + stdlib + examples all
green; ci profile clean.
Review findings 5, 6, 10 shared one root cause: the branch introduced a second univar kind (index univars, ue_iuf) and a combined closing substitution (close_subst), but left the legacy type-only close/assubst : unienv -> ty Muid.t exported — so ~30 call sites each chose independently, and the wrongly-chosen ones silently dropped resolved indices at persistence boundaries (dangling TIUnivar in proc bodies, have-hypotheses, abbrev bodies). Make the wrong choice inexpressible: close/assubst leave the public UniEnv API (they remain internals); the exported closing API is exactly close_subst (raising) and the new as_subst (non-raising), both returning the f_subst that resolves BOTH univar kinds. iu_assubst stays exported solely for the proof-term idx-link bridge. The compiler then enumerated every consumer (~30 sites, 13 files): ecProofTyping (finding 6: process_form/stmt/type/exp/poe, tc1_process_stmt), ecHiNotations (finding 10: abbrev), ecTyping (Pst_fun family, PSmatch, op-selection error paths — which now also DISPLAY resolved indices), ecTheoryReplay (clone-override op/pred closes — latent, unreported), ecScope (axiom close, subtype predicate, ring/field instance carriers), ecMatching (f_match's second component becomes the combined f_subst; its only caller ignored the map), ecReduction (user rewrite-rule instantiation), ecHiInductive, ecTypesafeFol, ecUserMessages, phl/ecPhlRwEquiv, phl/ecPhlRwPrgm. Most sites get simpler (no Tuni.subst wrapping). Tests: tests/indexed-univar-close.ec pins the three repaired behaviors (abbrev body linked to its binder instead of a frozen ?#N, proc bodies persisting resolved indices, have-hypotheses usable). No regression: unit + prelude + stdlib + examples green; ci profile clean.
Review finding 4 (confirmed derivation of false). Match-fix iota-reduction substituted only the operator's TYPE parameters into the body: the idxvar stayed dangling in both its namespaces (TIVar positions and its int-formula occurrences, e.g. a branch body that returns n), so f[:5] ... and f[:7] ... reduced to the SAME term and 'by cbv' proved their equality. New EcFol.f_subst_tparams instantiates an operator body at explicit targs the way EcEnv.Op.reduce already did correctly: tyvars via the type substitution, idxvars via the ~idx map AND via f_bind_local on their int-typed formula-local occurrences. Migrated four sites: - ecReduction iota (the reported one), - ecCallbyValue iota (its twin), - both rewrite-/op delta-unfold sites in ecHiGoal, whose destructuring tuple discarded the indices at construction (same bug shape, found by sweeping for the pattern; the tuple now carries the full targs and ty_params). Tests: tests/indexed-iota.ec — iota lands on the call-site index (cbv and simplify paths), the old collapse is REFUTED (f[:5] ... <> f[:7] ... by reduction), and rewrite /g on an indexed plain op unfolds correctly. No regression: unit + prelude + stdlib + examples green; ci profile clean.
…inks Review findings 7, 8, 9 (all with confirmed repros). LvMap (7): translvalue discarded the selected map-set operator's inferred indices ((path * ty list) payload); i_asgn_lv then rebuilt an index-erased op node whose arity contradicts the declaration. The lvmap payload now carries the full targs and the rebuild passes ~indices. Records + match desugar (8): trans_record opened the record type with the types-only openty and Tvar.init, so PFrecord built ctor/projection Fop nodes without indices, field/default types leaked formal idxvars, and the proc-match desugar built the projection without the scrutinee's indices. trans_record now opens via openty_r (combined substitution), threads the instance indices to both builders, and the match desugar reads the indices off the scrutinee's type. SMT side: there is no sound erased encoding of indexed datatypes/records yet (a width-erased nullary constructor plus the size axioms would be inconsistent), so trans_tydecl raises CanNotTranslate for them: goals degrade to the sound fallbacks instead of crashing with Why3 arity errors. Full index-threaded constructor encoding is left as documented follow-up. The 0 <= n guards also move to the Why3 level (w3_ge0 via the known-ops table): they no longer require the EC int theory to be in the file's scope. Proof-term links (9): propagate_idx_link / concretize_env resolved a link's univar with a single-hop map lookup, but ue_iuf is a chain map (?u := ?a, ?a := 7 read as unresolved), and concretize_env only handled literal TIVar/TIConst. Export UniEnv.repr_tindex (chain resolution) and use it at both sites; the link binding generalizes to any f_of_tindex-expressible index, so chains grounding in compound indices (m+1) concretize. Tests: tests/indexed-node-instantiation.ec (map-update wp proof, record node conversion, fail-smt degradation, chained and compound-chained proof-term application). No regression: unit + prelude + stdlib + examples green; ci profile clean.
Adds integer-indexed types to EasyCrypt: type constructors parameterised by both type variables and natural-number indices, with a small index language and decidable index equality. This lets you express size-carrying types like
'a vec<:n>and track index arithmetic through operators, lemmas, and cloning.