Skip to content

Deploy tc#633

Draft
strub wants to merge 289 commits into
mainfrom
deploy-tc
Draft

Deploy tc#633
strub wants to merge 289 commits into
mainfrom
deploy-tc

Conversation

@strub
Copy link
Copy Markdown
Member

@strub strub commented Sep 26, 2024

No description provided.

AntoineSere and others added 30 commits November 30, 2021 10:48
When `case` or `elim` search for a redex, allows the reduction
to unfold non-transparent operators.

This does not affect tactics that does case/elim internally
(e.g., />).

fix #132
This reverts commit 37dbab8.
strub added 30 commits May 12, 2026 13:32
Two refinements to the witness-selector resolver:

1. [select] in [select_op] catches [InvalidSelector] from
   [opentvi] and treats it as a candidate-rejection signal
   (raises [E.Failure]) rather than letting the anomaly bubble
   out. The error surfaces as a normal "no matching operator"
   if no candidate accepts the selector.

2. Concrete vs abstract dispatch: previously any [Tconstr p] with
   [tyd_type = `Abstract _] went to the abstract-search branch.
   But primitive types like [int] are declared abstract with an
   EMPTY bound list — they should be treated as concrete
   carriers. Fixed by requiring at least one bound to take the
   abstract path; empty-bound abstract types fall through to
   env-level instance lookup.

Verified: selectors work on [int] (concrete), on [t <: comring]
(declared-abstract with bounds), and (transitively) on type
variables in op signatures.
Renames monoid's op to neutral [mop] and axioms to [mopA]/[mopC]/[mop0].
Section lemmas use [m] suffix ([mop0m], [mopCAm], etc.). [addmonoid] and
[mulmonoid] rename ops via [(monoid with idm = zero, mop = (+))] and
[(monoid with idm = oner, mop = ( * ))], with re-export section
lemmas [addm*]/[mulm*] specialized to those subclasses.

[comring] drops body axioms [mulrA]/[mulrC]/[mul1r]. They come for free
through the [mulmonoid] chain; the comring section re-exports them
under conventional names via [apply: mulm*].

Instance files (TcInt, TcReal, TcZModP, TcPoly) use labeled proof
obligations: [proof mopA<:addmonoid> by ...] and
[proof mopA<:mulmonoid> by ...]. Discharges both views distinctly.

[src/ecScope.ml]: restore the per-entry obligation substitution fix
(reverted in 03cb8f4). Now safe to land because the user can
disambiguate via labels.

[TcBigop]: parametric over [t <: monoid] using neutral [mop].
[local abbrev (+) = mop<:t>] (as a direct alias, NOT [fun x y => mop x y])
so partial applications [(+) x] keep their curried shape without
introducing a [fun y => ...] lambda that would defeat rewrites.

Status: 9/13 tcalgebra files pass. Four downstream (TcBigalg,
TcBinomial, TcPoly, TcPolySmokeTest) fail with the same root cause:
[zero<:t>] (the auto-promoted addmonoid op) and [idm<:t>] (monoid's
op) have distinct global paths and the unifier doesn't see through
[tc_reduce] when matching them. Manifests as
[bigop_endo oppr0 opprD] failing because [oppr0]'s [-zero = zero]
doesn't unify with [f idm = idm]. Tracked as follow-up; needs
either unifier improvement or a redesign where renamed inherited
ops are transparent aliases (like the original [abbrev zero] in
TcMonoid) rather than fresh promoted ops.

sandbox.ec is pre-existing failure unrelated to this migration.
When the rigid [Fop, Fop] match case encounters paths that differ
but resolve to the same canonical TC origin (via [tc_ops_origin]),
treat the ops as equivalent. The canonical origin is the path of
the class where the op was originally introduced — followed
through auto-import [tc_ops_origin] chains. Two ops match when:

1. They share an origin, AND
2. One's declared class is an ancestor (or descendant) of the
   other's — i.e., they sit on a single inheritance chain.

The ancestor constraint distinguishes legitimate aliases
([monoid.idm] ↔ [addmonoid.zero], related by [addmonoid <: monoid]
with the [idm = zero] rename) from sibling views that share an
origin but are NOT equivalent ([addmonoid.(+)] vs [mulmonoid.( * )]
— both auto-imported renamings of [monoid.mop], but [addmonoid]
and [mulmonoid] are siblings, not ancestors).

When paths differ but the ops are origin-equivalent, only the
carrier types of the etyargs are unified (witness univars stay
independent, since the tparam constraints differ — [<:monoid>]
on one side, [<:addmonoid>] on the other; TC inference will
resolve them through the appropriate chain view once carriers
are pinned).

New helpers in [EcEnv.Op]:
- [tc_op_canonical_origin env p]: follows [tc_ops_origin].
- [tc_ops_same_origin env p1 p2]: origin + chain-relation check.

Fixes TcBigalg.ec ([sumrN] via [big_endo oppr0 opprD] now works
at section [t <: addgroup]: pattern [f idm = idm] matches subject
[-zero = zero] because [monoid.idm] and [addmonoid.zero] are
recognized as the same canonical op).

10/13 tcalgebra files now pass (was 9). TcPoly / TcBinomial /
TcPolySmokeTest still fail on related but different bigop-at-poly
issues.
Class declarations like [addmonoid <: (monoid with idm = zero, mop = (+))]
no longer auto-import renamed inherited ops as new global OP_TCs. The
rename clause is pure metadata; ergonomic local names ((+), zero, ( * ),
oner) are wired up at the theory level via manual abbrev declarations
that expand at typing time to the parent op (mop / idm). This makes
class-decl renames symmetric with type-variable-constraint renames
(which can never create ops) and eliminates the [Top.TcMonoid.+] vs
[Top.TcMonoid.mop] duality that broke congr in TcBinomial and forced
the same-origin scaffolding in the matcher.

Resolver/SMT changes:
- [match_tc_offsets] single-candidate guard ([ecUnify.ml]): with a
  single chain path, use it even when the rename clobbers the op name
  - the parent op is still accessible via that path. Without this
  [mop<:'a>] at ['a <: addmonoid] doesn't resolve.
- [drop_tc_bounded_notation] removed from canonicalize: it preferred
  unbounded ops by name regardless of type, killing the new TC-bounded
  abbrevs. Real ambiguities (e.g. legacy Real.ec [(^)] vs TC [(^)]) are
  resolved at the user-code site (use [exp] in TcBinomial.binomial_r).
- [tc_reduce_abstract_via_rename] removed entirely: it folded TC ops
  through rename edges to produce Fops at the renamed name. With
  renames now being abbreviations, the produced path resolves to an
  OB_nott that doesn't survive typing; the resulting Fop leaks an
  opaque why3 symbol into SMT tasks, breaking proofs like
  TcRing.expr_pred.
- [head_op_after_tc_reduce] and the [tc_ops_same_origin] / [same_op]
  matcher clause removed (dead with the duality gone).

Scope changes:
- [inherited_tc_ops], [inherited_origins] always empty; body-local
  bindings for renamed inherited ops dropped (they'd leave dangling
  Flocals in axiom bodies otherwise).

TcAlgebra changes:
- TcMonoid.ec gains four abbrevs ((+) / zero / ( * ) / oner).
- TcBinomial.binomial_r uses [exp] explicitly to pick the TC variant
  past the legacy Real.ec [(^) : real -> int -> real] abbrev.
- TcPoly.ec instance proof tactic name updates.

Batch: 11/11 examples/tcalgebra, 16/16 tests/tc.
Each TC class becomes a why3 record of its flattened ops (the dict).
Concrete instances and goal-context type carriers each get their own
dict const; Fop applications of TC class ops translate to record-field
projections on the appropriate dict. Different chain-leg views of the
same ancestor get distinct dicts, so the addmonoid leg and mulmonoid
leg of comring no longer collapse to the same why3 symbol.

Verified sound: `lemma false : false` at a `t::comring` carrier no
longer goes through smt() — previously the unsound encoding made it
derivable via `idm <> idm` collapsing to False.

Phases (see project memory `tc-smt-encoding-dict`):
  A. per class, dict record type + uninterpreted axioms predicate.
  B. per registered chain-instance, dict const + bridges to concrete
     realisations + axioms-hold assertion.
  C. per tparam / declared-abstract type bound, dict const + projected
     ancestor axioms (substituted via the witness's chain lift).
  D. trans_app intercepts class-op Fops, projecting through le_tdict
     (tparam-bound) or te_idict (instance) dicts.
  E. legacy `trans_tc_axioms`, `trans_reducible_instance_bridges`, and
     `delta_tc` pre-reduce removed from the goal init path.
  + trans_axiom now dict-passes for polymorphic axioms — universal
    over a fresh dict per TC bound, with `<class>_axioms` as premise
    matching against each instance's axioms-hold.
Two follow-up fixes for the dict-passing encoding:

1. [te_absdict]: declared-abstract type carriers (section-introduced
   `declare type t <: comring`) now register their dicts at tenv level,
   not just in the goal's [lenv]. Lemma translations and operator-body
   translations build their own [lenv]s and need to see the same dict
   to project class ops through it — otherwise a lemma's body emits an
   opaque `Top_TcMonoid_mop` and the goal's body emits `fld_mop @
   d_t_comring_0`, and SMT can't connect the two even though they
   denote the same value.

2. [<class>_axioms] axhold for tparam / declared-abstract dicts.
   Polymorphic lemmas now translate with [<class>_axioms 'a d] as
   premise — without this axhold, the SMT can't instantiate the
   universal lemma at the section's [t] / its dict because the premise
   has no witness in scope.

Together these fix the TC SMT regression at [TcRing.ec:530] (which is
the indicator goal `expr_pred (x : t) (i : int) : 0 < i =>
exp x i = x * exp x (i-1)` inside the comring section). Without the
fix, the lemma `exprS` translated as a polymorphic axiom over an
opaque `Top_TcMonoid_mop`, while the goal's `*` projected on
[d_t_comring_0] — the symbols didn't match.

tcalgebra status: 5 failures total on HEAD vs 7 at parent. Net +2
(TcRing:530 + TcNumber:1288 newly pass under the dict encoding); the
five remaining failures are pre-existing and unrelated to this work.
Two interlocking changes to make the sound dict encoding work at
concrete carriers without sacrificing tcalgebra obligation proofs:

1. **Instance-dict emission is lazy.** Triggered from
   [try_dict_proj] when an Fop with [TCIConcrete] witness is first
   encountered, via a forward-declared [emit_instance_dict_ref] so
   the mutual recursion stays well-typed. Eagerly emitting all
   reducible instances overwhelmed Alt-Ergo (5s timeout) — most
   goals don't need most instance dicts. Eager emission in [init]
   is kept for reducible instances so polymorphic lemma hints can
   still match against axhold facts.

2. **delta_tc reduction at two points.** In [init], the goal is
   pre-reduced so concrete-carrier class-op Fops resolve to their
   user-provided realisations (CoreInt.add etc.) instead of going
   through dict projection. In [emit_instance_dict], the
   substituted ancestor class axioms are similarly reduced — the
   bridges they'd otherwise reference now connect concrete-only
   facts, matching the goal's encoding.

Other adjustments:
- Phase D's projection uses HO form: [proj_ls : dict -> field_ty]
  (single dict arg, function-typed result), applied via
  [apply_highorder]. Avoids lambda eta-expansion (which Alt-Ergo
  handles poorly) and works uniformly for full/partial/over-
  application.
- Per-class axioms-hold is asserted for both tparam dicts (Phase C)
  and instance dicts (Phase B) — polymorphic lemma premises match.
- [ancestors_with_lifts] moved before [emit_instance_dict] (was
  defined later, broken forward reference).

tcalgebra results (5/10 pass cleanly, 5 remaining failures all in
proofs that previously relied on the encoding's unsoundness to
discharge):
  TcMonoid TcRing TcInt[1] TcReal TcBigop pass
  TcBigalg[1] TcBinomial[1] TcNumber TcPoly[1] TcZModP[1]

[1] one obligation each still relies on encoding-unsoundness in the
parent state; sound discharge needs `inv 0 = 0`-style hints that
aren't pulled by select_relevant. Documented as follow-up.

Soundness sanity: `lemma false : false. by smt()` at `t::comring`
no longer goes through (as before this commit).
Two changes to make polymorphic class-op uses fire reliably at concrete
carriers:

1. **First-order projections + lazy HO wrapper** (mirroring [trans_op]):
   - [wcf_proj : dict -> arg_tys -> codom] (first-order, full
     application path — what 99% of uses look like).
   - [wcf_proj_ho]: lazily-emitted function-typed constant with a
     [mk_highorder_func]-style spec axiom, only materialised when an
     Fop is partially applied (e.g. [mop] passed to [associative]).
   - Phase D's [try_dict_proj] picks: full app → first-order direct;
     over-app → first-order then [apply_highorder]; partial → HO
     wrapper applied via [@]. This keeps SMT bridges in first-order
     form (which Alt-Ergo handles without HO machinery), matching
     the existing [trans_op] / [w3op_ho_lsymbol] strategy.

2. **Specialised instances of polymorphic axioms** (extends [trans_axiom]):
   For each lemma over a TC bound, in addition to the dict-passing
   polymorphic version, ALSO emit a substituted-and-reduced version
   at each registered reducible instance whose class matches. The
   substituted form has the carrier and dict pinned to the instance;
   [delta_tc] then resolves class-op Fops to the concrete realisations
   (CoreInt.add etc.). SMT uses these concrete axioms directly without
   having to instantiate the polymorphic version's universal dict.

Closes the [TcInt:60 / intmulz / smt(opprK mulrNz opprK)] gap — the
specialised [mulrNz_at_TcInt_addgroup] now reads
[forall x:int n:int. intmul x (-n) = -(intmul x n)] and Alt-Ergo
chains it with the [have h] hypothesis to handle the c<0 case.

tcalgebra: 6 of 10 pass clean (TcMonoid TcRing TcInt TcReal TcBigop
TcNumber); 4 remain (TcBigalg:198 TcBinomial:14 TcPoly:683 TcZModP:141).
Soundness sanity (false at t::comring) still holds.
[create_op] gated [OneShot.now register] inside [if not known then],
so ops mapped to why3 builtins via [te_known_w3] (e.g. CoreInt.add,
CoreInt.mul, CoreInt.opp) were NEVER cached in [te_op]. Each call
to [trans_op] for such ops re-ran [create_op] from scratch, producing
a fresh [w3op] with a fresh [HO_TODO] — and when [w3op_ho_lsymbol]
fired in HO context (e.g. passing [( * )] to [Top_Logic_associative]),
[mk_highorder_func] emitted yet another [(+)_hoN] / [( * )_hoN] copy
+ spec axiom.

Visible as 10 copies of [(+)_ho], 10 of [( * )_ho], etc. in the 0014
task dump for TcInt.ec — bloating the task and probably slowing the
SMT solver. Moving the [OneShot.now register] call out of the guard
makes the cache fire uniformly, and the dump drops from 749 → 576
lines. tcalgebra status unchanged (6/10 pass clean); soundness
sanity still holds.
[fld_invr1] / [fld__1] / [fld____1] / [fld__2] aren't useful for
debugging — sub all the operator characters before why3's automatic
disambiguation kicks in:

  +     -> plus      *  -> times    -    -> minus
  [-]   -> neg       0  -> zero     1    -> one
  <     -> lt        <= -> le       >    -> gt   >= -> ge
  ( * ) -> times     /  -> div      %    -> mod

Prefix with the class basename so different classes' fields don't
collide either:

  comring_invr   comring_unit   comring_neg
  comring_plus   comring_times  comring_zero  comring_oner
  monoid_mop     monoid_idm
  addgroup_plus  addgroup_neg   addgroup_zero
  ...

tcalgebra status unchanged.
[inst_Top_comring_107002 : Top_TcRing_comring int] →
[inst_int_comring : Top_TcRing_comring int].

Build the instance name from the carrier head-type basename + the
chain-label path (which the TC registration already records, and
which ends in the class itself for chain ancestors — so we don't
need to append the class separately). Bridges and tcinstax axioms
reuse the same [inst_name] for consistency.

For [int <: idomain] the chain now reads:
  inst_int_idomain  (leaf)
  inst_int_comring
  inst_int_comring_addgroup
  inst_int_comring_mulmonoid
  inst_int_comring_addgroup_addmonoid
  inst_int_comring_mulmonoid_monoid          (addmonoid+monoid leg)
  inst_int_comring_addgroup_addmonoid_monoid (mulmonoid+monoid leg)

Each leg of the monoid diamond gets a distinct, leg-disambiguated
dict — readable AND sound.

tcalgebra status unchanged.
For each TC class C, for each parent edge i in C.tc_prts to parent
class P with rename ren_i, emit a why3 function symbol

  C_to_P_<i> : C_dict 'a -> P_dict 'a

plus one spec axiom per parent field f_P matching (by [wcf_origin]
and [wcf_orig_name]) a child field f_C — asserting

  forall (d : C_dict 'a) (xs...), f_P (C_to_P_<i> d) xs... = f_C d xs...

This is the dict-level analogue of [trans_class]'s flat-field
projections, and bridges the polymorphic chain: a comring dict
projects to its addgroup view, which projects to its addmonoid view,
which projects to its monoid view (either leg).

The projection lsymbols are cached per (class, edge index) in the new
[wcd_chain_projs] field. Not yet consumed by [compute_op_dicts] /
[try_dict_proj] — that's the next step, but the dict terms need to
exist before call-site resolution can walk lifts via composition.

tcalgebra: 6/10 still pass clean; soundness sanity still holds. The
extra symbols and axioms are inert until call sites start projecting
through them.
…ssing call sites)

Two helpers that consume the chain-projection lsymbols emitted by
[trans_class] (commit 628ed53):

[walk_dict_lift]: given a starting (dict_term, w3_class_decl) and a
[lift : int list], compose the per-edge [wcd_chain_projs] lsymbols
to produce the (dict_term, w3_class_decl) at the target ancestor
class. This is the dict-level analogue of [walk_lift] (which only
walks the metadata, not term-level projections).

[compute_op_dicts]: at a polymorphic op's call site, walk
[op.op_tparams] paired with the Fop's etyargs witnesses; for each
TC bound, resolve the witness to a (start dict, w3cd) — via
[emit_instance_dict] for TCIConcrete, [le_tdict] for
TCIAbstract `Var, [te_absdict] for TCIAbstract `Abs — and apply
[walk_dict_lift] for the [lift]. Skips OP_TC class ops (handled
by Phase D at call site directly). Falls back to typed-opaque
[missing_dict] placeholder when resolution fails (polymorphic
instances aren't yet handled).

These helpers aren't wired into [create_op] / [trans_app] yet —
that's the next step, but the underlying terms now exist for use.
tcalgebra unchanged: 6/10 pass, soundness OK.
Polymorphic instances ([tci_params <> []]) now emit as why3 lsymbols
[inst_X_Y : <inner dicts> -> <outer dict>], stored in [te_pidict]. A
unified [resolve_witness] handles all four witness shapes
(abstract `Var/`Abs, monomorphic instance, polymorphic instance)
and is shared between [compute_op_dicts] (call-site dict-passing) and
[try_dict_proj] (Phase D class-op projection).

For [TCIConcrete] pointing at a polymorphic instance, [resolve_witness]
recursively resolves each inner witness in [etyargs] (walking ITS lift
via [walk_dict_lift] since the constructor's argument types are at the
inner classes), then applies the constructor.

No bridges or axioms-hold yet for polymorphic instances — the
constructor is opaque, so [comring_<op> (inst_poly_X di) xs] is
type-correct but unconstrained. This is the minimum needed to unblock
[create_op] dict-passing of op bodies whose tparams bind a polymorphic
carrier (the TcPoly:470 type-mismatch we hit earlier).

tcalgebra: 6/10 unchanged (TcMonoid TcRing TcInt TcReal TcBigop
TcNumber); 4 fail unchanged (TcBigalg:198 TcBinomial:14 TcPoly:683
TcZModP:141). No regressions.
create_op now prepends one dict-typed parameter per (tparam, tc) bound
to the lsymbol's domain for non-class ops. Body translation uses an
[le_tdict]-enriched lenv so class-op Fops in op bodies resolve via
Phase D's dict-projection rather than emitting opaque
[Top_<class>_<op>] symbols.

apply_wop accepts ~dict_args; the Fop case in trans_app calls
compute_op_dicts to compute them from the call-site etyargs. The
lsymbol signature is [dict_doms @ textra @ wdom -> wcodom]; both new
and old shapes co-exist (ops without TC bounds produce empty dict_doms
and behave exactly as before).

w3op_ta now returns [(dict_doms, textra, targs, tres)] at the
instantiated types. All non-trivial w3op constructors updated.

For ops where EC's elaboration omits class witnesses at the call site
(subtype generators like to_poly / of_poly), compute_op_dicts emits
typed placeholder dicts so the lsymbol's arity is matched; these
placeholders are unconstrained but type-check.

tcalgebra: 6/10 pass clean (TcMonoid TcRing TcInt TcReal TcBigop
TcNumber); 4 fail at same lines as baseline (TcBigalg:198,
TcBinomial:14, TcPoly:683, TcZModP:141). No regressions.

Soundness check: [lemma false : false. by smt()] at [t::comring]
correctly fails.
Two fixes that compose:

1. Chain-projection spec axioms now match parent/child fields by
   leaf-name (post-edge-rename) instead of (wcf_origin, wcf_orig_name).
   The origin-keyed lookup collides at diamond ancestors: at [comring],
   monoid's [idm] is reached via BOTH legs (renamed to [zero] /
   [oner]); last-write-wins picked the wrong one and emitted
   [mulmonoid_oner (comring_to_mulmonoid_1 d) = comring_zero d]. Now
   correctly emits [... = comring_oner d].

2. [resolve_witness] for [TCIConcrete] walks [lift] through
   [tci_parents] first (analogous to [ecEnv.ml]'s [resolve_lifted]) so
   chain-walked witnesses resolve to the registered SYNTH chain
   instance's dict directly (e.g. [inst_int_comring_mulmonoid_monoid])
   rather than building [mulmonoid_to_monoid_0 inst_int_comring_mulmonoid]
   via chain projections. The two are semantically equal but only the
   direct-instance form matches the [bridge_inst_X_<op>] axioms that
   relate dict projections to concrete realisations. Falls back to
   chain projections for parents missing from [tci_parents].

tcalgebra: 6/10 pass + TcBinomial advanced from fail-at-line-14 to
fail-at-line-35 (now passes fact0 and many subsequent lemmas before
hitting size_bin1's [/#]). TcBigalg, TcPoly, TcZModP failing at same
lines as baseline. No regressions.
in-progress chain is NOT yet registered

Bug: [add_generic_instance] used to register every chain entry as a
[Th_instance] BEFORE [check_tci_axioms]. The env at obligation-
discharge time therefore exposed the in-progress instance (and every
synthesised chain ancestor) to the resolver, matcher, conversion /
[tc_reduce], and SMT translation. An obligation like

    instance idomain with real ... proof unitout by smt().

would close via [instax_inst_real_idomain_unitout : forall x:real,
!real_unit x -> inv x = x] (emitted by the SMT translator for the
not-yet-discharged instance) matching the goal verbatim — circular.
The same hole let user tactics close an obligation by appealing to a
polymorphic lemma over the in-progress class.

Fix: process the chain entries in topological order over
[tci_parents] for the PURPOSE of pre-reducing each obligation form.
When pre-reducing entry X's obligations, X and all chain entries
already processed (ie all of X's strict ancestors in the chain) are
registered in a TEMPORARY scope. [delta_tc] simplification then
rewrites every class-op [Fop] reference whose witness points at a
chain path to the user-provided concrete realisation from the
instance's symbol map. After pre-reduction, X's obligation forms
mention only concrete ops — no class-op [Fop], no chain-path
witness.

[check_tci_axioms] runs against the ORIGINAL scope (no chain entry
registered). The user's proof tactic gets a fully-concrete goal and
has no way to invoke the in-progress instance. After every obligation
discharges, the chain entries are registered permanently — exactly
the post-condition we promise downstream.

The semantics now match what would happen if the user wrote each
chain ancestor as a separate [instance …] declaration in dependency
order: every instance's obligations get discharged against an env
that has all its ancestors registered but not itself or its
descendants. The chain mechanism keeps the one-block syntactic
convenience without the loss of soundness that came from registering
everything up front.

Verified: [lemma false : false by smt()] inside an instance proof at
[t::comring] correctly fails. Soundness probes that previously
derived [1 = 0] inside an in-progress idomain instance no longer
close.

tcalgebra: TcMonoid TcRing TcInt TcBigop pass clean. TcReal,
TcNumber, TcBigalg, TcBinomial, TcPoly, TcZModP now fail at the
obligations / SMT calls that previously closed via circular self-
reference; each is now an honest proof debt — the encoding no
longer fakes it.
Two orthogonal changes:

1. **smt(L<:T1, T2>) syntax**. [pdbmap1] gains [pht_tvi : ptyannot option],
   propagated through [ecScope.Prover.process_dbhint] (now returns both
   the path-set hints and a [path → ty list Mp.t] map of type
   instantiations) into [EcProvers.prover_infos.pr_tvi]. [EcSmt] reads
   [tenv.te_tvi], populated from [pi.pr_tvi] in [check]: when a lemma
   path has type instantiations, [trans_axiom] substitutes the tparams
   to those types, [delta_tc]-reduces the result, and emits a single
   concrete-op axiom — bypassing the polymorphic dict-passing emission.

2. **EC_TC_MINIMAL=1 env var**. When set:
   - Phase B is disabled (no per-instance dicts/bridges/axhold for
     reducible instances).
   - [lenv_of_hyps] skips [emit_tparam_dicts] / [emit_abstract_type_dicts]
     (no per-tparam dicts or projected ancestor axioms).
   - [trans_axiom] for lemmas with TC tparams and no [pr_tvi] entry
     emits nothing (a warning instead). The user must explicitly write
     [smt(L<:T>)] to bring such a lemma in.

The intent: send NO abstract TC machinery to the SMT prover; let the
user pre-instantiate the lemmas they need with [<:T>] so each enters
the task as a concrete-op axiom. Goal: avoid the task-pollution that
buries fast obligations in unrelated axioms (the Phase-B noise that
Z3 / CVC5 can't filter), and force explicit instantiation discipline
in proofs.

Verified on TcInt: with line 60's [smt(opprK mulrNz opprK)] rewritten
to [smt(opprK<:int> mulrNz<:int>)], the whole file passes under
[EC_TC_MINIMAL=1].

Default behavior unchanged (flag off).
Drop the [real_unit] op wrapper — the instance form accepts an inline
lambda for [op unit]. unitout now uses [smt(invr0)] (the [inv 0%r = 0%r]
rule), removing the previous reliance on the circular
[instax_inst_real_idomain_unitout] axiom.
[opprK] and [mulrNz] are TC-polymorphic; in minimal-mode SMT we have
to point them at [int] explicitly. Also drops the duplicate [opprK].
In TC-minimal mode, two issues blocked section-abstract lemmas from
applying to goals at the same abstract carrier:

  - [placeholder_dict] emitted a fresh [missing_dict<N>] for every
    TC-op call site, so the goal and the user-hinted lemma used
    distinct dicts for the same [(*)]. Cached on
    (carrier_w3_ty, tc_name) so calls at the same abstract carrier +
    class share one constant.

  - [trans_class] emitted chain-projection spec axioms describing
    [parent_field(proj d) = child_field(d)]. Skipped in minimal mode:
    we send class operators but not TC axioms.

Also:

  - Track [pi.pr_wanted] in [tenv.te_wanted]. A TC-polymorphic lemma
    explicitly requested via [smt(L)] without a [<:T>] instantiation
    is now a hard error (not a silent skip).

  - Arity-check [<:T1, …>] against the lemma's [ax_tparams] in
    [process_dbhint]; mismatched counts raise [hierror] at scope
    time instead of being dropped silently in [trans_axiom].

  - TcNumber line 747: instantiate [mul0r<:t>] (the only TC-poly
    lemma in that hint set; the rest are section-local).
In TC-minimal mode the dict-projection encoding was emitting one-step
projections at the witness's class (e.g. [tcrealdomain_times d]) while
operator bodies that internally project via chain projections produced
multi-hop forms (e.g. [monoid_mop (mulmonoid_to_monoid_0 (...)) x y]).
The two never matched without chain-projection spec axioms — and even
with them, the noise drowned Z3/CVC5.

This commit:

  - [try_dict_proj] now canonicalizes class-op Fops in minimal mode:
    walk the dict all the way to the OP's class via [lift] and project
    using the op's local name there. Both lemma and operator-body refs
    converge at the SAME (carrier, op_class, lift) key.

  - Pre-emit one flat-constant logic-definition per
    (carrier, reachable-ancestor-class, lift-of-edges) at init time.
    The body is the explicit chain composition; references to the
    constant unfold to the same chain expression when needed. Both
    [try_dict_proj] and [compute_op_dicts] route through this cache —
    every body term becomes a flat [<class>_<op> d_t_<class>_<lift> …]
    rather than a 4-deep nested [proj1 (proj2 (...))] expression.

  - [trans_class] in minimal mode skips emitting chain-proj spec axioms
    (canonicalization makes them redundant) AND emits projection
    lsymbols only for the natively-declared ops of each class
    (inherited ops are reached via origin canonicalization, not via
    leaf-class field clones).

  - [lenv_of_tparams_for_hyp] strips the leading tick from EC tparam
    tysymbol names ([ec_tv_a] instead of ['a]) and [trans_hyp] uses
    [preid_lid] for lsymbol names so the dump's [.why] companion is
    valid Why3 source. [str_p_raw] sanitises operator/punctuation
    chars in path-derived names.

  - [emit_one_dict] gains [?emit_axioms:bool]; minimal-mode dict
    emission passes [~emit_axioms:false] (no [axhold] / no
    [tcaxiom_] decls).

  - Lemma instantiation in [trans_axiom] uses
    [EcUnify.UniEnv.opentvi] to resolve witnesses for [smt(L<:T>)]
    hints — the same path as proof-term elaboration. Handles
    section-abstract carriers correctly, replacing the hand-rolled
    [List.find_opt] lookup.

  - [process_dbhint] now arity-checks [<:T1,…>] against
    [ax_tparams] at scope time (mismatched count is a hard
    [hierror], not a silent skip in [trans_axiom]).

Status: Alt-Ergo proves the TcBigalg:291 task in 0.02s. Z3 and CVC5
still struggle (Unknown / Timeout) despite identical SMT-symbol
matching across lemma / hypotheses / goal — investigation continues
(possibly per-call-site specialisation à la option C).
…e 1)

In TC-minimal mode, [try_dict_proj] now emits a CONCRETE-TYPED lsymbol
per (carrier, op_class, lift) instead of a dict-passing projection.
This mimics what clone-based theories produce: one fresh first-order
function symbol per (concrete instantiation, leg), with no polymorphism
or dict argument visible to the SMT prover.

  - Cache keyed by (carrier_w3_ty, op_class_path, local_name) in
    [te_carrier_ops]. Each entry is a fresh lsymbol whose
    [ls_args]/[ls_value] come from substituting the op's self-tparam
    with the carrier in the EC signature.

  - Lemma side (after [opentvi]) and operator-body side at the same
    (carrier, leg) hit the same cache entry — bytewise-identical SMT
    terms across the task. Z3 and CVC5 pattern-match without needing
    to instantiate polymorphic types or unfold dict projections.

  - Partial / over-application paths now branch on whether we
    resolved to [`Concrete ls] (minimal mode) or [`Field (d, w3cd, f)]
    (dict-passing mode); HO wrappers for the concrete form reuse
    [mk_highorder_func] from regular-op translation.

Sweep at this point (regular ops with TC tparams still polymorphic,
i.e. [bigM] keeps a dict argument — Phase 2 will specialize those
per call site too):

  PASS: TcMonoid, TcRing, TcInt, TcReal, TcNumber, TcBigop, TcBigalg,
        TcPolySmokeTest, TcZModPSmokeTest
  FAIL: TcBinomial:14, TcPoly, TcZModP:294
…nches

  - Remove the [tc_minimal_mode] env-var flag and all
    [if (not) (tc_minimal_mode ()) then …] conditionals — keep the
    minimal-mode body, drop the polymorphic-dict-passing branches.

  - Drop chain-projection spec axiom emission from [trans_class]
    (replaced by origin canonicalization in [try_dict_proj]).

  - Drop the polymorphic-dict-passing axiom encoding in [trans_axiom]
    plus its [_at_<carrier>_<class>] instance specialization
    (replaced by [do_instantiated] via [smt(L<:T>)] hints).

  - Drop the ancestor-class flattening in [trans_class] (only emit
    fields for natively-declared ops of the current class).

  - Drop the [Field] case of [try_dict_proj]'s [resolved] disjunction
    — class-op references always resolve to the concrete-typed
    [`Concrete] lsymbol now.

  - [emit_one_dict] no longer takes an [?emit_axioms] parameter; the
    axhold / tcaxiom emission paths are gone. It always emits
    dict-const + flat-constant logic-defs for ancestors.

  - [init] simplifies — no Phase B branch, just direct emission of
    tparam + abstract-type dicts.

