(************************************************************************) (* * The Rocq Prover / The Rocq Development Team *) (* v * Copyright INRIA, CNRS and contributors *) (* <O___,, * (see version control and CREDITS file for authors & dates) *) (* \VV/ **************************************************************) (* // * This file is distributed under the terms of the *) (* * GNU Lesser General Public License Version 2.1 *) (* * (see LICENSE file for the text of the license) *) (************************************************************************)
open Pp open Util open CErrors open Names open Constr open Context open Evd open EConstr open Vars open Environ open Mod_subst open Globnames open Libobject open Namegen open Libnames open Termops open Inductiveops open Typeclasses open Pattern open Patternops open Tacred open Printer
module NamedDecl = Context.Named.Declaration
(****************************************) (* General functions *) (****************************************)
type debug = Debug | Info | Off
exception Bound
let rec head_bound sigma t = match EConstr.kind sigma t with
| Prod (_, _, b) -> head_bound sigma b
| LetIn (_, _, _, b) -> head_bound sigma b
| App (c, _) -> head_bound sigma c
| Case (_, _, _, _, _, c, _) -> head_bound sigma c
| Ind (ind, _) -> GlobRef.IndRef ind
| Const (c, _) -> GlobRef.ConstRef c
| Construct (c, _) -> GlobRef.ConstructRef c
| Var id -> GlobRef.VarRef id
| Proj (p, _, _) -> GlobRef.ConstRef (Projection.constant p)
| Cast (c, _, _) -> head_bound sigma c
| Evar _ | Rel _ | Meta _ | Sort _ | Fix _ | Lambda _
| CoFix _ | Int _ | Float _ | String _ | Array _ -> raise Bound
let head_constr sigma c = try head_bound sigma c with Bound -> user_err (Pp.str "Head identifier must be a constant, section variable, \
(co)inductive type, (co)inductive type constructor, or projection.")
let decompose_app_bound sigma t = let t = strip_outer_cast sigma t in let _,ccl = decompose_prod_decls sigma t in let hd,args = decompose_app sigma ccl in letopen GlobRef in match EConstr.kind sigma hd with
| Const (c,u) -> ConstRef c, args
| Ind (i,u) -> IndRef i, args
| Construct (c,u) -> ConstructRef c, args
| Var id -> VarRef id, args
| Proj (p, _, c) -> ConstRef (Projection.constant p), Array.cons c args
| _ -> raise Bound
(** Compute the set of section variables that remain in the named context. Startsfromthetoptothebottomofthecontext,stopsatthefirst
different declaration between the named hyps and the section context. *) let secvars_of_hyps hyps = let secctx = Global.named_context () in letopen Context.Named.Declaration in let pred, all = List.fold_left (fun (pred,all) decl -> trylet _ = Context.Named.lookup (get_id decl) hyps in (* Approximation, it might be an hypothesis reintroduced with same name and unconvertible types, wemustallowitcurrently,ascomparingthedeclarationsforsyntacticequalityistoo
strong a check (e.g. an unfold in a section variable would make it unusable). *)
(Id.Pred.add (get_id decl) pred, all) with Not_found -> (pred, false))
(Id.Pred.empty,true) secctx in ifallthen Id.Pred.full (* If the whole section context is available *) else pred
(************************************************************************) (* The Type of Constructions Autotactic Hints *) (************************************************************************)
type'a hint_ast =
| Res_pf of'a (* Hint Apply *)
| ERes_pf of'a (* Hint EApply *)
| Give_exact of'a
| Res_pf_THEN_trivial_fail of'a (* Hint Immediate *)
| Unfold_nth of Evaluable.t (* Hint Unfold *)
| Extern of Pattern.constr_pattern option * Gentactic.glob_generic_tactic (* Hint Extern *)
type'a hints_path_atom_gen =
| PathHints of'a list (* For forward hints, their names is the list of projections *)
| PathAny
type hints_path_atom = GlobRef.t hints_path_atom_gen
type pre_hints_path = Libnames.qualid hints_path_gen type hints_path = GlobRef.t hints_path_gen
type hint_term =
| IsGlobRef of GlobRef.t
| IsConstr of constr * UnivGen.sort_context_set option(* None if monomorphic *)
type'a with_uid = {
obj : 'a;
uid : KerName.t;
}
type raw_hint = {
rhint_term : constr;
rhint_type : types;
rhint_uctx : UnivGen.sort_context_set option;
rhint_arty : int; (* Number of goals generated by the intended tactic *)
}
type hint = {
hint_term : constr;
hint_type : types;
hint_uctx : UnivGen.sort_context_set option; (* None if monomorphic *)
hint_clnv : Clenv.clausenv;
hint_arty : int; (* Number of goals generated by the intended tactic *)
}
type hint_pattern =
| DefaultPattern
| ConstrPattern of constr_pattern
| SyntacticPattern of constr_pattern
type ('a,'db) with_metadata =
{ pri : int (** A number lower is higher priority *)
; pat : hint_pattern option (** A pattern for the concl of the Goal *)
; name : GlobRef.t option (** A potential name to refer to the hint *)
; db : 'db (** The database from which the hint comes *)
; secvars : Id.Pred.t (** The set of section variables the hint depends on *)
; code : 'a (** the tactic to apply when the concl matches pat *)
}
type hint_db_name = string
(* db = None for local database (ie built from goal hyps) *) type full_hint = (hint hint_ast with_uid, hint_db_name option) with_metadata
type hint_mode =
| ModeInput (* No evars *)
| ModeNoHeadEvar (* No evar at the head *)
| ModeOutput (* Anything *)
module Modes = struct type t = hint_mode array list GlobRef.Map.t let empty = GlobRef.Map.empty let union m1 m2 = GlobRef.Map.union (fun _ m1 m2 -> Some (m1@m2)) m1 m2 end
let fresh_key = let id = Summary.ref ~name:"HINT-COUNTER"0in fun () -> let cur = incr id; !id in let lbl = Id.of_string ("_" ^ string_of_int cur) in let kn = Lib.make_kn lbl in let (mp, _) = KerName.repr kn in (* We embed the full path of the kernel name in the label so that theidentifiershouldbeunique.Thisensuresthatincluding
two modules together won't confuse the corresponding labels. *) let lbl = Id.of_string_soft (Printf.sprintf "%s#%i"
(ModPath.to_string mp) cur) in
KerName.make mp (Label.of_id lbl)
let pri_order_int (id1, {pri=pri1}) (id2, {pri=pri2}) = let d = Int.compare pri1 pri2 in if Int.equal d 0then Int.compare id2 id1 else d
let get_default_pattern (h : hint hint_ast) = match h with
| Give_exact h -> h.hint_type
| Res_pf h | ERes_pf h | Res_pf_THEN_trivial_fail h ->
Clenv.clenv_type h.hint_clnv
| Unfold_nth _ | Extern _ -> (* These hints cannot contain DefaultPattern *)
assert false
(* Nov 98 -- Papageno *) (* Les Hints sont ré-organisés en plusieurs databases.
Unesearch_entryestuntripletcomprenant: -lalistedestactiquesquin'ontpasdepatternassocié -lalistedestactiquesquiontunpattern -undiscriminationnetborné(Btermdn.t)constituédetousles
patterns de la seconde liste de tactiques *)
type stored_data = int * full_hint (* First component is the index of insertion in the table, to keep most recent first semantics. *)
module Stored = struct type t = stored_data let compare = pri_order_int end
module StoredSet = Set.Make(Stored)
let merge_set s l = List.merge Stored.compare (StoredSet.elements s) l
module Bounded_net : sig type t val empty : TransparentState.t option -> t val build : TransparentState.t option -> StoredSet.t -> t val add : t -> hint_pattern -> stored_data -> t val lookup : Environ.env -> Evd.evar_map -> t -> EConstr.constr -> stored_data list end = struct
module Bnet = Btermdn.Make(Stored)
type diff = hint_pattern * stored_data
type data =
| Bnet of (TransparentState.t option * Bnet.t)
| Diff of diff * data ref
| Build of TransparentState.t option * StoredSet.t
type t = data ref
let empty st = ref (Bnet (st, Bnet.empty))
let add net p v = ref (Diff ((p, v), net))
let build st data = ref (Build (st, data))
let add0 env sigma st p v dn = let p = match p with
| ConstrPattern p -> Bnet.pattern env st p
| SyntacticPattern p -> Bnet.pattern_syntactic env p
| DefaultPattern -> let c = get_default_pattern (snd v).code.obj in
Bnet.constr_pattern env sigma st c in
Bnet.add dn p v
let rec force env sigma net = match !net with
| Bnet dn -> dn
| Diff ((p, v), rem) -> let st, dn = force env sigma rem in let dn = add0 env sigma st p v dn in let () = net := (Bnet (st, dn)) in
st, dn
| Build (st, data) -> let fold v dn = add0 env sigma st (Option.get (snd v).pat) v dn in let ans = StoredSet.fold fold data Bnet.empty in let () = net := Bnet (st, ans) in
st, ans
let lookup env sigma net p = let st, dn = force env sigma net in
Bnet.lookup env sigma st dn p end
module StoredData : sig type t val empty : t val mem : KerName.t -> t -> bool val add : stored_data -> t -> t val remove : GlobRef.Set.t -> t -> t val elements : t -> StoredSet.t end = struct
type t = {
data : StoredSet.t; set : KNset.t;
}
let empty = { data = StoredSet.empty; set = KNset.empty }
let mem kn sd = KNset.mem kn sd.set
let add t sd = {
data = StoredSet.add t sd.data; set = KNset.add (snd t).code.uid sd.set;
}
let remove grs sd = let fold ((_, h) as v) (accu, ans) = let keep = match h.name with
| Some gr -> not (GlobRef.Set.mem gr grs)
| None -> true in if keep then (accu, StoredSet.add v ans) else (KNset.remove h.code.uid accu, ans) in letset, data = StoredSet.fold fold sd.data (sd.set, StoredSet.empty) in ifset == sd.setthen sd else { data = data; set }
let empty_se st = {
sentry_nopat = StoredData.empty;
sentry_pat = StoredData.empty;
sentry_bnet = Bounded_net.empty st;
sentry_mode = [];
}
let add_tac pat t se = match pat with
| None -> let uid = (snd t).code.uid in if StoredData.mem uid se.sentry_nopat then se else { se with sentry_nopat = StoredData.add t se.sentry_nopat }
| Some pat -> let uid = (snd t).code.uid in if StoredData.mem uid se.sentry_pat then se else { se with
sentry_pat = StoredData.add t se.sentry_pat;
sentry_bnet = Bounded_net.add se.sentry_bnet pat t; }
let rebuild_dn st se = let dn' = Bounded_net.build st (StoredData.elements se.sentry_pat) in
{ se with sentry_bnet = dn' }
let lookup_tacs env sigma concl se = let l' = Bounded_net.lookup env sigma se.sentry_bnet concl in let sl' = List.stable_sort pri_order_int l'in
merge_set (StoredData.elements se.sentry_nopat) sl'
let merge_context_set_opt sigma ctx = match ctx with
| None -> sigma
| Some ctx -> Evd.merge_sort_context_set Evd.univ_flexible sigma ctx
let instantiate_hint env sigma p = let mk_clenv { rhint_term = c; rhint_type = cty; rhint_uctx = ctx; rhint_arty = ar } = let sigma = merge_context_set_opt sigma ctx in let cl = Clenv.mk_clenv_from env sigma (c,cty) in let cl = Clenv.clenv_strip_proj_params cl in
{ hint_term = c; hint_type = cty; hint_uctx = ctx; hint_clnv = cl; hint_arty = ar } in let code = match p.code.obj with
| Res_pf c -> Res_pf (mk_clenv c)
| ERes_pf c -> ERes_pf (mk_clenv c)
| Res_pf_THEN_trivial_fail c ->
Res_pf_THEN_trivial_fail (mk_clenv c)
| Give_exact c -> Give_exact (mk_clenv c)
| (Unfold_nth _ | Extern _) as h -> h in
{ p with code = { p.code with obj = code } }
let path_matches env hp hints = let rec aux hp hints k = match hp, hints with
| PathAtom _, [] -> false
| PathAtom PathAny, (_ :: hints') -> k hints'
| PathAtom p, (h :: hints') -> if hints_path_atom_eq env p h then k hints' else false
| PathStar hp', hints ->
k hints || aux hp' hints (fun hints' -> aux hp hints' k)
| PathSeq (hp, hp'), hints ->
aux hp hints (fun hints' -> aux hp' hints' k)
| PathOr (hp, hp'), hints ->
aux hp hints k || aux hp' hints k
| PathEmpty, _ -> false
| PathEpsilon, hints -> k hints in aux hp hints (fun hints' -> true)
let rec matches_epsilon = function
| PathAtom _ -> false
| PathStar _ -> true
| PathSeq (p, p') -> matches_epsilon p && matches_epsilon p'
| PathOr (p, p') -> matches_epsilon p || matches_epsilon p'
| PathEmpty -> false
| PathEpsilon -> true
let path_matches_epsilon = matches_epsilon
let rec is_empty = function
| PathAtom _ -> false
| PathStar _ -> false
| PathSeq (p, p') -> is_empty p || is_empty p'
| PathOr (p, p') -> matches_epsilon p && matches_epsilon p'
| PathEmpty -> true
| PathEpsilon -> false
let path_seq p p' = match p, p' with
| PathEpsilon, p' -> p'
| p, PathEpsilon -> p
| p, p' -> PathSeq (p, p')
let rec path_derivate env hp hint = let rec derivate_atoms env hints hints' = match hints, hints' with
| gr :: grs, gr' :: grs' when QGlobRef.equal env gr gr' -> derivate_atoms env grs grs'
| [], [] -> PathEpsilon
| [], hints -> PathEmpty
| grs, [] -> PathAtom (PathHints grs)
| _, _ -> PathEmpty in match hp with
| PathAtom PathAny -> PathEpsilon
| PathAtom (PathHints grs) ->
(match grs, hint with
| h :: _, PathAny -> PathEmpty
| hints, PathHints hints' -> derivate_atoms env hints hints'
| _, _ -> assert false)
| PathStar p -> if path_matches env p [hint] then hp else PathEpsilon
| PathSeq (hp, hp') -> let hpder = path_derivate env hp hint in if matches_epsilon hp then
PathOr (path_seq hpder hp', path_derivate env hp' hint) elseif is_empty hpder then PathEmpty else path_seq hpder hp'
| PathOr (hp, hp') ->
PathOr (path_derivate env hp hint, path_derivate env hp' hint)
| PathEmpty -> PathEmpty
| PathEpsilon -> PathEmpty
let rec normalize_path env h = match h with
| PathStar PathEpsilon -> PathEpsilon
| PathOr (p, q) ->
(match normalize_path env p with
| PathEmpty -> normalize_path env q
| p' -> match normalize_path env q with
| PathEmpty -> p'
| q' -> if hints_path_eq env p' q' then p'else PathOr (p', q'))
| PathSeq (p, q) ->
(match normalize_path env p with
| PathEmpty -> PathEmpty
| PathEpsilon -> normalize_path env q
| p' -> match normalize_path env q with
| PathEmpty -> PathEmpty
| PathEpsilon -> p'
| q' -> PathSeq (p', q'))
| _ -> h
let path_derivate env hp hint = let hint = match hint with
| None -> PathAny
| Some gr -> PathHints [gr] in
normalize_path env (path_derivate env hp hint)
let pp_hints_path_atom prg a = match a with
| PathAny -> str"_"
| PathHints grs -> pr_sequence prg grs
let pp_hints_path_gen prg = let rec aux = function
| PathAtom pa -> pp_hints_path_atom prg pa
| PathStar (PathAtom PathAny) -> str"_*"
| PathStar p -> str "(" ++ aux p ++ str")*"
| PathSeq (p, p') -> aux p ++ spc () ++ aux p'
| PathOr (p, p') ->
str "(" ++ aux p ++ spc () ++ str"|" ++ cut () ++ spc () ++
aux p' ++ str ")"
| PathEmpty -> str"emp"
| PathEpsilon -> str"eps" in aux
let pp_hints_path = pp_hints_path_gen pr_global
let glob_hints_path_atom p = match p with
| PathHints g -> PathHints (List.map Nametab.global g)
| PathAny -> PathAny
let glob_hints_path = let rec aux = function
| PathAtom pa -> PathAtom (glob_hints_path_atom pa)
| PathStar p -> PathStar (aux p)
| PathSeq (p, p') -> PathSeq (aux p, aux p')
| PathOr (p, p') -> PathOr (aux p, aux p')
| PathEmpty -> PathEmpty
| PathEpsilon -> PathEpsilon in aux
let subst_path_atom subst p = match p with
| PathAny -> p
| PathHints grs -> let gr' gr = fst (subst_global subst gr) in let grs' = List.Smart.map gr' grs in if grs' == grs then p else PathHints grs'
let rec subst_hints_path subst hp = match hp with
| PathAtom p -> let p' = subst_path_atom subst p in if p' == p then hp else PathAtom p'
| PathStar p -> let p' = subst_hints_path subst p in if p' == p then hp else PathStar p'
| PathSeq (p, q) -> let p' = subst_hints_path subst p in let q' = subst_hints_path subst q in if p' == p && q' == q then hp else PathSeq (p', q')
| PathOr (p, q) -> let p' = subst_hints_path subst p in let q' = subst_hints_path subst q in if p' == p && q' == q then hp else PathOr (p', q')
| _ -> hp
type mode_match =
| NoMode
| WithMode of hint_mode array
type'a with_mode =
| ModeMatch of mode_match * 'a
| ModeMismatch
module Hint_db : sig type t val empty : ?name:hint_db_name -> TransparentState.t -> bool -> t val map_none : secvars:Id.Pred.t -> t -> full_hint list val map_all : secvars:Id.Pred.t -> GlobRef.t -> t -> full_hint list val map_eauto : Environ.env -> evar_map -> secvars:Id.Pred.t ->
(GlobRef.t * constr array) -> constr -> t -> full_hint list with_mode val map_auto : Environ.env -> evar_map -> secvars:Id.Pred.t ->
(GlobRef.t * constr array) -> constr -> t -> full_hint list val add_list : env -> evar_map -> hint_entry list -> t -> t val remove_one : Environ.env -> GlobRef.t -> t -> t val remove_list : Environ.env -> GlobRef.t list -> t -> t val iter : (GlobRef.t option -> hint_mode array list -> full_hint list -> unit) -> t -> unit val use_dn : t -> bool val transparent_state : t -> TransparentState.t val set_transparent_state : t -> TransparentState.t -> t val add_cut : Environ.env -> hints_path -> t -> t val add_mode : GlobRef.t -> hint_mode array -> t -> t val cut : t -> hints_path val unfolds : t -> Id.Set.t * Cset.t * PRset.t val add_modes : hint_mode array list GlobRef.Map.t -> t -> t val modes : t -> hint_mode array list GlobRef.Map.t val find_mode : env -> GlobRef.t -> t -> hint_mode array list val fold : (GlobRef.t option -> hint_mode array list -> full_hint list -> 'a -> 'a) ->
t -> 'a -> 'a end = struct
type t = {
hintdb_state : TransparentState.t;
hintdb_cut : hints_path;
hintdb_unfolds : Id.Set.t * Cset.t * PRset.t;
hintdb_max_id : int;
use_dn : bool;
hintdb_map : search_entry GlobRef.Map.t; (* A list of unindexed entries with no associated pattern. *)
hintdb_nopat : stored_data list;
hintdb_name : stringoption;
}
let next_hint_id db = let h = db.hintdb_max_id in
{ db with hintdb_max_id = succ db.hintdb_max_id }, h
let dn_ts db = if db.use_dn then (Some db.hintdb_state) else None
letfind key db = try GlobRef.Map.find key db.hintdb_map with Not_found -> empty_se (dn_ts db)
let realize_tac secvars (id,tac) = if Id.Pred.subset tac.secvars secvars then Some tac else (* Warn about no longer typable hint? *)
None
let has_no_head_evar sigma c = let rec hrec c = match EConstr.kind sigma c with
| Evar (evk,_) -> false
| App (c,_) -> hrec c
| Cast (c,_,_) -> hrec c
| _ -> true in
hrec c
let match_mode sigma m arg = match m with
| ModeInput -> not (occur_existential sigma arg)
| ModeNoHeadEvar -> has_no_head_evar sigma arg
| ModeOutput -> true
let matches_mode sigma args mode = if Array.length mode == Array.length args &&
Array.for_all2 (match_mode sigma) mode args then Some mode else None
let matches_modes sigma args modes = ifList.is_empty modes then Some NoMode else Option.map (fun x -> WithMode x) (List.find_map (matches_mode sigma args) modes)
let merge_entry secvars db nopat pat = let h = List.sort pri_order_int db.hintdb_nopat in let h = List.merge pri_order_int h nopat in let h = List.merge pri_order_int h pat in List.map_filter (realize_tac secvars) h
let map_none ~secvars db =
merge_entry secvars db [] []
let map_all ~secvars k db = let se = find k db in let h = List.sort pri_order_int db.hintdb_nopat in let h = merge_set (StoredData.elements se.sentry_nopat) h in let h = merge_set (StoredData.elements se.sentry_pat) h in List.map_filter (realize_tac secvars) h
(* Precondition: concl has no existentials *) let map_auto env sigma ~secvars (k,args) concl db = let se = find k db in let pat = lookup_tacs env sigma concl se in
merge_entry secvars db [] pat
(* [c] contains an existential *) let map_eauto env sigma ~secvars (k,args) concl db = let se = find k db in match matches_modes sigma args se.sentry_mode with
| Some m -> let pat = lookup_tacs env sigma concl se in
ModeMatch (m, merge_entry secvars db [] pat)
| None -> ModeMismatch
let is_exact = function
| Give_exact _ -> true
| _ -> false
let addkv gr id v db = let idv = id, { v with db = db.hintdb_name } in match gr with
| None -> let is_present (_, v') = KerName.equal v.code.uid v'.code.uid in ifnot (List.exists is_present db.hintdb_nopat) then (* FIXME *)
{ db with hintdb_nopat = idv :: db.hintdb_nopat } else db
| Some gr -> let pat = ifnot db.use_dn && is_exact v.code.obj then None else v.pat in let oval = find gr db in
{ db with hintdb_map = GlobRef.Map.add gr (add_tac pat idv oval) db.hintdb_map }
let rebuild_db st' db = let db' =
{ db with hintdb_map = GlobRef.Map.map (rebuild_dn (Some st')) db.hintdb_map;
hintdb_state = st'; hintdb_nopat = [] } in List.fold_left (fun db (id, v) -> addkv None id v db) db' db.hintdb_nopat
let add_one env sigma (k, v) db = let v = instantiate_hint env sigma v in let st',db,rebuild = match v.code.obj with
| Unfold_nth egr -> let addunf ts (ids, csts, prjs) = letopen TransparentState in match egr with
| Evaluable.EvalVarRef id ->
{ ts with tr_var = Id.Pred.add id ts.tr_var }, (Id.Set.add id ids, csts, prjs)
| Evaluable.EvalConstRef cst ->
{ ts with tr_cst = Cpred.add cst ts.tr_cst }, (ids, Cset.add cst csts, prjs)
| Evaluable.EvalProjectionRef p ->
{ ts with tr_prj = PRpred.add p ts.tr_prj }, (ids, csts, PRset.add p prjs) in let state, unfs = addunf db.hintdb_state db.hintdb_unfolds in
state, { db with hintdb_unfolds = unfs }, true
| _ -> db.hintdb_state, db, false in let db = if db.use_dn && rebuild then rebuild_db st' db else db in let db, id = next_hint_id db in
addkv k id v db
let add_list env sigma l db = List.fold_left (fun db k -> add_one env sigma k db) db l
let remove st grs se = let grs = List.fold_left (fun accu gr -> GlobRef.Set.add gr accu) GlobRef.Set.empty grs in let nopat = StoredData.remove grs se.sentry_nopat in let pat = StoredData.remove grs se.sentry_pat in if pat == se.sentry_pat && nopat == se.sentry_nopat then se else let se = { se with sentry_nopat = nopat; sentry_pat = pat } in
rebuild_dn st se
let remove_list env grs db = let eq gr1 gr2 = QGlobRef.equal env gr1 gr2 in letfilter (_, h) = match h.name with Some gr -> not (List.mem_f eq gr grs) | None -> truein let hintmap = GlobRef.Map.map (fun e -> remove (dn_ts db) grs e) db.hintdb_map in let hintnopat = List.filterfilter db.hintdb_nopat in
{ db with hintdb_map = hintmap; hintdb_nopat = hintnopat }
let remove_one env gr db = remove_list env [gr] db
let get_entry se = let h = merge_set (StoredData.elements se.sentry_nopat) (merge_set (StoredData.elements se.sentry_pat) []) in List.map snd h
let iter f db = let iter_se k se = f (Some k) se.sentry_mode (get_entry se) in
f None [] (List.map snd db.hintdb_nopat);
GlobRef.Map.iter iter_se db.hintdb_map
let fold f db accu = let accu = f None [] (List.map snd db.hintdb_nopat) accu in
GlobRef.Map.fold (fun k se -> f (Some k) se.sentry_mode (get_entry se)) db.hintdb_map accu
let transparent_state db = db.hintdb_state
let set_transparent_state db st = if db.use_dn then rebuild_db st db else { db with hintdb_state = st }
let add_cut env path db =
{ db with hintdb_cut = normalize_path env (PathOr (db.hintdb_cut, path)) }
let add_mode gr m db = let se = find gr db in let se = { se with sentry_mode = m :: List.remove (Array.equal hint_mode_eq) m se.sentry_mode } in
{ db with hintdb_map = GlobRef.Map.add gr se db.hintdb_map }
let cut db = db.hintdb_cut
let unfolds db = db.hintdb_unfolds
let add_modes modes db = let f gr e me =
Some { e with sentry_mode = me.sentry_mode @ e.sentry_mode } in let mode_entries = GlobRef.Map.map (fun m -> { (empty_se (dn_ts db)) with sentry_mode = m }) modes in
{ db with hintdb_map = GlobRef.Map.union f db.hintdb_map mode_entries }
let modes db = GlobRef.Map.map (fun se -> se.sentry_mode) db.hintdb_map
let find_mode _env gr db = (GlobRef.Map.find gr db.hintdb_map).sentry_mode
let use_dn db = db.use_dn
end
module Hintdbmap = String.Map
type hint_db = Hint_db.t
let searchtable = Summary.ref ~name:"searchtable" Hintdbmap.empty let statustable = Summary.ref ~name:"statustable" KNmap.empty
let searchtable_map name =
Hintdbmap.find name !searchtable let searchtable_add (name,db) =
searchtable := Hintdbmap.add name db !searchtable let current_db_names () = Hintdbmap.domain !searchtable let current_db () = Hintdbmap.bindings !searchtable
let current_pure_db () = List.map snd (current_db ())
let error_no_such_hint_database x =
user_err (str "No such Hint database: " ++ str x ++ str ".")
(**************************************************************************) (* Auxiliary functions to prepare AUTOHINT objects *) (**************************************************************************)
(* adding and removing tactics in the search table *)
let with_uid c = { obj = c; uid = fresh_key () }
let secvars_of_idset s =
Id.Set.fold (fun id p -> if is_section_variable (Global.env ()) id then
Id.Pred.add id p else p) s Id.Pred.empty
let secvars_of_constr env sigma c =
secvars_of_idset (Termops.global_vars_set env sigma c)
let secvars_of_global env gr =
secvars_of_idset (vars_of_global env gr)
let make_exact_entry env sigma info ?name (c, cty, ctx) = let secvars = secvars_of_constr env sigma c in let cty = strip_outer_cast sigma cty in match EConstr.kind sigma cty with
| Prod _ -> failwith "make_exact_entry"
| _ -> let hd = try head_bound sigma cty with Bound -> failwith "make_exact_entry" in let pri = match info.hint_priority with None -> 0 | Some p -> p in let pat = match info.hint_pattern with
| Some pat -> ConstrPattern (snd pat)
| None -> DefaultPattern in let h = { rhint_term = c; rhint_type = cty; rhint_uctx = ctx; rhint_arty = 0 } in
(Some hd,
{ pri; pat = Some pat; name;
db = (); secvars;
code = with_uid (Give_exact h); })
let name_of_hint = function
| IsGlobRef gr -> Some gr
| IsConstr _ -> None
let make_apply_entry env sigma hnf info ?name (c, cty, ctx) = let cty = if hnf then hnf_constr0 env sigma cty else cty in match EConstr.kind sigma cty with
| Prod _ -> let cty = if hnf then Reductionops.nf_betaiota env sigma cty else cty in let sigma' = merge_context_set_opt sigma ctx in let ce = Clenv.mk_clenv_from env sigma' (c,cty) in let c' = Clenv.clenv_type (* ~reduce:false *) ce in let hd = try head_bound (Clenv.clenv_evd ce) c' with Bound -> failwith "make_apply_entry"in let miss, hyps = Clenv.clenv_missing ce in let nmiss = List.length miss in let secvars = secvars_of_constr env sigma c in let pri = match info.hint_priority with None -> hyps + nmiss | Some p -> p in let pat = match info.hint_pattern with
| Some p -> ConstrPattern (snd p)
| None -> DefaultPattern in let h = { rhint_term = c; rhint_type = cty; rhint_uctx = ctx; rhint_arty = hyps; } in if Int.equal nmiss 0then
(Some hd,
{ pri; pat = Some pat; name;
db = ();
secvars;
code = with_uid (Res_pf h); }) else
(Some hd,
{ pri; pat = Some pat; name;
db = (); secvars;
code = with_uid (ERes_pf h); })
| _ -> failwith "make_apply_entry"
(* flags is (e,h,v) with e=true if eapply and h=true if hnf and v=true if verbose cisaconstr
cty is the type of constr *)
let fresh_global_or_constr env sigma cr = match cr with
| IsGlobRef gr -> let (c, ctx) = UnivGen.fresh_global_instance env gr in let ctx = if Environ.is_polymorphic env gr then Some ctx else None in
(EConstr.of_constr c, ctx)
| IsConstr (c, ctx) -> (c, ctx)
let make_resolves env sigma (eapply, hnf) info ~check cr = let name = name_of_hint cr in let c, ctx = fresh_global_or_constr env sigma cr in let cty = Retyping.get_type_of env sigma c in let try_apply f = try let (_, hint) as ans = f (c, cty, ctx) in match hint.code.obj with
| ERes_pf _ -> ifnot eapply then None else Some ans
| _ -> Some ans with Failure _ -> None in let ents = List.map_filter try_apply
[make_exact_entry env sigma info ?name;
make_apply_entry env sigma hnf info ?name] in if check && List.is_empty ents then
user_err
(pr_leconstr_env env sigma c ++ spc() ++
(if eapply then str"cannot be used as a hint." else str "can be used as a hint only for eauto."));
ents
(* used to add an hypothesis to the local hint database *) let make_resolve_hyp env sigma hname = let decl = EConstr.lookup_named hname env in let c = mkVar hname in try
[make_apply_entry env sigma true empty_hint_info
~name:(GlobRef.VarRef hname)
(c, NamedDecl.get_type decl, None)] with
| Failure _ -> []
| e when noncritical e -> anomaly (Pp.str "make_resolve_hyp.")
(* REM : in most cases hintname = id *)
let make_unfold eref = let g = global_of_evaluable_reference eref in
(Some g,
{ pri = 4;
pat = None;
name = Some g;
db = ();
secvars = secvars_of_global (Global.env ()) g;
code = with_uid (Unfold_nth eref) })
let make_extern pri pat tacast = let hdconstr = match pat with
| None -> None
| Some c -> try Some (head_pattern_bound c) with BoundPattern ->
user_err (Pp.str "Head pattern or sub-pattern must be a global constant, a section variable, \
an if, case, orlet expression, an application, or a projection.") in
(hdconstr,
{ pri = pri;
pat = Option.map (fun p -> SyntacticPattern p) pat;
name = None;
db = ();
secvars = Id.Pred.empty; (* Approximation *)
code = with_uid (Extern (pat, tacast)) })
let make_mode ref m = letopen Term in let ty, _ = Typeops.type_of_global_in_context (Global.env ()) refin let ctx, t = decompose_prod_decls ty in let n = Context.Rel.nhyps ctx in let m' = Array.of_list m in ifnot (n == Array.length m') then
user_err
(pr_global ref ++ str" has " ++ int n ++
str" arguments while the mode declares " ++ int (Array.length m') ++ str ".") else m'
let make_trivial env sigma r = let name = name_of_hint r in let c,ctx = fresh_global_or_constr env sigma r in let sigma = merge_context_set_opt sigma ctx in let t = hnf_constr env sigma (Retyping.get_type_of env sigma c) in let hd = head_constr sigma t in let h = { rhint_term = c; rhint_type = t; rhint_uctx = ctx; rhint_arty = 0 } in
(Some hd,
{ pri=1;
pat = Some DefaultPattern;
name = name;
db = ();
secvars = secvars_of_constr env sigma c;
code= with_uid (Res_pf_THEN_trivial_fail h) })
(**************************************************************************) (* declaration of the AUTOHINT library object *) (**************************************************************************)
(* If the database does not exist, it is created *) (* TODO: should a warning be printed in this case ?? *)
let get_db dbname = try searchtable_map dbname with Not_found -> Hint_db.empty ~name:dbname TransparentState.empty false
let add_hint dbname hintlist = let check (_, h) = let () = if KNmap.mem h.code.uid !statustable then
user_err Pp.(str "Conflicting hint keys. This can happen when including \
twice the same module.") in
statustable := KNmap.add h.code.uid false !statustable in let () = List.iter check hintlist in let db = get_db dbname in let env = Global.env () in let sigma = Evd.from_env env in let db' = Hint_db.add_list env sigma hintlist db in
searchtable_add (dbname,db')
let add_transparency dbname target b = letopen TransparentState in let db = get_db dbname in let st = Hint_db.transparent_state db in let st' = match target with
| HintsVariables -> { st with tr_var = (if b then Id.Pred.full else Id.Pred.empty) }
| HintsConstants -> { st with tr_cst = (if b then Cpred.full else Cpred.empty) }
| HintsProjections -> { st with tr_prj = (if b then PRpred.full else PRpred.empty) }
| HintsReferences grs -> List.fold_left (fun st gr -> match gr with
| Evaluable.EvalConstRef c -> { st with tr_cst = (if b then Cpred.add else Cpred.remove) c st.tr_cst }
| Evaluable.EvalVarRef v -> { st with tr_var = (if b then Id.Pred.add else Id.Pred.remove) v st.tr_var }
| Evaluable.EvalProjectionRef p -> { st with tr_prj = (if b then PRpred.add else PRpred.remove) p st.tr_prj } )
st grs in searchtable_add (dbname, Hint_db.set_transparent_state db st')
let remove_hint dbname grs = let env = Global.env () in let db = get_db dbname in let db' = Hint_db.remove_list env grs db in
searchtable_add (dbname, db')
let add_cut dbname path = let env = Global.env () in let db = get_db dbname in let db' = Hint_db.add_cut env path db in
searchtable_add (dbname, db')
let add_mode dbname l m = let db = get_db dbname in let db' = Hint_db.add_mode l m db in
searchtable_add (dbname, db')
let warn_mismatch_create_hintdb = CWarnings.create ~name:"mismatched-hint-db" ~category:CWarnings.CoreCategories.automation
Pp.(fun {db_name;db_use_dn} ->
str "Hint Db " ++ str db_name ++ str " already exists and " ++
(if db_use_dn then str "is not"else str "is") ++ str " discriminated.")
let cache_db ({db_name=name; db_use_dn=b; db_ts=ts} as o) = match searchtable_map name with
| exception Not_found -> searchtable_add (name, Hint_db.empty ~name ts b)
| db -> (* Explicit DBs start with full TS, implicit DBs start with empty TS ThisshouldprobablybecontrollableinCreateHintDb, otherwisewehavetodoeg"CreateHintDbfoo.HintConstantsOpaque:foo."
and if someone else creates foo and puts some transparency hints they will be overwritten. *) if Hint_db.use_dn db <> b then warn_mismatch_create_hintdb o
let load_db _ x = cache_db x
let classify_db db = if db.db_local then Dispose else Substitute
let create_hint_db l n ts b = let hint = {db_local=l; db_name=n; db_use_dn=b; db_ts=ts} in
Lib.add_leaf (inDB hint)
type hint_action =
| AddTransparency of {
grefs : Evaluable.t hints_transparency_target;
state : bool;
}
| AddHints of hint_entry list
| RemoveHints of GlobRef.t list
| AddCut of hints_path
| AddMode of { gref : GlobRef.t; mode : hint_mode array }
type hint_locality = Libobject.locality = Local | Export | SuperGlobal
let is_trivial_action = function
| AddTransparency { grefs } -> beginmatch grefs with
| HintsVariables | HintsConstants | HintsProjections -> false
| HintsReferences l -> List.is_empty l end
| AddHints l -> List.is_empty l
| RemoveHints l -> List.is_empty l
| AddCut _ | AddMode _ -> false
let rec is_section_path = function
| PathAtom PathAny -> false
| PathAtom (PathHints grs) -> let check c = isVarRef c && Global.is_in_section c in List.exists check grs
| PathStar p -> is_section_path p
| PathSeq (p, q) | PathOr (p, q) -> is_section_path p || is_section_path q
| PathEmpty | PathEpsilon -> false
let superglobal h = match h.hint_local with
| SuperGlobal -> true
| Local | Export -> false
let load_autohint _ h = let name = h.hint_name in let superglobal = superglobal h in match h.hint_action with
| AddTransparency { grefs; state } -> if superglobal then add_transparency name grefs state
| AddHints hints -> if superglobal then add_hint name hints
| RemoveHints hints -> if superglobal then remove_hint name hints
| AddCut paths -> if superglobal then add_cut name paths
| AddMode { gref; mode } -> if superglobal then add_mode name gref mode
let open_autohint h = let superglobal = superglobal h in match h.hint_action with
| AddHints hints -> let () = ifnot superglobal then (* Import-bound hints must be declared when not imported yet *) letfilter (_, h) = not @@ KNmap.mem h.code.uid !statustable in
add_hint h.hint_name (List.filterfilter hints) in let add (_, hint) = statustable := KNmap.add hint.code.uid true !statustable in List.iter add hints
| AddCut paths -> ifnot superglobal then add_cut h.hint_name paths
| AddTransparency { grefs; state } -> ifnot superglobal then add_transparency h.hint_name grefs state
| RemoveHints hints -> ifnot superglobal then remove_hint h.hint_name hints
| AddMode { gref; mode } -> ifnot superglobal then add_mode h.hint_name gref mode
let cache_autohint o =
load_autohint 1 o; open_autohint o
let subst_autohint (subst, obj) = let subst_key gr = let (gr', t) = subst_global subst gr in match t with
| None -> gr'
| Some t ->
(try head_bound Evd.empty (EConstr.of_constr t.UVars.univ_abstracted_value) with Bound -> gr') in let subst_mps subst c = EConstr.of_constr (subst_mps subst (EConstr.Unsafe.to_constr c)) in let subst_aux ({ rhint_term = c; rhint_type = t; rhint_uctx = ctx; rhint_arty = ar } as h) = let c' = subst_mps subst c in let t' = subst_mps subst t in if c==c' && t'==t then h else { rhint_term = c'; rhint_type = t'; rhint_uctx = ctx; rhint_arty = ar } in let subst_hint (k,data as hint) = let k' = Option.Smart.map subst_key k in let env = Global.env () in let sigma = Evd.from_env env in let subst_hint_pattern = function
| DefaultPattern -> DefaultPattern
| ConstrPattern p as p0 -> let p' = subst_pattern env sigma subst p in if p' == p then p0 else ConstrPattern p'
| SyntacticPattern p as p0 -> let p' = subst_pattern env sigma subst p in if p' == p then p0 else SyntacticPattern p' in let pat' = Option.Smart.map subst_hint_pattern data.pat in let code' = match data.code.obj with
| Res_pf h -> let h' = subst_aux h in if h == h' then data.code.obj else Res_pf h'
| ERes_pf h -> let h' = subst_aux h in if h == h' then data.code.obj else ERes_pf h'
| Give_exact h -> let h' = subst_aux h in if h == h' then data.code.obj else Give_exact h'
| Res_pf_THEN_trivial_fail h -> let h' = subst_aux h in if h == h' then data.code.obj else Res_pf_THEN_trivial_fail h'
| Unfold_nth ref -> letref' = subst_evaluable_reference subst ref in ifref==ref' then data.code.obj else Unfold_nth ref'
| Extern (pat, tac) -> let pat' = Option.Smart.map (subst_pattern env sigma subst) pat in let tac' = Gentactic.subst subst tac in if pat==pat' && tac==tac'then data.code.obj else Extern (pat', tac') in let name' = Option.Smart.map (subst_global_reference subst) data.name in let uid' = subst_kn subst data.code.uid in let data' = if data.code.uid == uid' && data.pat == pat' &&
data.name == name' && data.code.obj == code'then data else { data with pat = pat'; name = name'; code = { obj = code'; uid = uid' } } in if k' == k && data' == data then hint else (k',data') in let action = match obj.hint_action with
| AddTransparency { grefs = target; state = b } -> let target' = match target with
| HintsVariables -> target
| HintsConstants -> target
| HintsProjections -> target
| HintsReferences grs -> let grs' = List.Smart.map (subst_evaluable_reference subst) grs in if grs == grs' then target else HintsReferences grs' in if target' == target then obj.hint_action else AddTransparency { grefs = target'; state = b }
| AddHints hints -> let hints' = List.Smart.map subst_hint hints in if hints' == hints then obj.hint_action else AddHints hints'
| RemoveHints grs -> let grs' = List.Smart.map (subst_global_reference subst) grs in if grs == grs' then obj.hint_action else RemoveHints grs'
| AddCut path -> let path' = subst_hints_path subst path in if path' == path then obj.hint_action else AddCut path'
| AddMode { gref = l; mode = m } -> let l' = subst_global_reference subst l in if l' == l then obj.hint_action else AddMode { gref = l'; mode = m } in if action == obj.hint_action then obj else { obj with hint_action = action }
let is_hint_local = function Local -> true | Export | SuperGlobal -> false
let classify_autohint obj = if is_hint_local obj.hint_local || is_trivial_action obj.hint_action then Dispose else Substitute
let discharge_autohint obj = if is_hint_local obj.hint_local then None else let action = match obj.hint_action with
| AddTransparency { grefs; state } -> let grefs = match grefs with
| HintsVariables | HintsConstants | HintsProjections -> grefs
| HintsReferences grs -> letfilter e = match e with
| Evaluable.EvalConstRef c -> Some e
| Evaluable.EvalProjectionRef p -> let p = Global.discharge_proj_repr p in
Some (Evaluable.EvalProjectionRef p)
| Evaluable.EvalVarRef id -> if Global.is_in_section (GlobRef.VarRef id) then None else Some e in let grs = List.filter_map filter grs in
HintsReferences grs in
AddTransparency { grefs; state }
| AddHints _ | RemoveHints _ -> (* not supported yet *)
assert false
| AddCut path -> if is_section_path path then AddHints [] (* dummy *) else obj.hint_action
| AddMode { gref; mode } -> if Global.is_in_section gref then if isVarRef gref then AddHints [] (* dummy *) else let inst = Global.section_instance gref in (* Default mode for discharged parameters is output *) let mode = Array.append (Array.make (Array.length inst) ModeOutput) mode in
AddMode { gref; mode } else obj.hint_action in if is_trivial_action action then None else Some { obj with hint_action = action }
let check_locality locality = let not_local what =
CErrors.user_err
Pp.(str "This command does not support the " ++
str what ++ str " attribute in sections.") in if Lib.sections_are_opened () then match locality with
| Local -> ()
| SuperGlobal -> not_local "global"
| Export -> not_local "export"
let make_hint ~locality name action =
{
hint_local = locality;
hint_name = name;
hint_action = action;
}
let remove_hints ~locality dbnames grs = let () = check_locality locality in let dbnames = ifList.is_empty dbnames then ["core"] else dbnames in List.iter
(fun dbname -> let hint = make_hint ~locality dbname (RemoveHints grs) in
Lib.add_leaf (inAutoHint hint))
dbnames
(**************************************************************************) (* The "Hint" vernacular command *) (**************************************************************************)
let add_resolves env sigma clist ~locality dbnames = List.iter
(fun dbname -> let r = List.flatten (List.map (fun (pri, hnf, gr) ->
make_resolves env sigma (true, hnf) pri ~check:true gr) clist) in let check (_, hint) = match hint.code.obj with
| ERes_pf { rhint_term = c; rhint_type = cty; rhint_uctx = ctx } -> let sigma' = merge_context_set_opt sigma ctx in let ce = Clenv.mk_clenv_from env sigma' (c,cty) in let miss, _ = Clenv.clenv_missing ce in let nmiss = List.length miss in let variables = str (CString.plural nmiss "variable") in
Feedback.msg_info (
strbrk "The hint " ++
pr_leconstr_env env sigma' c ++
strbrk " will only be used by eauto, because applying " ++
pr_leconstr_env env sigma' c ++
strbrk " would leave " ++ variables ++ Pp.spc () ++
Pp.prlist_with_sep Pp.pr_comma Name.print miss ++
strbrk " as unresolved existential " ++ variables ++ str "."
)
| _ -> () in let () = ifnot !Flags.quiet thenList.iter check r in let hint = make_hint ~locality dbname (AddHints r) in
Lib.add_leaf (inAutoHint hint))
dbnames
let add_unfolds l ~locality dbnames = List.iter
(fun dbname -> let hint = make_hint ~locality dbname (AddHints (List.map make_unfold l)) in
Lib.add_leaf (inAutoHint hint))
dbnames
let add_cuts l ~locality dbnames = List.iter
(fun dbname -> let hint = make_hint ~locality dbname (AddCut l) in
Lib.add_leaf (inAutoHint hint))
dbnames
let add_mode l m ~locality dbnames = List.iter
(fun dbname -> let m' = make_mode l m in let hint = make_hint ~locality dbname (AddMode { gref = l; mode = m' }) in
Lib.add_leaf (inAutoHint hint))
dbnames
let add_transparency l b ~locality dbnames = List.iter
(fun dbname -> let hint = make_hint ~locality dbname (AddTransparency { grefs = l; state = b }) in
Lib.add_leaf (inAutoHint hint))
dbnames
let add_extern info tacast ~locality dbname = let pat = match info.hint_pattern with
| None -> None
| Some (_, pat) -> Some pat in let hint = make_hint ~locality dbname
(AddHints [make_extern (Option.get info.hint_priority) pat tacast]) in
Lib.add_leaf (inAutoHint hint)
let add_externs info tacast ~locality dbnames = List.iter (add_extern info tacast ~locality) dbnames
let add_trivials env sigma l ~locality dbnames = List.iter
(fun dbname -> let l = List.map (fun c -> make_trivial env sigma c) l in let hint = make_hint ~locality dbname (AddHints l) in
Lib.add_leaf (inAutoHint hint))
dbnames
type hnf = bool
type nonrec hint_info = hint_info
type hints_entry =
| HintsResolveEntry of (hint_info * hnf * hint_term) list
| HintsImmediateEntry of hint_term list
| HintsCutEntry of hints_path
| HintsUnfoldEntry of Evaluable.t list
| HintsTransparencyEntry of Evaluable.t hints_transparency_target * bool
| HintsModeEntry of GlobRef.t * hint_mode list
| HintsExternEntry of hint_info * Gentactic.glob_generic_tactic
let default_prepare_hint_ident = Id.of_string "H"
exception Found of constr * types
let prepare_hint env init (sigma,c) = let sigma = Typeclasses.resolve_typeclasses ~fail:false env sigma in (* We re-abstract over uninstantiated evars and universes. Itisactuallyabitstupidtogeneralizeoverevarssincethefirst
thing make_resolves will do is to re-instantiate the products *) let c = Evarutil.nf_evar sigma c in let c = drop_extra_implicit_args sigma c in let vars = ref (collect_vars sigma c) in let subst = ref [] in let rec find_next_evar c = match EConstr.kind sigma c with
| Evar (evk,args as ev) -> (* We skip the test whether args is the identity or not *) let t = Evarutil.nf_evar sigma (existential_type sigma ev) in let t = List.fold_right (fun (e,id) c -> replace_term sigma e id c) !subst t in ifnot (closed0 sigma c) then
user_err Pp.(str "Hints with holes dependent on a bound variable not supported."); if occur_existential sigma t then (* Not clever enough to construct dependency graph of evars *)
user_err Pp.(str "Not clever enough to deal with evars dependent in other evars."); raise (Found (c,t))
| _ -> EConstr.iter sigma find_next_evar c in let rec iter c = try find_next_evar c; c with Found (evar,t) -> let id = next_ident_away_from default_prepare_hint_ident (fun id -> Id.Set.mem id !vars) in
vars := Id.Set.add id !vars;
subst := (evar,mkVar id)::!subst;
mkNamedLambda sigma (make_annot id ERelevance.relevant) t (iter (replace_term sigma evar (mkVar id) c)) in let c' = iter c in let diff = UnivGen.diff_sort_context (Evd.sort_context_set sigma) (Evd.sort_context_set init) in
(c', diff)
let warn_non_local_section_hint =
CWarnings.create ~name:"non-local-section-hint" ~category:CWarnings.CoreCategories.automation
(fun () -> strbrk "This hint is not local but depends on a section variable. It will disappear when the section is closed.")
let is_notlocal = function
| Local -> false
| Export | SuperGlobal -> true
let add_hints ~locality dbnames h = let () = match h with
| HintsResolveEntry _ | HintsImmediateEntry _ | HintsUnfoldEntry _ | HintsExternEntry _ ->
check_locality locality
| HintsTransparencyEntry ((HintsVariables | HintsConstants | HintsProjections), _) -> ()
| HintsTransparencyEntry (HintsReferences grs, _) -> let iter gr = let gr = global_of_evaluable_reference gr in if is_notlocal locality && isVarRef gr && Global.is_in_section gr then warn_non_local_section_hint () in List.iter iter grs
| HintsCutEntry p -> if is_notlocal locality && is_section_path p then warn_non_local_section_hint ()
| HintsModeEntry (gr, _) -> if is_notlocal locality && isVarRef gr && Global.is_in_section gr then warn_non_local_section_hint () in ifString.List.mem "nocore" dbnames then
user_err Pp.(str "The hint database \"nocore\" is meant to stay empty.");
assert (not (List.is_empty dbnames)); let env = Global.env() in let sigma = Evd.from_env env in match h with
| HintsResolveEntry lhints -> add_resolves env sigma lhints ~locality dbnames
| HintsImmediateEntry lhints -> add_trivials env sigma lhints ~locality dbnames
| HintsCutEntry lhints -> add_cuts lhints ~locality dbnames
| HintsModeEntry (l,m) -> add_mode l m ~locality dbnames
| HintsUnfoldEntry lhints -> add_unfolds lhints ~locality dbnames
| HintsTransparencyEntry (lhints, b) ->
add_transparency lhints b ~locality dbnames
| HintsExternEntry (info, tacexp) ->
add_externs info tacexp ~locality dbnames
let hint_globref gr = IsGlobRef gr
let warn_non_reference_hint_using =
CWarnings.create ~name:"non-reference-hint-using" ~category:CWarnings.CoreCategories.deprecated
Pp.(fun (env, sigma, c) -> str "Use of the non-reference term " ++ pr_leconstr_env env sigma c ++ str " in \"using\" clauses is deprecated")
let expand_constructor_hints env sigma lems = List.map_append (fun lem -> let evd, lem = lem env sigma in let lem0 = drop_extra_implicit_args evd lem in match EConstr.kind evd lem0 with
| Ind (ind,u) -> List.init (nconstructors env ind)
(fun i -> IsGlobRef (GlobRef.ConstructRef ((ind,i+1))))
| Const (cst, _) -> [IsGlobRef (GlobRef.ConstRef cst)]
| Var id -> [IsGlobRef (GlobRef.VarRef id)]
| Construct (cstr, _) -> [IsGlobRef (GlobRef.ConstructRef cstr)]
| _ -> let () = warn_non_reference_hint_using (env, evd, lem) in let (c, ctx) = prepare_hint env sigma (evd,lem) in let ctx = if UnivGen.is_empty_sort_context ctx then None else Some ctx in
[IsConstr (c, ctx)]) lems (* builds a hint database from a constr signature *) (* typically used with (lid, ltyp) = pf_hyps_types <some goal> *)
let constructor_hints env sigma eapply lems = let lems = expand_constructor_hints env sigma lems in List.map_append (fun lem ->
make_resolves env sigma (eapply, true) empty_hint_info ~check:true lem) lems
let make_local_hint_db env sigma ts eapply lems = let sign = EConstr.named_context env in let ts = match ts with
| None -> Hint_db.transparent_state (searchtable_map "core")
| Some ts -> ts in let hintlist = List.map_append (fun decl -> make_resolve_hyp env sigma (Named.Declaration.get_id decl)) sign in
Hint_db.empty ts false
|> Hint_db.add_list env sigma hintlist
|> Hint_db.add_list env sigma (constructor_hints env sigma eapply lems)
let make_db_list dbnames = let fold (core, nocore) db = ifString.equal db "core"then (true, nocore) elseifString.equal db "nocore"then (core, true) else (core, nocore) in let has_core, has_nocore = List.fold_left fold (false, false) dbnames in let dbnames = match has_core, has_nocore with
| true, true -> user_err Pp.(str "The core and nocore databases are mutually exclusive")
| true, false -> dbnames
| false, true -> List.remove String.equal "nocore" dbnames
| false, false -> "core" :: dbnames in let lookup db = try searchtable_map db with Not_found -> error_no_such_hint_database db in List.map lookup dbnames
let push_resolves env sigma hint db = let entries = make_resolves env sigma (true, false) empty_hint_info ~check:false (IsGlobRef hint) in
Hint_db.add_list env sigma entries db
let push_resolve_hyp env sigma decl db = let entries = make_resolve_hyp env sigma decl in
Hint_db.add_list env sigma entries db
(**************************************************************************) (* Functions for printing the hints *) (**************************************************************************)
let pr_hint_elt env sigma h = pr_econstr_env env sigma h.hint_term
let pr_hint env sigma h = match h.obj with
| Res_pf c -> (str"simple apply " ++ pr_hint_elt env sigma c)
| ERes_pf c -> (str"simple eapply " ++ pr_hint_elt env sigma c)
| Give_exact c -> (str"exact " ++ pr_hint_elt env sigma c)
| Res_pf_THEN_trivial_fail c ->
(str"simple apply " ++ pr_hint_elt env sigma c ++ str" ; trivial")
| Unfold_nth c ->
str"unfold " ++ pr_evaluable_reference c
| Extern (_, tac) ->
str "(*external*) " ++ Gentactic.print_glob env sigma ~level:(LevelLe 0) tac
let pr_id_hint env sigma (id, v) = let pr_pat p = match p.pat with
| None -> mt ()
| Some (ConstrPattern p | SyntacticPattern p) -> str", pattern " ++ pr_lconstr_pattern_env env sigma p
| Some DefaultPattern -> str", pattern " ++ pr_leconstr_env env sigma (get_default_pattern v.code.obj) in
(pr_hint env sigma v.code ++ str" (cost " ++ int v.pri ++ pr_pat v
++ str", id " ++ int id ++ str ")")
let pr_hints_db env sigma (name,db,hintlist) =
(str "In the database " ++ str name ++ str ":" ++ ifList.is_empty hintlist then (str " nothing" ++ fnl ()) else (fnl () ++ pr_hint_list env sigma hintlist))
(* Print all hints associated to head c in any database *) let pr_hint_list_for_head env sigma c = let dbs = current_db () in let validate (name, db) = let hints = List.map (fun v -> 0, v) (Hint_db.map_all ~secvars:Id.Pred.full c db) in
(name, db, hints) in let valid_dbs = List.map validate dbs in ifList.is_empty valid_dbs then
(str "No hint declared for :" ++ pr_global c) else
hov 0
(str"For " ++ pr_global c ++ str" -> " ++ fnl () ++
hov 0 (prlist (pr_hints_db env sigma) valid_dbs))
let pr_hint_ref ref = pr_hint_list_for_head ref
(* Print all hints associated to head id in any database *)
let pr_hint_term env sigma cl = try let dbs = current_db () in let valid_dbs = let fn = try let hdc = decompose_app_bound sigma cl in if occur_existential sigma cl then
(fun db -> match Hint_db.map_eauto env sigma ~secvars:Id.Pred.full hdc cl db with
| ModeMatch (_, l) -> l
| ModeMismatch -> []) else Hint_db.map_auto env sigma ~secvars:Id.Pred.full hdc cl with Bound -> Hint_db.map_none ~secvars:Id.Pred.full in let fn db = List.map (fun x -> 0, x) (fn db) in List.map (fun (name, db) -> (name, db, fn db)) dbs in ifList.is_empty valid_dbs then
(str "No hint applicable for current goal") else
(str "Applicable Hints :" ++ fnl () ++
hov 0 (prlist (pr_hints_db env sigma) valid_dbs)) with Match_failure _ | Failure _ ->
(str "No hint applicable for current goal")
(* print all hints that apply to the concl of the current goal *) let pr_applicable_hint pf = let env = Global.env () in let Proof.{goals;sigma} = Proof.data pf in match goals with
| [] -> CErrors.user_err Pp.(str "No focused goal.")
| g::_ ->
pr_hint_term env sigma (Evd.evar_concl (Evd.find_undefined sigma g))
let parse_mode s = match s with
| "+" -> ModeInput
| "-" -> ModeOutput
| "!" -> ModeNoHeadEvar
| _ -> CErrors.user_err Pp.(str"Unrecognized hint mode " ++ str s)
let parse_modes s = let modes = String.split_on_char ' ' s in List.map parse_mode modes
let string_of_mode = function
| ModeInput -> "+"
| ModeOutput -> "-"
| ModeNoHeadEvar -> "!"
let pp_hint_mode m = str (string_of_mode m)
(* displays the whole hint database db *) let pr_hint_db_env env sigma db = let pr_mode = prvect_with_sep spc pp_hint_mode in let pr_modes l = ifList.is_empty l then mt () else str" (modes " ++ prlist_with_sep pr_comma pr_mode l ++ str")" in let content = let pr_one (head, modes, hintlist) = let goal_descr = match head with
| None -> str "For any goal"
| Some head -> str "For " ++ pr_global head ++ pr_modes modes in (* sort because db.hintdb_nopat isn't kept in priority sorted order;
"auto" sorts on priority before using the hintdb *) let sorted = List.stable_sort (fun a b -> Int.compare a.pri b.pri) hintlist in (* always prints "id 0" in Print HintDb *) let hints = pr_hint_list env sigma (List.map (fun x -> (0, x)) sorted) in
hov 0 (goal_descr ++ str " -> " ++ hints) in let hints = let name x = Nametab.shortest_qualid_of_global Id.Set.empty x in let order (h1, _, _) (h2, _, _) = Option.compare (fun a b -> let a = name a and b = name b in let rv = Id.compare (qualid_basename a) (qualid_basename b) in if rv <> 0then rv else String.compare (string_of_qualid a) (string_of_qualid b))
h1 h2 in let hints = Hint_db.fold (fun h m hl l -> (h, m, hl) :: l) db [] in List.stable_sort order hints in
Pp.prlist pr_one hints in let { TransparentState.tr_var = ids; tr_cst = csts; tr_prj = ps } =
Hint_db.transparent_state db in
hov 0
((if Hint_db.use_dn db then str"Discriminated database" else str"Non-discriminated database")) ++ fnl () ++
hov 2 (str"Unfoldable variable definitions: " ++ pr_idpred ids) ++ fnl () ++
hov 2 (str"Unfoldable constant definitions: " ++ pr_cpred csts) ++ fnl () ++
hov 2 (str"Unfoldable projection definitions: " ++ pr_prpred ps) ++ fnl () ++
hov 2 (str"Cut: " ++ pp_hints_path (Hint_db.cut db)) ++ fnl () ++
content
let pr_hint_db_by_name env sigma dbname = try let db = searchtable_map dbname in pr_hint_db_env env sigma db with Not_found ->
error_no_such_hint_database dbname
(* displays all the hints of all databases *) let pr_searchtable env sigma = let fold name db accu =
accu ++ str "In the database " ++ str name ++ str ":" ++ fnl () ++
pr_hint_db_env env sigma db ++ fnl () in
Hintdbmap.fold fold !searchtable (mt ())
let print_mp mp = try let qid = Nametab.shortest_qualid_of_module mp in
str " from " ++ pr_qualid qid with Not_found -> mt ()
let is_imported h = try KNmap.find h.uid !statustable with Not_found -> true
let hint_trace = Evd.Store.field "hint_trace"
let log_hint h = letopen Proofview.Notations in
Proofview.tclEVARMAP >>= fun sigma -> let store = get_extra_data sigma in match Store.get store hint_trace with
| None -> (* All calls to hint logging should be well-scoped *)
assert false
| Some trace -> let trace = KNmap.add h.uid h trace in let store = Store.set store hint_trace trace in
Proofview.Unsafe.tclEVARS (set_extra_data store sigma)
let warn_non_imported_hint =
CWarnings.create ~name:"non-imported-hint" ~category:CWarnings.CoreCategories.automation
(fun (hint,mp) ->
strbrk "Hint used but not imported: " ++ hint ++ print_mp mp)
let warn env sigma h = let hint = pr_hint env sigma h in let mp = KerName.modpath h.uid in
warn_non_imported_hint (hint,mp)
let wrap_hint_warning t = letopen Proofview.Notations in
Proofview.tclEVARMAP >>= fun sigma -> let store = get_extra_data sigma in let old = Store.get store hint_trace in let store = Store.set store hint_trace KNmap.empty in
Proofview.Unsafe.tclEVARS (set_extra_data store sigma) >>= fun () ->
t >>= fun ans ->
Proofview.tclENV >>= fun env ->
Proofview.tclEVARMAP >>= fun sigma -> let store = get_extra_data sigma in let hints = match Store.get store hint_trace with
| None -> assert false
| Some hints -> hints in let () = KNmap.iter (fun _ h -> warn env sigma h) hints in let store = match old with
| None -> Store.remove store hint_trace
| Some v -> Store.set store hint_trace v in
Proofview.Unsafe.tclEVARS (set_extra_data store sigma) >>= fun () ->
Proofview.tclUNIT ans
let wrap_hint_warning_fun env sigma t = let store = get_extra_data sigma in let old = Store.get store hint_trace in let store = Store.set store hint_trace KNmap.empty in let (ans, sigma) = t (set_extra_data store sigma) in let store = get_extra_data sigma in let hints = match Store.get store hint_trace with
| None -> assert false
| Some hints -> hints in let () = KNmap.iter (fun _ h -> warn env sigma h) hints in let store = match old with
| None -> Store.remove store hint_trace
| Some v -> Store.set store hint_trace v in
(ans, set_extra_data store sigma)
let run_hint tac k = match warn_hint () with
| HintLax -> k tac.obj
| HintWarn -> if is_imported tac then k tac.obj else Proofview.tclTHEN (log_hint tac) (k tac.obj)
| HintStrict -> if is_imported tac then k tac.obj else let info = Exninfo.reify () in
Proofview.tclZERO ~info (UserError (str "Tactic failure."))
module FullHint = struct type t = full_hint let priority (h : t) = h.pri let database (h : t) = h.db let pattern (h : t) = match h.pat with
| None -> None
| Some (ConstrPattern p | SyntacticPattern p) -> Some p
| Some DefaultPattern -> None let run (h : t) k = run_hint h.code k letprint env sigma (h : t) = pr_hint env sigma h.code let name (h : t) = h.name
let subgoals (h : t) = match h.code.obj with
| Res_pf h | ERes_pf h | Give_exact h | Res_pf_THEN_trivial_fail h -> Some h.hint_arty
| Unfold_nth _ -> Some 1
| Extern _ -> None
let repr (h : t) = h.code.obj end
let connect_hint_clenv h gl = let { hint_uctx = ctx; hint_clnv = clenv } = h in (* [clenv] has been generated by a hint-making function, so the only relevant datainitsevarmapisthesetofmetas.Thefunction
below just replaces the metas of sigma by those coming from the clenv. *) let env = Proofview.Goal.env gl in let sigma = Proofview.Goal.sigma gl in
Clenv.clenv_refresh env sigma ctx clenv
let fresh_hint env sigma h = let { hint_term = c; hint_uctx = ctx } = h in match h.hint_uctx with
| None -> sigma, c
| Some ctx -> (* Refresh the instance of the hint *) let (subst, ctx) = UnivGen.fresh_sort_context_instance ctx in let c = Vars.subst_univs_level_constr subst c in let sigma = Evd.merge_sort_context_set Evd.univ_flexible sigma ctx in
sigma, c
let hint_res_pf ?with_evars ?with_classes ?flags h =
Proofview.Goal.enter beginfun gl -> let clenv = connect_hint_clenv h gl in
Clenv.res_pf ?with_evars ?with_classes ?flags clenv end
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.90 Sekunden
(vorverarbeitet am 2026-09-27)
¤