Stefan pushed to branch main at Stefan / Typer
Commits:
b07d2ac3 by Maxim Bernard at 2024-07-16T15:33:20-04:00
Keep track of residues during elaboration, in case unification becomes
possible later.
* src/elab.ml:
Check for remaining residues after elaboration of definitions, but before
generalization.
* src/instargs.ml:
Don't unify when searching for matches, simply check and discard
side-effects (unless a match is found).
* src/unification.ml:
Keep track of residues in a global list, add them each time `unify'` is
to return a `CKresidual`.
Change the behavior of `unify` to only return impossible contraints,
since otherwise it causes errors to be raised on residues.
Provide a `check_unifiable` function to perform unification without
side-effects.
* tests/unify_test.ml:
Adapt tests to account for new `unify` behavior.
- - - - -
4 changed files:
- src/elab.ml
- src/instargs.ml
- src/unification.ml
- tests/unify_test.ml
Changes:
=====================================
src/elab.ml
=====================================
@@ -1428,6 +1428,8 @@ and infer_and_generalize_type (ctx : elab_context) se name =
and infer_and_generalize_def (ctx : elab_context) se =
let nctx = ectx_new_scope ctx in
let (e,t) = infer se nctx in
+ Unif.check_no_residues (location e);
+ Unif.clean_residues ();
let g = resolve_instances_and_generalize nctx e in
let e' = g wrapLambda e in
let t' = g (fun ne name t _l e
=====================================
src/instargs.ml
=====================================
@@ -106,7 +106,7 @@ let try_match t1 t2 lctx sl =
List.exists (function | (Unif.CKimpossible,_,_,_) -> true
| _ -> false)
constraints in
- match Unif.unify ~matching:sl t1 t2 lctx with
+ match Unif.check_unifiable ~matching:sl t1 t2 lctx with
| [] -> Match
| constraints when has_impossible constraints -> Impossible
| _ -> Possible
=====================================
src/unification.ml
=====================================
@@ -44,6 +44,32 @@ let create_metavar (ctx : lexp_context) (sl : scope_level) (t : ltype)
let dloc = DB.dloc
let dsinfo = DB.dsinfo
+(* For every definition, keep track of all residues during elaboration. There
+ is a chance they can be unified later (when some metavariable becomes
+ instanciated, for example). *)
+let current_residues = ref ([] : (lexp_context * lexp * lexp) list)
+
+let add_residue (ctx : lexp_context) (e1 : lexp) (e2 : lexp) =
+ current_residues := (ctx, e1, e2) :: !current_residues
+
+let check_no_residues (first_def_loc : Source.Location.t) : unit =
+ (* Raises an error for every remaining residue. *)
+ List.iter
+ (fun (_ctx, lxp1, lxp2) ->
+ Log.log_error
+ ~section:"UNIF"
+ ~loc:first_def_loc
+ ("@[<v>Remaining residue. Can't unify:"
+ ^^ "@, @[<hov 2>%a@]"
+ ^^ "@,with:"
+ ^^ "@, @[<hov 2>%a@]@]")
+ Fmt.pp_print_lexp (clean lxp1)
+ Fmt.pp_print_lexp (clean lxp2))
+ !current_residues
+
+let clean_residues () : unit =
+ current_residues := []
+
(* For convenience *)
type constraint_kind =
| CKimpossible (* Unification is simply impossible. *)
@@ -55,14 +81,6 @@ type constraints = (constraint_kind * lexp_context * lexp * lexp) list
type return_type = constraints
-(* The association of a metavariable must change the scope levels of
- the metavariables that are introduced to a wider scope. Here we
- assume that this has already been done. This happens in `occurs_in`
- during unification. *)
-let associate (id: meta_id) (lxp: lexp) : unit =
- (* FIXME: Check that the types are convertible? *)
- metavar_table := U.IMap.add id (MVal lxp) (!metavar_table)
-
let occurs_in (id: meta_id) (e : lexp) : bool = match metavar_lookup id with
| MVal _ -> Log.internal_error
"Checking occurrence of an instantiated metavar!!"
@@ -75,7 +93,7 @@ let occurs_in (id: meta_id) (e : lexp) : bool = match metavar_lookup id with
| SortLevel (SLlub (e1, e2)) -> oi e1 || oi e2
| Sort (_, Stype e) -> oi e
| Sort (_, (StypeOmega | StypeLevel)) -> false
- | Builtin _ -> false
+ | Builtin (_, t) -> oi t
| Var (_, _i) -> false
| Proj (_,lxp, _) -> oi lxp
| Susp (_e, _s) -> Log.internal_error "`e` should be \"clean\" here!?"
@@ -115,11 +133,14 @@ let occurs_in (id: meta_id) (e : lexp) : bool = match metavar_lookup id with
metavar_table := U.IMap.add id' (MVar (sl, t, cl))
(!metavar_table);
false) in
- let old_mvt = (!metavar_table) in
+ let old_mvt = !metavar_table in
+ let old_residue_list = !current_residues in
if oi e then
(* Undo the side-effects since we're not going to instantiate the
var after all! *)
- (metavar_table := old_mvt; true)
+ (metavar_table := old_mvt;
+ current_residues := old_residue_list;
+ true)
else false
(* When unifying a metavar with itself, if the two metavars don't
@@ -218,7 +239,39 @@ let rec unify ?(matching : scope_level option)
(e1: lexp) (e2: lexp)
(ctx : lexp_context)
: return_type =
- unify' e1 e2 ctx OL.set_empty matching
+ let constraints = unify' e1 e2 ctx OL.set_empty matching in
+ List.filter (fun (kind, _, _, _) -> kind <> CKresidual) constraints
+
+(* The association of a metavariable must change the scope levels of
+ the metavariables that are introduced to a wider scope. Here we
+ assume that this has already been done. This happens in `occurs_in`
+ during unification. *)
+and associate (id: meta_id) (lxp: lexp) : unit =
+ (* FIXME: Check that the types are convertible? *)
+ metavar_table := U.IMap.add id (MVal lxp) (!metavar_table);
+ (* Go through residues, see if, having instanciated this metavar,
+ we can unify any residue. *)
+ let old_residue_list = !current_residues in
+ (* Reason for purging the list: any lexp pair that is still non-unifiable
+ will be re-added to the list by `unify`. *)
+ clean_residues ();
+ let new_constraints = List.concat_map
+ (fun (ctx, lxp1, lxp2) -> unify lxp1 lxp2 ctx)
+ old_residue_list in
+ (* In case we find out some expressions are impossible to unify,
+ raise an error. *)
+ List.iter
+ (fun (_, _, lxp1, lxp2) ->
+ Log.log_error
+ ~section:"UNIF"
+ ~loc:(location lxp1)
+ ("@[<v>Unresolvable constraint. Expression:"
+ ^^ "@, @[<hov 2>%a@]"
+ ^^ "@,cannot be unified with:"
+ ^^ "@, @[<hov 2>%a@]@]")
+ Fmt.pp_print_lexp (clean lxp1)
+ Fmt.pp_print_lexp (clean lxp2))
+ new_constraints
and unify' (e1: lexp) (e2: lexp)
(ctx : lexp_context) (vs : OL.set_plexp)
@@ -279,8 +332,7 @@ and unify' (e1: lexp) (e2: lexp)
| (Cons _, _) -> unify_cons msl e1' e2' ctx vs'
| _ -> (if OL.conv_p ctx e1' e2' then []
- else ((* print_string "Unification failure on default\n"; *)
- [(CKresidual, ctx, e1, e2)]))
+ else (add_residue ctx e1' e2'; [(CKresidual, ctx, e1, e2)]))
(************************* Type specific unify *******************************)
@@ -341,7 +393,7 @@ and unify_metavar (matching : scope_level option)
"`lexp_whnf` returned an instantiated metavar!!"
| MVar (sl, t, _) -> push_susp t s, sl in
if not (matching_instantiation_check matching sl)
- then [(CKresidual, ctx, lxp1, lxp2)] else
+ then (add_residue ctx lxp1 lxp2; [(CKresidual, ctx, lxp1, lxp2)]) else
match Inverse_subst.apply_inv_subst lxp s with
| exception Inverse_subst.Not_invertible
-> log_info
@@ -351,7 +403,7 @@ and unify_metavar (matching : scope_level option)
^^ "@, @[<hov 2>%a@]@]")
Fmt.pp_print_subst s
pp_print_clean_lexp lxp;
- [(CKresidual, ctx, lxp1, lxp2)]
+ (add_residue ctx lxp1 lxp2; [(CKresidual, ctx, lxp1, lxp2)])
| lxp' when occurs_in idx lxp' -> [(CKimpossible, ctx, lxp1, lxp2)]
| lxp'
-> associate idx lxp';
@@ -370,7 +422,7 @@ and unify_metavar (matching : scope_level option)
pp_print_clean_lexp t
pp_print_clean_lexp (OL.get_type ctx lxp)
pp_print_clean_lexp lxp;
- [(CKresidual, ctx, lxp1, lxp2)] in
+ (add_residue ctx lxp1 lxp2; [(CKresidual, ctx, lxp1, lxp2)]) in
(* FIXME Here, we unify lxp1 with lxp2 again, because that
the metavariables occuring in the associated term might
have different substitutions from the corresponding
@@ -462,7 +514,7 @@ and unify_var (var: lexp) (lxp: lexp) ctx
: return_type =
match (lexp_lexp' var, lexp_lexp' lxp) with
| (Var _, Var _) when OL.conv_p ctx var lxp -> []
- | (_, _) -> [(CKresidual, ctx, var, lxp)]
+ | (_, _) -> (add_residue ctx var lxp; [(CKresidual, ctx, var, lxp)])
(** Unify a Call (call) and a lexp (lxp)
- Call , Call -> UNIFY
@@ -480,7 +532,7 @@ and unify_call (matching : scope_level option) (call: lexp) (lxp: lexp) ctx vs
[]
(List.combine lxp_list1 lxp_list2)
with Invalid_argument _ (* Lists of diff. length in combine. *)
- -> [(CKresidual, ctx, call, lxp)])
+ -> (add_residue ctx call lxp; [(CKresidual, ctx, call, lxp)]))
| (call', lxp') ->
let head_left = match call' with
| Call (_, head_left, _) -> head_left
@@ -501,7 +553,7 @@ and unify_call (matching : scope_level option) (call: lexp) (lxp: lexp) ctx vs
inconvertible heads will remain. *)
[(CKimpossible, ctx, head_left, head_right)]
else
- [(CKresidual, ctx, call, lxp)]
+ (add_residue ctx call lxp; [(CKresidual, ctx, call, lxp)])
(** Unify a Case with a lexp
- Case, Case -> try to unify
@@ -697,3 +749,19 @@ and unify_cons (matching : scope_level option) lxp1 lxp2 ctx vs =
| (Cons (it1, (_, l1)), Cons (it2, (_, l2))) when l1 = l2
-> unify' it1 it2 ctx vs matching
| _, _ -> [(CKimpossible, ctx, lxp1, lxp2)]
+
+(* This function attempts to unify `e1` with `e2`, but discards all
+ side-effects, except if no impossible constraints nor any residues have
+ been found.
+ In the latter case, any instanciated metavariables are kept instanciated. *)
+let check_unifiable ?(matching : scope_level option)
+ (e1: lexp) (e2: lexp)
+ (ctx : lexp_context) =
+ let old_residue_list = !current_residues in
+ let old_metavars = !metavar_table in
+ let unify_result = unify' e1 e2 ctx OL.set_empty matching in
+ current_residues := old_residue_list;
+ match unify_result with
+ | [] -> []
+ | _ -> (metavar_table := old_metavars;
+ unify_result)
=====================================
tests/unify_test.ml
=====================================
@@ -55,16 +55,19 @@ let unif_output ?matching (lxp1: lexp) (lxp2: lexp) ctx =
let orig_subst = !metavar_table in
let constraints = unify ?matching lxp1 lxp2 ctx in
match constraints with
- | []
- -> let new_subst = !metavar_table in
- if orig_subst == new_subst
- then (Equivalent, constraints)
- else (Unification, constraints)
- | (CKresidual, _, _, _)::_ -> (Constraint, constraints)
- | (CKimpossible, _, _, _)::_ -> (Nothing, constraints)
+ | [] ->
+ if !current_residues == [] then
+ let new_subst = !metavar_table in
+ if orig_subst == new_subst
+ then (Equivalent, constraints)
+ else (Unification, constraints)
+ else
+ (Constraint, constraints)
+ | _ -> (Nothing, constraints)
let add_unif_test name ?matching ?(ectx=ectx) lxp_a lxp_b expected =
add_test "UNIFICATION" name (fun () ->
+ clean_residues ();
let (r, _) = unif_output ?matching lxp_a lxp_b (DB.ectx_to_lctx ectx) in
if r = expected then
View it on GitLab: https://gitlab.com/monnier/typer/-/commit/b07d2ac397b7ca5b8e78917b0887eb01c…
--
View it on GitLab: https://gitlab.com/monnier/typer/-/commit/b07d2ac397b7ca5b8e78917b0887eb01c…
You're receiving this email because of your account on gitlab.com.