Sweep unchanged from previous commit: 9 PASS, 3 FAIL (TcBinomial:14,
TcPoly, TcZModP:294).
…rrier

Three call sites of [smt(ge0_deg)] and two of [smt(oner_neq0)] in
sections declaring [c :: comring] / [c :: idomain]. Both lemmas are
TC-polymorphic; the minimal-mode encoding requires explicit [<:c>]
instantiation.
  - TcZModP:unitE — replace [smt(zmod_mulrC zmod_mul1r choicebP)]
    with [apply: (unitr0<:zmod>)] (analogue of main's
    [apply: ZModpRing.unitr0]).
  - TcZModP:zmod_mulf_eq0 — port main's case-analysis proof
    using [mul0r] / [mulr0] / [mulrI] / [unitE].
  - TcBinomial:fact0 — replace [rewrite /fact /bigiM /bigM
    range_geq /#] with [rewrite /fact big_geq // ler_naddl]
    (main's proof).
  - TcBinomial:size_bin1 — replace the [/#] tail with main's
    [subz0 ler_maxr ?size_ge0] rewrite chain.

All four had been rewritten with [smt(...)] / [/#] tails that
relied on auto-relevance to pull in lemmas the minimal-mode
encoding doesn't surface. Backporting main's proofs verbatim
closes them.
…rier

[pmulr_gt0] is TC-polymorphic; the minimal-mode SMT encoding requires
explicit [<:t>] instantiation (section's [t :: tcrealdomain]).
[prodr_ge0_seq] is section-local with 0 tparams — left bare.
Conflicts resolved in 6 files, all from origin/main's [ac42e2a
subtype: generalize over section-declared free types at section close].
That commit's subtype-tracking change had already been applied to
HEAD's [module Ty.add_subtype]/[Ty.add] independently (tyd_subtype
field threaded through, carrier+pred stored on the tydecl), so the
conflict resolutions all KEEP HEAD's structure:

  - [tydecl] keeps HEAD's [tyd_resolve] field on top of the
    [tyd_subtype] field both branches added.
  - Type-body constructors stay as polymorphic variants
    ([`Abstract], [`Concrete]) — TC needs the typeclass-list payload
    on [`Abstract tc] that main's plain constructor form doesn't
    carry.
  - Subtype-related tydecl construction sites all carry the
    [tyd_resolve = …] field.
  - [generalize_tydecl] in [ecSection] keeps the destructuring
    [fun (id, _) -> …] for HEAD's tparam-with-tcs tuple shape.
  - [ecScope.ml]'s conflict was a structural duplicate (origin/main's
    [module Ty] block was being pasted before HEAD's [module Search];
    both branches already have [module Ty] independently) — dropped
    the duplicated insertion.

Verified: [dune build] clean, tcalgebra sweep 12/12 PASS after merge.
Commit [0b2f706 TC: matcher recognizes ops with same canonical
origin] left this line as a broken split [rewrite ... addrAC.
rewrite !addzA. ler_subr_addl (...).] — apparent WIP that didn't
get re-joined. Restoring main's single-line form
[rewrite ... addrAC !addrA ler_subr_addl (IntID.addrC 1).].
Commit [5e04bf6 TC: drain pending TC-constraints before [try_delta]'s
tc_reduce check] added [flush_tc_problems] + [norm f1] / [norm f2]
at the entrance of [try_delta]. The flush is needed; the [norm] calls
are not — they re-substitute ev/tu bindings that may have been pinned
during earlier sibling-argument matches, which can change which heads
appear after [destr_app] and silently steer the case dispatch away
from the [Op.reducible] branch that would otherwise apply.

Concrete regression: [theories/algebra/IntDiv.ec:1234]'s
[rewrite -(big_mapT _ idfun)] failed with [nothing to rewrite]
because the pattern's [idfun \o ?h] couldn't unfold past the norm-
substituted form. Dropping the [norm] calls restores the match
(IntDiv.ec passes again; tcalgebra 12/12 still PASS).

[flush_tc_problems] alone is enough to make the queued [TcCtt]
problems get resolved so [tc_reducible] can fire on the now-pinned
witnesses.
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.

3 participants