Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing an admit_termination attribute #2896

Merged
merged 7 commits into from
Apr 27, 2023
Merged

Conversation

mtzguido
Copy link
Member

Currently, I don't think we have a good way of working on a total function while disregarding its termination. This can be useful when the termination is non-trivial (although perhaps evident to a human) so we want to postpone its proof, but without admiting anything else in the function. (This is exactly my reason to add it, working on some big lemma over reflected syntax.)

This PR then introduces an attribute admit_termination that can be used to disable termination checking for a given letbinding, by not refining its domain with a precedes relation (as if it were in a non-total effect). So this works:

[@@admit_termination]
let rec f (x:nat) = f x

But this does not:

[@@expect_failure [19]; admit_termination]
let rec f2 (x:nat) : nat = f2 x - 1
// Subtyping check failed; expected type Prims.nat; got type Prims.int; The SMT solver could not prove the query.

The attribute can actually be attached to individual letbindings in a recursive group, not necessarily the entire group, see this:

let top1 () =
  let rec blah () = ()
  [@@admit_termination]
  and f (x:int) : int = f x
  in ()

(* This admits the termination of blah, not of f! *)
[@@expect_failure [19]]
let top2 () =
  [@@admit_termination]
  let rec blah () = ()
  and f (x:int) : int = f x
  in ()

(* Note that the admit_termination attribute applies to
*calls* to the admitted binding, and not the definition
of the binding. The difference is significant in mutual
recursion, see ahead. *)

(* Works: the body of f respects the termination argument
for g. Since we admit the termination of f, the body of g
has no obligation, and verifies. *)
let top3 () =
  [@@admit_termination]
  let rec f (x:pos) : int = g (x-1)
  and g (x:nat) : int = f (x+1)
  in ()

(* Fails: the body of g does not respect the termination
argument for f, which is not admitted. *)
[@@expect_failure [19]]
let top4 () =
  let rec f (x:pos) : int = g (x-1)
  [@@admit_termination]
  and g (x:nat) : int = f (x+1)
  in ()

However, we don't have surface syntax to attach attributes to top-level letbindings (only to the sigelt). And these attributes were not propagated to the letbindings--- this PR changes that. I took a look at adding syntax for attributes for bindings, but it's not clear what it should look like, since the sigelt can also have attributes.

As we do not have per-letbinding attributes (a syntax for it is
unclear), at least provide this way to attach attributes to each
letbinding for options/behaviors that need them. If a per-letbinding
attribute syntax comes up, this can be reverted.
This attribute can be attached to a recursive group in order to disable
termination checking for it. It works over the entire group, propagating
the `admit_termination` to each sigelt. The typechecker, however,
can (and does) make the decision for each particular binding, so if
per-binding attribute syntax appears this can easily be used to admit
the termination of one particular definition in the group.

Use of this attribute triggers a Warn_WarnOnUse warning, which is silent
by default but picked up by --report_assumes.
@mtzguido mtzguido merged commit f89ee53 into taramana_no_steel Apr 27, 2023
@mtzguido mtzguido deleted the guido_misc branch April 27, 2023 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant