-
Notifications
You must be signed in to change notification settings - Fork 231
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
Enable subtyping in ctrl_rewrite (and hence, l_to_r) #2482
base: master
Are you sure you want to change the base?
Conversation
Update on this: generating a uvar for the equality in all cases is a bit too powerful. The new solution proposes to generate a uvar for the equality type conditionally, based on a flag passed to The current implementation passes CI, and also allows the rewritings presented in #2476 |
@@ -599,12 +599,12 @@ let unfold_def (t:term) : Tac unit = | |||
equalities. The lemmas need to prove *propositional* equalities, that | |||
is, using [==]. *) | |||
let l_to_r (lems:list term) : Tac unit = | |||
let first_or_trefl () : Tac unit = | |||
let first_or_skip () : Tac unit = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments about this change:
IIUC, rather than applying trefl in case all the rewriting lemmas fail, this change will instead fail silently.
- I don't see changes anywhere to any callers of l_to_r. How come?
- Because of the fold_left, if I write
l_to_r [l1;l2;l3]
wouldn't this actually try to apply
(fun () -> apply_lemma_r1 l3) `or_else`
(fun () -> apply_lemma_r1 l2) `or_else`
(fun () -> apply_lemma_r1 l1) `or_else`
(fun () -> fail_silently "SKIP")
i.e., isn't this backwards? Or rather isn't it more like "last_or_skip". Looks like that was the original behavior too ... is it intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: 1, the implementation of ctrl_rewrite
hardcodes a special behaviour for a failure with Msg "SKIP", where it simply rolls back the current proofstate to before generating the equality, hence avoiding unresolved uvars when all rewritings fail. Note that the previous use of trefl
had a similar "fallback" workflow, but trefl
does not solve the type of the equality, which would here lead to an unresolved uvar in the default case.
Re: 2, you're indeed right, testing it with the small program below, the last lemma is indeed the one called. Another slightly annoying thing is that it stops at the first lemma it can apply instead of trying them all if rewritings can be chained. I think all our usecases so far use l_to_r
with only one lemma, but it'd be good to change in the future.
assume val f : int
assume val g : int
assume val h : int
assume
val lem1 (u:unit) : Lemma (f == g)
assume
val lem2 (u:unit) : Lemma (f == h)
open FStar.Tactics
let test () =
assert (f == h) by (l_to_r [(`lem1); (`lem2)]; dump "here")
bind ( | ||
(* If this type is already a uvar, no need to generate one. *) | ||
if not allow_subtyping || U.is_uvar lcomp.res_typ then ret lcomp.res_typ else | ||
bind (new_uvar "do_rewrite.eq_ty" env (fst (U.type_u ())) (rangeof g0)) (fun x -> ret (fst x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks good to me, thanks
Overall, this looks pretty good. However, it would be great to have some test cases for l_to_r that exploit the new behavior. |
(rewriter : rewriter_ty) | ||
(env : env) | ||
(tm : term) | ||
: tac term | ||
= bind (catch (__do_rewrite g0 rewriter env tm)) (function | ||
= bind (catch (__do_rewrite g0 allow_subtyping rewriter env tm)) (function | ||
| Inl (TacticFailure "SKIP") -> ret tm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@R1kM : I think our last conversation about this PR was to try to find a way to avoid the use of matching on this string constant, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikswamy My understanding was that, we would ideally like to avoid relying on this string, but that it might be a bit hard without refactoring ctrl_rewrite, and hence should be done in a subsequent PR since this mechanism was already part of ctrl_rewrite. Some possible ideas to do this I had:
- Raise an actual failure (instead of fail "SKIP") if none of the
l_to_r
lemma rewritings worked, and catch it when callingt_pointwise
in the implementation ofl_to_r
. The problem is that this prevents deep rewritings, asl_to_r
will now stop at the first subterm where rewriting is not possible. - Rollback by default during
ctrl_rewrite
if no rewriting succeeded. I'm not sure this can be distinguished from a standard exception, and there are likely cases where we would want exceptions raised during ctrl_rewrite to bubble up. - Pattern-match on a new exception (say,
CtrlRewriteSkip
) instead ofTacticFailure "SKIP"
, and raise this exception at the end ofl_to_r
if no rewriting was performed. This might be the better solution, but 1. I'm not sure how removing the pattern-matching onTacticFailure "SKIP"
would impact existing code, and 2. is it possible to expose not just functions, but exceptions from FStar.Tactics.Basic.fsti, so that they can be accessed inFStar.Tactics.Builtins
?
#2476 reported issues when using
l_to_r
to perform rewritings.l_to_r
is based on the primitivectrl_rewrite
, which traverses all subtermst
of a given term, creates a goal of the shapet == ?u
, and tries to apply lemma a rewriter lemma provided by the user. The core problem was that the goal created also sets a type for the equality based on the type of t in the environment, i.e.eq2 #(type_of t) t ?u
. With @TWal and @W95Psp, we observed that this type was sometimes imprecise, for instance, it was often without guards, and led to unification errors when trying to apply the rewriter lemma.To solve this issue, this PR does not set the type of the eq2 goal, but instead creates a new unification variable for it. The rationale is that rewritings should focus only on the terms to rewrite, assuming all equalities are well-typed, the eq2 type itself is superfluous information (which, in this case, leads to an overly restrictive behaviour). Unfortunately, because of the structure of do_rewrite, there is no rollback of the environment when a rewriting fails and uvars created when generating the eq2 goal must be solved. To do so, I'm currently querying the unifier to ensure that the type of the subterm is a subtype of the type indicated in eq2, but there might be a better way to do that.
Fixes #2476