Skip to content

feat(clone): reintroduce module overriding for stateless modules - #1104

Merged
strub merged 1 commit into
mainfrom
clones-with-modules
Aug 28, 2026
Merged

feat(clone): reintroduce module overriding for stateless modules#1104
strub merged 1 commit into
mainfrom
clones-with-modules

Conversation

@strub

@strub strub commented Aug 27, 2026

Copy link
Copy Markdown
Member

Module overriding in clones (clone T with module X <- Y, also <=, =, and theory S <- S' for theories containing modules) was removed by #458 as a fix for the unsoundness reported in #380: a clone transfers the statements of a theory without replaying their proofs, and proofs freely use the disjointness of program variables owned by distinct modules; identifying two modules that own state can therefore turn a proved statement into a false one.

That argument does not apply to modules owning no program variable. The only facts the logic has about a concrete module are judgments on its procedures, its glob, the disjointness of its own variables, and memory restrictions (normalised to variable sets). With an empty own-glob and AST-equal components, the source and target modules are indiscernible, so substituting one for the other is a renaming.

This restores the override for exactly that fragment. An override is accepted iff:

  • the source and the target own no program variable, transitively through their sub-modules (EcCoreModules.module_own_state; calling external stateful modules and taking module parameters is allowed);
  • the target is a concrete top-level module;
  • the components are AST-equal after the clone substitution (EqTest.for_mexpr ~body:false, so an alias target is accepted).

Otherwise the clone fails at the override clause, naming the offending module: "declares state and cannot be overridden", "is not a concrete top-level module", or "is incompatible". theory S <- S' is likewise accepted when every module of S is stateless, and rejected naming the first stateful one (this replaces the former blanket rejection of any theory containing a module). Stateful modules keep failing as before.

Mode semantics follow the pre-#458 code: <- rewrites references to the target and drops the item, <= additionally keeps an alias, = keeps an alias only. The alias mode now honours rename [module].

Both #380 counter-examples are kept as failing regression tests (tests/clone-module-stateless.ec).

Also fix a pre-existing defect in EqTest.for_module_expr reached by the new check: the two functor parameter lists were bound with add_modules (a List.fold_left2) without being compared, unlike for_module_sig. An arity mismatch raised Invalid_argument, and parameters of different module types were treated as equal whenever the body did not use them. Add the same for_module_type guard.

Related: #380 (left open — the stateful case is not addressed), #458 (the removal this partially reverts).

Module overriding in clones (`clone T with module X <- Y`, also `<=`,
`=`, and `theory S <- S'` for theories containing modules) was removed
by #458 as a fix for the unsoundness reported in #380: a clone transfers
the statements of a theory without replaying their proofs, and proofs
freely use the disjointness of program variables owned by distinct
modules; identifying two modules that own state can therefore turn a
proved statement into a false one.

That argument does not apply to modules owning no program variable.
The only facts the logic has about a concrete module are judgments on
its procedures, its glob, the disjointness of its own variables, and
memory restrictions (normalised to variable sets).  With an empty
own-glob and AST-equal components, the source and target modules are
indiscernible, so substituting one for the other is a renaming.

This restores the override for exactly that fragment.  An override is
accepted iff:

- the source and the target own no program variable, transitively
  through their sub-modules (`EcCoreModules.module_own_state`; calling
  external stateful modules and taking module parameters is allowed);
- the target is a concrete top-level module;
- the components are AST-equal after the clone substitution
  (`EqTest.for_mexpr ~body:false`, so an alias target is accepted).

Otherwise the clone fails at the override clause, naming the offending
module: "declares state and cannot be overridden", "is not a concrete
top-level module", or "is incompatible".  `theory S <- S'` is likewise
accepted when every module of `S` is stateless, and rejected naming the
first stateful one (this replaces the former blanket rejection of any
theory containing a module).  Stateful modules keep failing as before.

Mode semantics follow the pre-#458 code: `<-` rewrites references to the
target and drops the item, `<=` additionally keeps an alias, `=` keeps
an alias only.  The alias mode now honours `rename [module]`.

Both #380 counter-examples are kept as failing regression tests.

Also fix a pre-existing defect in `EqTest.for_module_expr` reached by
the new check: the two functor parameter lists were bound with
`add_modules` (a `List.fold_left2`) without being compared, unlike
`for_module_sig`.  An arity mismatch raised `Invalid_argument`, and
parameters of different module types were treated as equal whenever the
body did not use them.  Add the same `for_module_type` guard.
@strub
strub force-pushed the clones-with-modules branch from f85ba74 to f94761f Compare August 27, 2026 14:17
strub added a commit that referenced this pull request Aug 27, 2026
Module overriding in clones (`clone T with module X <- Y`, also `<=`,
`=`, and `theory S <- S'` for theories containing modules) was removed
by #458 as a fix for the unsoundness reported in #380: a clone transfers
the statements of a theory without replaying their proofs, and proofs
freely use the disjointness of program variables owned by distinct
modules; identifying two modules that own state can therefore turn a
proved statement into a false one.

That argument does not apply to modules owning no program variable.
The only facts the logic has about a concrete module are judgments on
its procedures, its glob, the disjointness of its own variables, and
memory restrictions (normalised to variable sets).  With an empty
own-glob and AST-equal components, the source and target modules are
indiscernible, so substituting one for the other is a renaming.

This restores the override for exactly that fragment.  An override is
accepted iff:

- the source and the target own no program variable, transitively
  through their sub-modules (`EcCoreModules.module_own_state`; calling
  external stateful modules and taking module parameters is allowed);
- the target is a concrete top-level module;
- the components are AST-equal after the clone substitution
  (`EqTest.for_mexpr ~body:false`, so an alias target is accepted).

Otherwise the clone fails at the override clause, naming the offending
module: "declares state and cannot be overridden", "is not a concrete
top-level module", or "is incompatible".  `theory S <- S'` is likewise
accepted when every module of `S` is stateless, and rejected naming the
first stateful one (this replaces the former blanket rejection of any
theory containing a module).  Stateful modules keep failing as before.

Mode semantics follow the pre-#458 code: `<-` rewrites references to the
target and drops the item, `<=` additionally keeps an alias, `=` keeps
an alias only.  The alias mode now honours `rename [module]`.

Both #380 counter-examples are kept as failing regression tests.

Also fix a pre-existing defect in `EqTest.for_module_expr` reached by
the new check: the two functor parameter lists were bound with
`add_modules` (a `List.fold_left2`) without being compared, unlike
`for_module_sig`.  An arity mismatch raised `Invalid_argument`, and
parameters of different module types were treated as equal whenever the
body did not use them.  Add the same `for_module_type` guard.

Cherry-picked from f94761f (clones-with-modules, PR #1104) onto a base
predating 01fce12, with two adaptations: `add_moddef` still takes a
`src:path` here, and `subst_mpath` re-rooted a `sb_moddef` hit with the
*unsubstituted* functor arguments (`mp.m_args` instead of `args`), so a
transferred lemma over a functor kept referring to the stale module
ident of its refreshed binder (`forall (_ <: T), … G(A) …`).  Fixed by
using the substituted arguments, as the rewrite in 01fce12 does.
@strub
strub added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 497248a Aug 28, 2026
19 checks passed
@strub
strub deleted the clones-with-modules branch August 28, 2026 17:36
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.

2 participants