123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249(* Yoann Padioleau
*
* Copyright (C) 2019 r2c
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
* license.txt for more details.
*)openAst_pythonmoduleAst=Ast_pythonmoduleV=Visitor_python(*****************************************************************************)(* Prelude *)(*****************************************************************************)(* Identifiers tagger (so we can colorize them differently in codemap/efuns).
*
* See also Ast_python.resolved_name.
*
* TODO: generalize for ast_generic at some point?
* This naming phase helps dealing with:
* - scoping issues in static analysis as it deals with
* variable shadowing (every variables now have a different name)
* - coloring identifiers in codemap/efuns
*)(*****************************************************************************)(* Type *)(*****************************************************************************)typecontext=|AtToplevel|InClass|InFunction(* TODO: InLambda *)typeenv={ctx:contextref;names:(string*resolved_name)listref;}letdefault_env()={ctx=refAtToplevel;names=ref[];}(*****************************************************************************)(* Helpers *)(*****************************************************************************)(* because we use Visitor_python instead of a clean recursive
* function passing down an environment, we need to emulate a scoped
* environment by using save_excursion.
*)letwith_added_envxsenvf=letnewnames=xs@!(env.names)inCommon.save_excursionenv.namesnewnamesfletadd_name_envnamekindenv=env.names:=(Ast.str_of_namename,kind)::!(env.names)letwith_new_contextctxenvf=Common.save_excursionenv.ctxctxfletparams_of_parametersparams=params|>Common.map_filter(function|ParamClassic((name,_),_)|ParamStar(name,_)|ParamPow(name,_)->Somename)(*****************************************************************************)(* Entry point *)(*****************************************************************************)letresolveprog=letenv=default_env()in(* helper to factorize code related to (polymorphic comprehension) *)letcomprehensionkev(e,xs)=letnew_vars=xs|>Common.map_filter(function|CompFor(target,_)->(matchtargetwith|Name(name,_ctx,_res)->Somename(* tuples? *)|_->None)|CompIf_->None)inletnew_names=new_vars|>List.map(funname->Ast.str_of_namename,Ast.LocalVar)inwith_added_envnew_namesenv(fun()->kee);(* TODO: should fold and use new env *)xs|>List.iter(function|CompFor(e1,e2)->v(Expre1);v(Expre2)|CompIfe->v(Expre))in(* would be better to use a classic recursive with environment visit *)letvisitor=V.mk_visitor{V.default_visitorwith(* No need to resolve at the definition sites (for parameters, locals).
* This will be patterned-match specially anyway in the highlighter. What
* you want is to tag the use sites, and to maintain the right environment.
*)V.kexpr=(fun(k,v)x->matchxwith|Name(name,ctx,resolved)->(matchctxwith|Load|AugLoad->(* assert resolved = NotResolved *)lets=Ast.str_of_namenamein(matchList.assoc_opts!(env.names)with|Somex->resolved:=x|None->()(* will be tagged as Error by highlighter later *))|Store|AugStore->letkind=lets=Ast.str_of_namenameinmatchList.assoc_opts!(env.names)with(* can happen if had a 'Global' declaration before *)|Somex->x|None->(match!(env.ctx)with|AtToplevel->GlobalVar|InClass->ClassField|InFunction->LocalVar)inenv|>add_name_envnamekind;resolved:=kind;(* optional *)|Del->(* should remove from env *)()|Param->resolved:=Parameter;(* optional *));kx|Tuple(CompForIfx,_)|List(CompForIfx,_)(* bugfix: do not pass just k here, because we want to intercept
* the processing of 'e' too!
*)->comprehension(fune->v(Expre))vx|DictOrSet(CompForIfx)->comprehension(funelt->v(DictElemelt))vx(* general case *)|_->kx);V.kstmt=(fun(k,v)x->matchxwith|FunctionDef(name,params,_typopt,_body,_decorators)->letnew_params=params_of_parameters(params:parameters)inletnew_names=new_params|>List.map(funname->Ast.str_of_namename,Ast.Parameter)inwith_added_envnew_namesenv(fun()->with_new_contextInFunctionenv(fun()->kx));(* nested function *)if!(env.ctx)=InFunctionthenenv|>add_name_envname(LocalVar);|ClassDef(name,_bases,_body,_decorators)->env|>add_name_envname(ImportedEntity[name]);(* could be more precise *)with_new_contextInClassenv(fun()->kx)|Import(aliases)->aliases|>List.iter(fun(dotted_name,asname_opt)->asname_opt|>Common.do_option(funasname->env|>add_name_envasname(ImportedModuledotted_name)););kx|ImportFrom(dotted_name,aliases,_)->aliases|>List.iter(fun(name,asname_opt)->letentity=dotted_name@[name]in(matchasname_optwith|None->env|>add_name_envname(ImportedEntityentity);|Someasname->env|>add_name_envasname(ImportedEntityentity)););kx|With(e,eopt,stmts)->v(Expre);(matcheoptwith|None->v(Stmtsstmts)|Some(Name(name,_ctx,_res))->(* the scope of name is valid only inside the body, but the
* body may define variables that are used then outside the with
* so simpler to use add_name_env() here, not with_add_env()
let new_names = (fst name, LocalVar)::!(env.names) in
with_added_env new_names env (fun () ->
v (Stmts stmts)
)
*)env|>add_name_envnameLocalVar;v(Stmtsstmts);(* todo: tuples? *)|Somee->v(Expre);v(Stmtsstmts))|TryExcept(stmts1,excepts,stmts2)->v(Stmtsstmts1);excepts|>List.iter(fun(ExceptHandler(_typ,e,body))->matchewith|None->v(Stmtsbody)|Some(Name(name,_ctx,_res))->letnew_names=(fstname,LocalVar)::!(env.names)inwith_added_envnew_namesenv(fun()->v(Stmtsbody))(* tuples? *)|Somee->v(Expre);v(Stmtsbody));v(Stmtsstmts2);|Globalnames->names|>List.iter(funname->env|>add_name_envnameGlobalVar;)(* TODO: NonLocal!! *)(* general case *)|_->kx);}invisitor(Programprog)