]> matita.cs.unibo.it Git - helm.git/blob - helm/ocaml/cic_unification/cicMetaSubst.ml
- In the case (?i args) vs term the term is now eta-expanded w.r.t. args.
[helm.git] / helm / ocaml / cic_unification / cicMetaSubst.ml
1 (* Copyright (C) 2004, HELM Team.
2  * 
3  * This file is part of HELM, an Hypertextual, Electronic
4  * Library of Mathematics, developed at the Computer Science
5  * Department, University of Bologna, Italy.
6  * 
7  * HELM is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * 
12  * HELM is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with HELM; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
20  * MA  02111-1307, USA.
21  * 
22  * For details, see the HELM World-Wide-Web page,
23  * http://cs.unibo.it/helm/.
24  *)
25
26 open Printf
27
28 exception MetaSubstFailure of string
29 exception Uncertain of string
30 exception AssertFailure of string
31
32 let debug_print = prerr_endline
33
34 type substitution = (int * Cic.term) list
35
36 (*** Functions to apply a substitution ***)
37
38 let apply_subst_gen ~appl_fun subst term =
39  let rec um_aux =
40   let module C = Cic in
41   let module S = CicSubstitution in 
42    function
43       C.Rel _ as t -> t
44     | C.Var (uri,exp_named_subst) ->
45        let exp_named_subst' =
46          List.map (fun (uri, t) -> (uri, um_aux t)) exp_named_subst
47        in
48        C.Var (uri, exp_named_subst')
49     | C.Meta (i, l) -> 
50         (try
51           let t = List.assoc i subst in
52           um_aux (S.lift_meta l t)
53         with Not_found -> (* not constrained variable, i.e. free in subst*)
54           let l' =
55             List.map (function None -> None | Some t -> Some (um_aux t)) l
56           in
57            C.Meta (i,l'))
58     | C.Sort _ as t -> t
59     | C.Implicit _ -> assert false
60     | C.Cast (te,ty) -> C.Cast (um_aux te, um_aux ty)
61     | C.Prod (n,s,t) -> C.Prod (n, um_aux s, um_aux t)
62     | C.Lambda (n,s,t) -> C.Lambda (n, um_aux s, um_aux t)
63     | C.LetIn (n,s,t) -> C.LetIn (n, um_aux s, um_aux t)
64     | C.Appl (hd :: tl) -> appl_fun um_aux hd tl
65     | C.Appl _ -> assert false
66     | C.Const (uri,exp_named_subst) ->
67        let exp_named_subst' =
68          List.map (fun (uri, t) -> (uri, um_aux t)) exp_named_subst
69        in
70        C.Const (uri, exp_named_subst')
71     | C.MutInd (uri,typeno,exp_named_subst) ->
72        let exp_named_subst' =
73          List.map (fun (uri, t) -> (uri, um_aux t)) exp_named_subst
74        in
75        C.MutInd (uri,typeno,exp_named_subst')
76     | C.MutConstruct (uri,typeno,consno,exp_named_subst) ->
77        let exp_named_subst' =
78          List.map (fun (uri, t) -> (uri, um_aux t)) exp_named_subst
79        in
80        C.MutConstruct (uri,typeno,consno,exp_named_subst')
81     | C.MutCase (sp,i,outty,t,pl) ->
82        let pl' = List.map um_aux pl in
83        C.MutCase (sp, i, um_aux outty, um_aux t, pl')
84     | C.Fix (i, fl) ->
85        let fl' =
86          List.map (fun (name, i, ty, bo) -> (name, i, um_aux ty, um_aux bo)) fl
87        in
88        C.Fix (i, fl')
89     | C.CoFix (i, fl) ->
90        let fl' =
91          List.map (fun (name, ty, bo) -> (name, um_aux ty, um_aux bo)) fl
92        in
93        C.CoFix (i, fl')
94  in
95  um_aux term
96 ;;
97
98 let apply_subst =
99 (* CSC: old code that never performs beta reduction
100   let appl_fun um_aux he tl =
101     let tl' = List.map um_aux tl in
102       begin
103        match um_aux he with
104           Cic.Appl l -> Cic.Appl (l@tl')
105         | he' -> Cic.Appl (he'::tl')
106       end
107   in
108   apply_subst_gen ~appl_fun
109 *)
110   let appl_fun um_aux he tl =
111     let tl' = List.map um_aux tl in
112     let t' =
113      match um_aux he with
114         Cic.Appl l -> Cic.Appl (l@tl')
115       | he' -> Cic.Appl (he'::tl')
116     in
117      begin
118       match he with
119          Cic.Meta (m,_) ->
120           let rec beta_reduce =
121            function
122               (Cic.Appl (Cic.Lambda (_,_,t)::he'::tl')) ->
123                 let he'' = CicSubstitution.subst he' t in
124                  if tl' = [] then
125                   he''
126                  else
127                   beta_reduce (Cic.Appl(he''::tl'))
128             | t -> t
129           in
130            beta_reduce t'
131        | _ -> t'
132      end
133   in
134   apply_subst_gen ~appl_fun
135 ;;
136
137 let rec apply_subst_context subst context =
138   List.fold_right
139     (fun item context ->
140       match item with
141       | Some (n, Cic.Decl t) ->
142           let t' = apply_subst subst t in
143           Some (n, Cic.Decl t') :: context
144       | Some (n, Cic.Def (t, ty)) ->
145           let ty' =
146             match ty with
147             | None -> None
148             | Some ty -> Some (apply_subst subst ty)
149           in
150           let t' = apply_subst subst t in
151           Some (n, Cic.Def (t', ty')) :: context
152       | None -> None :: context)
153     context []
154
155 let apply_subst_metasenv subst metasenv =
156   List.map
157     (fun (n, context, ty) ->
158       (n, apply_subst_context subst context, apply_subst subst ty))
159     (List.filter
160       (fun (i, _, _) -> not (List.exists (fun (j, _) -> (j = i)) subst))
161       metasenv)
162
163 (***** Pretty printing functions ******)
164
165 let ppsubst subst =
166   String.concat "\n"
167     (List.map
168       (fun (idx, term) -> Printf.sprintf "?%d := %s" idx (CicPp.ppterm term))
169       subst)
170 ;;
171
172 let ppterm subst term = CicPp.ppterm (apply_subst subst term)
173
174 let ppterm_in_context subst term name_context =
175  CicPp.pp (apply_subst subst term) name_context
176
177 let ppcontext' ?(sep = "\n") subst context =
178  let separate s = if s = "" then "" else s ^ sep in
179   List.fold_right 
180    (fun context_entry (i,name_context) ->
181      match context_entry with
182         Some (n,Cic.Decl t) ->
183          sprintf "%s%s : %s" (separate i) (CicPp.ppname n)
184           (ppterm_in_context subst t name_context), (Some n)::name_context
185       | Some (n,Cic.Def (bo,ty)) ->
186          sprintf "%s%s : %s := %s" (separate i) (CicPp.ppname n)
187           (match ty with
188               None -> "_"
189             | Some ty -> ppterm_in_context subst ty name_context)
190           (ppterm_in_context subst bo name_context), (Some n)::name_context
191        | None ->
192           sprintf "%s_ :? _" (separate i), None::name_context
193     ) context ("",[])
194
195 let ppcontext ?sep subst context = fst (ppcontext' ?sep subst context)
196
197 let ppmetasenv ?(sep = "\n") metasenv subst =
198   String.concat sep
199     (List.map
200       (fun (i, c, t) ->
201         let context,name_context = ppcontext' ~sep:"; " subst c in
202          sprintf "%s |- ?%d: %s" context i
203           (ppterm_in_context subst t name_context))
204       (List.filter
205         (fun (i, _, _) -> not (List.exists (fun (j, _) -> (j = i)) subst))
206         metasenv))
207
208 (* From now on we recreate a kernel abstraction where substitutions are part of
209  * the calculus *)
210
211 let lift subst n term =
212   let term = apply_subst subst term in
213   try
214     CicSubstitution.lift n term
215   with e ->
216     raise (MetaSubstFailure ("Lift failure: " ^ Printexc.to_string e))
217
218 let subst subst t1 t2 =
219   let t1 = apply_subst subst t1 in
220   let t2 = apply_subst subst t2 in
221   try
222     CicSubstitution.subst t1 t2
223   with e ->
224     raise (MetaSubstFailure ("Subst failure: " ^ Printexc.to_string e))
225
226 let whd subst context term =
227   let term = apply_subst subst term in
228   let context = apply_subst_context subst context in
229   try
230     CicReduction.whd context term
231   with e ->
232     raise (MetaSubstFailure ("Weak head reduction failure: " ^
233       Printexc.to_string e))
234
235 let are_convertible subst context t1 t2 =
236   let context = apply_subst_context subst context in
237   let t1 = apply_subst subst t1 in
238   let t2 = apply_subst subst t2 in
239   CicReduction.are_convertible context t1 t2
240
241 let tempi_type_of_aux_subst = ref 0.0;;
242 let tempi_type_of_aux = ref 0.0;;
243
244 let type_of_aux' metasenv subst context term =
245 let time1 = Unix.gettimeofday () in
246   let term = apply_subst subst term in
247   let context = apply_subst_context subst context in
248   let metasenv =
249     List.map
250       (fun (i, c, t) -> (i, apply_subst_context subst c, apply_subst subst t))
251       (List.filter
252         (fun (i, _, _) -> not (List.exists (fun (j, _) -> (j = i)) subst))
253         metasenv)
254   in
255 let time2 = Unix.gettimeofday () in
256 let res =
257   try
258     CicTypeChecker.type_of_aux' metasenv context term
259   with CicTypeChecker.TypeCheckerFailure msg ->
260     raise (MetaSubstFailure ("Type checker failure: " ^ msg))
261 in
262 let time3 = Unix.gettimeofday () in
263  tempi_type_of_aux_subst := !tempi_type_of_aux_subst +. time3 -. time1 ; 
264  tempi_type_of_aux := !tempi_type_of_aux +. time2 -. time1 ; 
265  res
266
267 (**** DELIFT ****)
268 (* the delift function takes in input a metavariable index, an ordered list of
269  * optional terms [t1,...,tn] and a term t, and substitutes every tk = Some
270  * (rel(nk)) with rel(k).  Typically, the list of optional terms is the explicit
271  * substitution that is applied to a metavariable occurrence and the result of
272  * the delift function is a term the implicit variable can be substituted with
273  * to make the term [t] unifiable with the metavariable occurrence.  In general,
274  * the problem is undecidable if we consider equivalence in place of alpha
275  * convertibility. Our implementation, though, is even weaker than alpha
276  * convertibility, since it replace the term [tk] if and only if [tk] is a Rel
277  * (missing all the other cases). Does this matter in practice?
278  * The metavariable index is the index of the metavariable that must not occur
279  * in the term (for occur check).
280  *)
281
282 exception NotInTheList;;
283
284 let position n =
285   let rec aux k =
286    function 
287        [] -> raise NotInTheList
288      | (Some (Cic.Rel m))::_ when m=n -> k
289      | _::tl -> aux (k+1) tl in
290   aux 1
291 ;;
292
293 exception Occur;;
294
295 let rec force_does_not_occur subst to_be_restricted t =
296  let module C = Cic in
297  let more_to_be_restricted = ref [] in
298  let rec aux k = function
299       C.Rel r when List.mem (r - k) to_be_restricted -> raise Occur
300     | C.Rel _
301     | C.Sort _ as t -> t
302     | C.Implicit _ -> assert false
303     | C.Meta (n, l) ->
304        (* we do not retrieve the term associated to ?n in subst since *)
305        (* in this way we can restrict if something goes wrong         *)
306        let l' =
307          let i = ref 0 in
308          List.map
309            (function t ->
310              incr i ;
311              match t with
312                 None -> None
313               | Some t ->
314                  try
315                    Some (aux k t)
316                  with Occur ->
317                    more_to_be_restricted := (n,!i) :: !more_to_be_restricted;
318                    None)
319            l
320        in
321         C.Meta (n, l')
322     | C.Cast (te,ty) -> C.Cast (aux k te, aux k ty)
323     | C.Prod (name,so,dest) -> C.Prod (name, aux k so, aux (k+1) dest)
324     | C.Lambda (name,so,dest) -> C.Lambda (name, aux k so, aux (k+1) dest)
325     | C.LetIn (name,so,dest) -> C.LetIn (name, aux k so, aux (k+1) dest)
326     | C.Appl l -> C.Appl (List.map (aux k) l)
327     | C.Var (uri,exp_named_subst) ->
328         let exp_named_subst' =
329           List.map (fun (uri,t) -> (uri, aux k t)) exp_named_subst
330         in
331         C.Var (uri, exp_named_subst')
332     | C.Const (uri, exp_named_subst) ->
333         let exp_named_subst' =
334           List.map (fun (uri,t) -> (uri, aux k t)) exp_named_subst
335         in
336         C.Const (uri, exp_named_subst')
337     | C.MutInd (uri,tyno,exp_named_subst) ->
338         let exp_named_subst' =
339           List.map (fun (uri,t) -> (uri, aux k t)) exp_named_subst
340         in
341         C.MutInd (uri, tyno, exp_named_subst')
342     | C.MutConstruct (uri,tyno,consno,exp_named_subst) ->
343         let exp_named_subst' =
344           List.map (fun (uri,t) -> (uri, aux k t)) exp_named_subst
345         in
346         C.MutConstruct (uri, tyno, consno, exp_named_subst')
347     | C.MutCase (uri,tyno,out,te,pl) ->
348         C.MutCase (uri, tyno, aux k out, aux k te, List.map (aux k) pl)
349     | C.Fix (i,fl) ->
350        let len = List.length fl in
351        let k_plus_len = k + len in
352        let fl' =
353          List.map
354           (fun (name,j,ty,bo) -> (name, j, aux k ty, aux k_plus_len bo)) fl
355        in
356        C.Fix (i, fl')
357     | C.CoFix (i,fl) ->
358        let len = List.length fl in
359        let k_plus_len = k + len in
360        let fl' =
361          List.map
362           (fun (name,ty,bo) -> (name, aux k ty, aux k_plus_len bo)) fl
363        in
364        C.CoFix (i, fl')
365  in
366  let res = aux 0 t in
367  (!more_to_be_restricted, res)
368  
369 let rec restrict subst to_be_restricted metasenv =
370   let names_of_context_indexes context indexes =
371     String.concat ", "
372       (List.map
373         (fun i ->
374           try
375            match List.nth context i with
376            | None -> assert false
377            | Some (n, _) -> CicPp.ppname n
378           with
379            Failure _ -> assert false
380         ) indexes)
381   in
382   let force_does_not_occur_in_context to_be_restricted = function
383     | None -> [], None
384     | Some (name, Cic.Decl t) ->
385         let (more_to_be_restricted, t') =
386           force_does_not_occur subst to_be_restricted t
387         in
388         more_to_be_restricted, Some (name, Cic.Decl t')
389     | Some (name, Cic.Def (bo, ty)) ->
390         let (more_to_be_restricted, bo') =
391           force_does_not_occur subst to_be_restricted bo
392         in
393         let more_to_be_restricted, ty' =
394           match ty with
395           | None ->  more_to_be_restricted, None
396           | Some ty ->
397               let more_to_be_restricted', ty' =
398                 force_does_not_occur subst to_be_restricted ty
399               in
400               more_to_be_restricted @ more_to_be_restricted',
401               Some ty'
402         in
403         more_to_be_restricted, Some (name, Cic.Def (bo', ty'))
404   in
405   let rec erase i to_be_restricted n = function
406     | [] -> [], to_be_restricted, []
407     | hd::tl ->
408         let more_to_be_restricted,restricted,tl' =
409          erase (i+1) to_be_restricted n tl
410         in
411         let restrict_me = List.mem i restricted in
412         if restrict_me then
413          more_to_be_restricted, restricted, None:: tl'
414         else
415           (try
416             let more_to_be_restricted', hd' =
417               let delifted_restricted =
418                let rec aux =
419                 function
420                    [] -> []
421                  | j::tl when j > i -> (j - i)::aux tl
422                  | _::tl -> aux tl
423                in
424                 aux restricted
425               in
426                force_does_not_occur_in_context delifted_restricted hd
427             in
428              more_to_be_restricted @ more_to_be_restricted',
429              restricted, hd' :: tl'
430           with Occur ->
431             more_to_be_restricted, (i :: restricted), None :: tl')
432   in
433   let (more_to_be_restricted, metasenv, subst) =
434     List.fold_right
435       (fun (n, context, t) (more, metasenv, subst) ->
436         let to_be_restricted =
437           List.map snd (List.filter (fun (m, _) -> m = n) to_be_restricted)
438         in
439         let (more_to_be_restricted, restricted, context') =
440          (* just an optimization *)
441          if to_be_restricted = [] then
442           [],[],context
443          else
444           erase 1 to_be_restricted n context
445         in
446         try
447           let more_to_be_restricted', t' =
448             force_does_not_occur subst restricted t
449           in
450           let metasenv' = (n, context', t') :: metasenv in
451           (try
452             let s = List.assoc n subst in
453             try
454               let more_to_be_restricted'', s' =
455                 force_does_not_occur subst restricted s
456               in
457               let subst' = (n, s') :: (List.remove_assoc n subst) in
458               let more =
459                 more @ more_to_be_restricted @ more_to_be_restricted' @
460                   more_to_be_restricted''
461               in
462               (more, metasenv', subst')
463             with Occur ->
464               raise (MetaSubstFailure (sprintf
465                 "Cannot restrict the context of the metavariable ?%d over the hypotheses %s since ?%d is already instantiated with %s and at least one of the hypotheses occurs in the substituted term"
466                 n (names_of_context_indexes context to_be_restricted) n
467                 (ppterm subst s)))
468            with Not_found -> (more @ more_to_be_restricted @ more_to_be_restricted', metasenv', subst))
469         with Occur ->
470           raise (MetaSubstFailure (sprintf
471             "Cannot restrict the context of the metavariable ?%d over the hypotheses %s since metavariable's type depends on at least one of them"
472             n (names_of_context_indexes context to_be_restricted))))
473       metasenv ([], [], subst)
474   in
475   match more_to_be_restricted with
476   | [] -> (metasenv, subst)
477   | _ -> restrict subst more_to_be_restricted metasenv
478 ;;
479
480 (*CSC: maybe we should rename delift in abstract, as I did in my dissertation *)
481 let delift n subst context metasenv l t =
482  let module S = CicSubstitution in
483   let l =
484    let (_, canonical_context, _) = CicUtil.lookup_meta n metasenv in
485    List.map2 (fun ct lt ->
486      match (ct, lt) with
487      | None, _ -> None
488      | Some _, _ -> lt)
489      canonical_context l
490   in
491   let to_be_restricted = ref [] in
492   let rec deliftaux k =
493    let module C = Cic in
494     function
495        C.Rel m -> 
496          if m <=k then
497           C.Rel m   (*CSC: che succede se c'e' un Def? Dovrebbe averlo gia' *)
498                     (*CSC: deliftato la regola per il LetIn                 *)
499                     (*CSC: FALSO! La regola per il LetIn non lo fa          *)
500          else
501           (try
502             match List.nth context (m-k-1) with
503                Some (_,C.Def (t,_)) ->
504                 (*CSC: Hmmm. This bit of reduction is not in the spirit of    *)
505                 (*CSC: first order unification. Does it help or does it harm? *)
506                 deliftaux k (S.lift m t)
507              | Some (_,C.Decl t) ->
508                 C.Rel ((position (m-k) l) + k)
509              | None -> raise (MetaSubstFailure "RelToHiddenHypothesis")
510            with
511             Failure _ ->
512              raise (MetaSubstFailure "Unbound variable found in deliftaux")
513           )
514      | C.Var (uri,exp_named_subst) ->
515         let exp_named_subst' =
516          List.map (function (uri,t) -> uri,deliftaux k t) exp_named_subst
517         in
518          C.Var (uri,exp_named_subst')
519      | C.Meta (i, l1) as t -> 
520         if i = n then
521           raise (MetaSubstFailure (sprintf
522             "Cannot unify the metavariable ?%d with a term that has as subterm %s in which the same metavariable occurs (occur check)"
523             i (ppterm subst t)))
524         else
525          (* I do not consider the term associated to ?i in subst since *)
526          (* in this way I can restrict if something goes wrong.        *)
527           let rec deliftl j =
528            function
529               [] -> []
530             | None::tl -> None::(deliftl (j+1) tl)
531             | (Some t)::tl ->
532                let l1' = (deliftl (j+1) tl) in
533                 try
534                  Some (deliftaux k t)::l1'
535                 with
536                    NotInTheList
537                  | MetaSubstFailure _ ->
538                     to_be_restricted := (i,j)::!to_be_restricted ; None::l1'
539           in
540            let l' = deliftl 1 l1 in
541             C.Meta(i,l')
542      | C.Sort _ as t -> t
543      | C.Implicit _ as t -> t
544      | C.Cast (te,ty) -> C.Cast (deliftaux k te, deliftaux k ty)
545      | C.Prod (n,s,t) -> C.Prod (n, deliftaux k s, deliftaux (k+1) t)
546      | C.Lambda (n,s,t) -> C.Lambda (n, deliftaux k s, deliftaux (k+1) t)
547      | C.LetIn (n,s,t) -> C.LetIn (n, deliftaux k s, deliftaux (k+1) t)
548      | C.Appl l -> C.Appl (List.map (deliftaux k) l)
549      | C.Const (uri,exp_named_subst) ->
550         let exp_named_subst' =
551          List.map (function (uri,t) -> uri,deliftaux k t) exp_named_subst
552         in
553          C.Const (uri,exp_named_subst')
554      | C.MutInd (uri,typeno,exp_named_subst) ->
555         let exp_named_subst' =
556          List.map (function (uri,t) -> uri,deliftaux k t) exp_named_subst
557         in
558          C.MutInd (uri,typeno,exp_named_subst')
559      | C.MutConstruct (uri,typeno,consno,exp_named_subst) ->
560         let exp_named_subst' =
561          List.map (function (uri,t) -> uri,deliftaux k t) exp_named_subst
562         in
563          C.MutConstruct (uri,typeno,consno,exp_named_subst')
564      | C.MutCase (sp,i,outty,t,pl) ->
565         C.MutCase (sp, i, deliftaux k outty, deliftaux k t,
566          List.map (deliftaux k) pl)
567      | C.Fix (i, fl) ->
568         let len = List.length fl in
569         let liftedfl =
570          List.map
571           (fun (name, i, ty, bo) ->
572            (name, i, deliftaux k ty, deliftaux (k+len) bo))
573            fl
574         in
575          C.Fix (i, liftedfl)
576      | C.CoFix (i, fl) ->
577         let len = List.length fl in
578         let liftedfl =
579          List.map
580           (fun (name, ty, bo) -> (name, deliftaux k ty, deliftaux (k+len) bo))
581            fl
582         in
583          C.CoFix (i, liftedfl)
584   in
585    let res =
586     try
587      deliftaux 0 t
588     with
589      NotInTheList ->
590       (* This is the case where we fail even first order unification. *)
591       (* The reason is that our delift function is weaker than first  *)
592       (* order (in the sense of alpha-conversion). See comment above  *)
593       (* related to the delift function.                              *)
594 debug_print "\n!!!!!!!!!!! First Order UnificationFailure, but maybe it could have been successful even in a first order setting (no conversion, only alpha convertibility)! Please, implement a better delift function !!!!!!!!!!!!!!!!" ;
595       raise (Uncertain (sprintf
596         "Error trying to abstract %s over [%s]: the algorithm only tried to abstract over bound variables"
597         (ppterm subst t)
598         (String.concat "; "
599           (List.map
600             (function Some t -> ppterm subst t | None -> "_")
601             l))))
602    in
603    let (metasenv, subst) = restrict subst !to_be_restricted metasenv in
604     res, metasenv, subst
605 ;;
606
607 (**** END OF DELIFT ****)
608
609
610 (** {2 Format-like pretty printers} *)
611
612 let fpp_gen ppf s =
613   Format.pp_print_string ppf s;
614   Format.pp_print_newline ppf ();
615   Format.pp_print_flush ppf ()
616
617 let fppsubst ppf subst = fpp_gen ppf (ppsubst subst)
618 let fppterm ppf term = fpp_gen ppf (CicPp.ppterm term)
619 let fppmetasenv ppf metasenv = fpp_gen ppf (ppmetasenv metasenv [])
620