ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
python_annotation.h
Go to the documentation of this file.
1#pragma once
2
16
17#include <map>
18#include <set>
19#include <string>
20#include <unordered_map>
21#include <unordered_set>
22#include <utility>
23
24// `InferResult`, `builtin_functions` and the pure-helper utilities
25// (`has_annotation`, `has_return_none`, `get_type_from_json`,
26// `get_base_var_name`, `invert_substrings`,
27// `infer_type_from_default_arg_shape`) were moved to the
28// `python_annotation_intrinsics` and `python_annotation_utils`
29// modules. The using declarations below preserve the unqualified
30// names so existing call sites inside the .inl files remain byte-
31// identical except, for `builtin_functions`, the trailing parens
32// (`builtin_functions.find(...)` -> `builtin_functions().find(...)`).
43
44// Python-frontend annotation pass. Walks the JSON AST produced by
45// `parser.py` and decorates every assignment, parameter, and return
46// site with an inferred type so the downstream converter sees a
47// fully-typed program. Method definitions are split across .inl
48// files in python_annotation/ and are `#include`d at the bottom of
49// this header. The class shape, member layout, and template
50// parameter are unchanged from the pre-split version — this header
51// is a thin facade over the .inl files.
52template <class Json>
54{
55public:
57 : ast_(ast),
58 gs_(gs),
59 current_func(nullptr),
60 parent_func(nullptr),
61 current_line_(0)
62 {
63 python_filename_ = ast_["filename"].template get<std::string>();
64 if (ast_.contains("ast_output_dir"))
65 module_manager_ =
66 module_manager::create(ast_["ast_output_dir"], python_filename_);
67 }
68
69 // Entry points exercised by callers outside python_annotation.h.
70 bool preprocess_constructor_calls(Json &node);
71 void preprocess_method_calls(const Json &node);
73 bool is_instance_call(const Json &func) const;
74 Json *class_method(const std::string &cls, const Json &method);
77 static const std::string &unresolved_arg_type()
78 {
79 static const std::string s = "<unresolved>";
80 return s;
81 }
82
83 bool preprocess_function_calls(Json &root);
84 std::string
85 promote_to_float_operand(const Json &stmt, const std::string &type);
87 Json &root,
88 Json &param,
89 const std::pair<std::string, size_t> &key,
90 std::set<std::string> types,
91 const std::set<std::string> &top_level_funcs);
93 Json &node,
94 std::map<std::pair<std::string, size_t>, std::set<std::string>>
95 &param_types,
96 const std::set<std::string> &top_level_funcs,
97 std::set<std::pair<std::string, size_t>> &dynamically_typed_params);
99 void add_type_annotation(const std::string &func_name);
100
101 // Register an additional AST whose multi-axis subscript usages must be
102 // considered when inferring `__getitem__` / `__setitem__` key tuple
103 // types. Used when annotating an imported module to make the importing
104 // module's call sites visible (GitHub #4545). The caller retains
105 // ownership; @p ast must outlive this annotator.
106 void add_extra_subscript_inference_source(const Json &ast);
107
108private:
109 Json *
110 class_init(const std::string &class_name, unsigned depth, bool &inherited);
112 std::map<Json *, std::set<std::string>> inherited_init_args_;
113 // Implementations live in annotation_conversion.inl unless noted.
114
115 // ----- AST lookup and walk helpers -----
116 Json find_var_node_for_inference(const std::string &var_name);
117 // Whether an empty-list append argument resolves to a *direct* integer
118 // scalar (literal, int-annotated name, or name bound to a plain int
119 // source) rather than through a container/object indirection. See the
120 // call site in infer_type (issue #3239 / jpl_1 regression).
121 bool append_arg_is_plain_int(const Json &arg);
122 const Json
123 find_annotated_assign(const std::string &node_name, const Json &body);
124 Json
125 match_unpacking_assignment(const Json &elem, const std::string &node_name);
126 Json find_receiver_decl(const std::string &obj);
127 std::vector<Json>
128 find_function_calls(const std::string &func_name, const Json &body);
129 void find_function_calls_recursive(
130 const std::string &func_name,
131 const Json &node,
132 std::vector<Json> &calls);
133 void get_global_elements(const Json &node);
134
135 // ----- type inspectors -----
136 std::string get_current_func_name();
137 bool is_rebound_in_scope(const std::string &var_name);
138 unsigned arg_type_depth_ = 0;
139 std::string get_type_from_constant(const Json &element);
140 std::string get_type_from_lhs(const std::string &id, const Json &body);
141 std::string get_list_subtype(const Json &list);
142 std::string get_list_type_from_literal(const Json &list_arg);
143 std::string subscript_annotation_type(const Json &annotation);
144 std::string
145 container_literal_type(const std::string &base_type, const Json &value);
146 std::string list_concat_type(const Json &binop);
147 // Recover a `list[T]` type for a name bound to an *empty* list literal by
148 // inspecting the first `<var_name>.append(arg)` in the enclosing scope.
149 // Comprehensions lower to `tmp = []; tmp.append(elt)`, so the element type
150 // is lost through the empty literal; without recovery a parameter bound to
151 // the list is typed bare `list` and element reads are mis-modelled as nested
152 // lists (issue #5022). Returns "" when no append is found or the element
153 // type does not resolve concretely (then the caller keeps the bare list).
154 std::string recover_list_type_from_appends(const std::string &var_name);
155 std::string bare_list_subscript_type(
156 const Json &var_node,
157 const std::string &var_name,
158 bool is_slice);
159 std::string homogeneous_literal_subscript_type(
160 const Json &var_node,
161 const std::string &var_name,
162 bool is_slice);
163 std::string
164 refine_any_from_value(const std::string &annotated, const Json &var_node);
165 std::string get_object_name(const Json &call, const std::string &prefix);
166 std::string get_string_method_return_type(const std::string &method) const;
167 bool extract_type_info(
168 const Json &annotation,
169 std::string &base_type,
170 std::string &element_type);
171 std::string infer_dict_value_type(const Json &var_node);
172 std::string resolve_subscript_type(
173 const Json &subscript_node,
174 const Json &body,
175 bool nested = false);
176
177 // The type of `f(...)[i]` read from f's `list[T]` return annotation, or "Any"
178 // when the subscript's base is not such a call.
179 std::string subscript_type_from_call(const Json &subscript_node);
180
181 // The type every element of the literal in `[a, b, ...][i]` shares, or "Any".
182 std::string subscript_type_from_list_literal(const Json &subscript_node);
183 bool
184 is_imported_name_in_body(const std::string &name, const Json &stmts) const;
185 bool is_imported_name(const std::string &name) const;
186 std::string infer_unpacked_element_type(const Json &rhs, size_t index);
187 std::string resolve_object_class_name(const std::string &obj);
188 std::string match_literal_argument(
189 const Json &call_node,
190 std::vector<Json> overloads) const;
191 std::string resolve_overload_return_type(
192 const std::string &func_name,
193 const Json &call_node) const;
194
195 // ----- core inference dispatchers -----
196 std::string get_argument_type(const Json &arg);
197 std::string get_dict_literal_type(const Json &dict_value);
198 std::string get_call_type(const Json &arg);
199 std::string get_attribute_type(const Json &arg);
200 std::string class_attribute_type(
201 const std::string &cls,
202 const std::string &attr,
203 bool constructed_only = false);
204 std::string self_attribute_type(const Json &node);
205 std::string get_operand_type(const Json &node);
206 std::string get_builtin_call_type(const Json &call);
207 // Type of a builtin call whose result depends on its argument (abs, hash),
208 // or empty for any other call.
209 std::string arg_dependent_call_type(const Json &call);
210 std::string list_copy_type(const std::string &func_name, const Json &call);
211 std::string dict_view_list_type(const Json &call);
212 std::string get_type_from_binary_expr(const Json &stmt, const Json &body);
213 bool is_array_operand(const Json &operand, const Json &body);
214 std::string operand_annotation_type(const Json &decl);
215 std::string get_type_from_rhs_variable(const Json &element, const Json &body);
216 bool holds_callable_value(const std::string &name) const;
217 std::string get_type_from_call(const Json &element);
218 std::optional<std::string> get_type_from_literal_method(const Json &call);
219 std::string get_type_from_method(const Json &call);
220 std::string get_type_from_call_or_random(const Json &element);
221 InferResult infer_type_from_ifexp(
222 const Json &ifexp_node,
223 const Json &body,
224 std::string &inferred_type);
225 std::optional<std::pair<Json, std::string>>
226 function_signature(const function_values::candidate &function);
227 std::string
228 function_value_return_type(const Json &value, const Json *call = nullptr);
230 infer_type(const Json &stmt, const Json &body, std::string &inferred_type);
231 // True for a `np.<ctor>(...)` call node (module or aliased), where <ctor>
232 // is one of the array-shape constructors whose numpy.py signature is a
233 // generic `list[float]` the converter later replaces with a concrete
234 // array type. Scopes infer_type's Attribute-Call branch decline to
235 // exactly the calls that can hit the array_return_local_* gap.
236 bool is_numpy_array_ctor_call(const Json &call_value) const;
237 // True when the function currently being annotated (current_func) returns
238 // `name` directly as its own trailing statement -- `return name`, or
239 // identically through both arms of a trailing if/else. Mirrors
240 // get_function_definition's local_var_numpy_array_return on the converter
241 // side, at the granularity infer_type's decline needs (it only has to
242 // know whether the name being assigned right now is the one at risk, not
243 // reconstruct the whole pattern).
244 bool current_func_returns_name_directly(const std::string &name) const;
245 std::string
246 get_function_return_type(const std::string &func_name, const Json &ast);
247 std::string infer_lambda_return_type(const Json &lambda_elem) const;
248 // Sets *divergent when branches return incompatible types.
249 std::string infer_from_return_statements(
250 const Json &body,
251 const std::string &func_name,
252 bool *divergent = nullptr);
253 // The type a parameter's default value implies, or empty.
254 std::string infer_type_from_parameter_default(
255 const Json &function_element,
256 size_t param_index,
257 size_t param_count);
258 // Return type of a FunctionDef @p member: its declared `-> T` (or `->
259 // T[...]`) annotation, else the type inferred from its return statements,
260 // else "Any".
261 std::string
262 method_return_type(const Json &member, const std::string &class_name);
263 // Type inferred from the return statements of @p member, a method of
264 // @p class_name, resolving names in the method's own scope, or empty.
265 std::string
266 infer_method_return_type(const Json &member, const std::string &class_name);
267 void collect_return_types(
268 const Json &body,
269 const std::string &func_name,
270 std::set<std::string> &types);
271 // True if @p node (a return-value expression subtree) contains a direct call
272 // to @p func_name. Used to skip self-recursive returns during return-type
273 // inference so they don't re-enter the analysis (stack overflow).
274 static bool
275 expr_calls_function(const Json &node, const std::string &func_name);
276 // True if the arithmetic expression @p node has an attribute read among its
277 // operands, at any depth.
278 static bool arithmetic_reads_attribute(const Json &node);
279
280 // ----- annotation-node constructors (annotation_expr.inl) -----
281 Json create_name_annotation(
282 const std::string &type_id,
283 int lineno,
284 int col_offset,
285 int end_lineno,
286 int end_col_offset);
287 Json create_subscript_annotation(
288 const std::string &base_type,
289 const std::string &element_type,
290 int lineno,
291 int col_offset,
292 int end_lineno);
293 // Build a `tuple[t0, t1, ...]` Subscript annotation node. Used by the
294 // parameter-inference pass to specialise bare `tuple` annotations once
295 // the element types have been recovered from call sites (GitHub #4515).
296 Json create_tuple_subscript_annotation(
297 const std::vector<std::string> &elem_types,
298 int lineno,
299 int col_offset,
300 int end_lineno);
301 Json create_annotation_from_type(
302 const std::string &inferred_type,
303 int lineno,
304 int col_offset,
305 int end_lineno);
306 void update_assignment_node(Json &element, const std::string &inferred_type);
307 void add_parameter_annotation(Json &param, const std::string &type);
308 void update_end_col_offset(Json &ast);
309
310 // ----- class inheritance and specialisation (annotation_symbolic.inl) -----
311 bool are_all_user_classes(const std::vector<std::string> &types) const;
312 bool are_specializable_types(const std::vector<std::string> &types) const;
313 bool is_specialization_pending(
314 const std::string &func_name,
315 size_t param_index,
316 const std::vector<std::string> &types) const;
317 std::string build_specialized_function_name(
318 const std::string &base_name,
319 const std::string &class_name) const;
320 std::string
321 resolve_name_assigned_class(const std::string &name, const Json &node);
322 void rewrite_specialized_calls(
323 const std::string &original_name,
324 size_t param_index,
325 const std::unordered_map<std::string, std::string> &specialized_names,
326 Json &node);
327 void apply_pending_specializations();
328 // Returns the ancestors of a class (including itself) in BFS order via
329 // "bases". Handles multiple inheritance and cycle-safe (visited guard).
330 // "object" is intentionally excluded — it has no ESBMC IR representation.
331 std::vector<std::string> get_class_ancestors(const std::string &class_name);
332 // Returns the lowest common ancestor of two user-defined class types,
333 // or "" when none exists.
334 std::string
335 find_common_ancestor(const std::string &type_a, const std::string &type_b);
336
337 // ----- per-class subscript-key tuple inference -----
338 std::vector<std::string>
339 infer_subscript_key_tuple_types(const std::string &class_name);
340 void collect_class_instance_map(
341 const Json &ast,
342 std::unordered_map<std::string, std::string> &out) const;
343 template <typename Visitor>
344 void visit_class_tuple_subscripts(
345 const Json &node,
346 const std::string &class_name,
347 const std::unordered_map<std::string, std::string> &var_to_class,
348 Visitor &&visit,
349 const Json *enclosing_func = nullptr);
350 std::string resolve_name_in_enclosing_func(
351 const std::string &name,
352 const Json &enclosing_func);
353 std::string resolve_wildcard_import_func(const std::string &func_name);
358 bool reads_import(const std::string &name, const Json &use) const;
359 std::string
360 resolve_expr_in_enclosing_func(const Json &elt, const Json &enclosing_func);
361 std::vector<std::string> infer_tuple_element_types(
362 const Json &elts,
363 const Json *enclosing_func = nullptr);
364
365 // ----- function-call collection and parameter-type inference -----
366 std::vector<std::string> collect_parameter_types_from_calls(
367 size_t param_index,
368 const std::vector<Json> &function_calls);
369 std::vector<std::string> infer_tuple_param_types_from_calls(
370 size_t param_index,
371 const std::vector<Json> &function_calls);
372 std::string infer_parameter_type_from_calls(
373 size_t param_index,
374 const std::vector<Json> &function_calls);
375 struct caller_scopet
376 {
377 std::string root;
378 std::string ctx;
379 std::string class_prefix;
380 bool in_lambda = false;
381 };
382 std::string caller_arg_type(const Json &arg, const caller_scopet &scope);
383 void collect_caller_arg_types(
384 const std::string &callee,
385 size_t index,
386 const Json &node,
387 const caller_scopet &scope,
388 std::vector<std::string> &types);
389 std::vector<std::string> caller_arg_types(
390 const std::string &func_name,
391 size_t index,
392 const Json &search_context);
393 std::string infer_parameter_type_at_call_sites(
394 const std::string &func_name,
395 size_t param_index,
396 const std::vector<Json> &function_calls,
397 const Json &search_context);
398 bool specializable_at_call_sites(
399 const std::string &func_name,
400 size_t param_index,
401 std::vector<std::string> &call_types,
402 const Json &search_context);
403 void infer_parameter_types(Json &function_element);
404
405 // ----- top-level orchestration entry points -----
406 void annotate_global_scope();
407 void annotate_function(Json &function_element);
408 void annotate_class(Json &class_element);
409 void infer_loop_variable_types(Json &while_stmt, bool synthesized_only);
410 Json list_element_annotation(const Json &iter_node);
411 Json self_attr_list_element(const std::string &attr);
412 void annotate_branch_blocks(Json &element);
413 void add_annotation(Json &body);
414 std::string widen_to_optional_scalar(
415 const Json &element,
416 const std::string &inferred_type);
417 std::string optional_scalar_of(const std::string &name, const Json &scope);
418 std::string returned_optional_scalar(const Json &function, const Json &body);
419 void collect_name_stores(
420 const std::string &name,
421 const Json &body,
422 std::vector<const Json *> &stores);
423
424 // ----- state -----
425 Json &ast_;
426 global_scope &gs_;
427 std::shared_ptr<module_manager> module_manager_;
428 Json *current_func;
429 Json
430 *parent_func; // Track parent function for nested function scope resolution
431 int current_line_;
432 std::string python_filename_;
433 bool filter_global_elements_ = false;
434 // True only while add_type_annotation(func_name) (the --function
435 // entry-point pass over the *main* module) is running, so add_annotation's
436 // bare-call forward-reference scan is scoped to that module. The scan
437 // searches only the current ast_, so applying it while annotating an
438 // *imported* module (e.g. an OM calling another OM's helper) would throw
439 // on any name that module itself imports (github #5937).
440 bool annotating_function_entry_point_ = false;
441 std::vector<Json> referenced_global_elements;
442 std::set<std::string> functions_in_analysis_;
443 // Functions whose recursive-branch inference is itself running. A second
444 // re-entry means the self-reference is indirect (the returned name's binding
445 // calls the function), which the return-expression scan cannot see.
446 std::set<std::string> functions_reentered_;
447 std::set<std::string> resolving_rhs_vars_;
448 // Per (scope, name): the Optional[T] annotation of a name stored None and
449 // one scalar type T, decided before any of its stores is annotated.
450 std::map<std::pair<const Json *, std::string>, std::string>
451 optional_scalar_names_;
452 std::string current_func_name_context_;
453 std::string current_class_name_;
454 struct pending_specializationt
455 {
456 std::string function_name;
457 size_t param_index;
458 std::vector<std::string> class_types;
459 };
460 std::vector<pending_specializationt> pending_specializations_;
461 void retype_builtin_copy(
462 Json &fn,
463 const pending_specializationt &spec,
464 const std::string &type,
465 const std::vector<Json> &calls);
466 std::vector<const Json *> extra_subscript_inference_asts_;
467};
468
469// Out-of-class template member definitions. These files are part of
470// this header and must not be included independently — the
471// definitions need the `python_annotation<Json>` class layout above.
void base_type(type2tc &type, const namespacet &ns)
Definition base_type.cpp:6
Definition global_scope.h:7
static std::shared_ptr< module_manager > create(const std::string &module_search_path, const std::string &main_module_path)
Definition module_manager.cpp:343
void collect_function_call_arg_types(Json &node, std::map< std::pair< std::string, size_t >, std::set< std::string > > &param_types, const std::set< std::string > &top_level_funcs, std::set< std::pair< std::string, size_t > > &dynamically_typed_params)
Definition annotation_conversion.inl:5836
bool is_instance_call(const Json &func) const
Whether func names neither a module class nor a module function, so a call through it can only reach ...
Definition annotation_conversion.inl:5657
void add_extra_subscript_inference_source(const Json &ast)
Definition annotation_conversion.inl:5979
void preprocess_method_calls(const Json &node)
Definition annotation_conversion.inl:5571
void add_type_annotation()
Definition annotation_conversion.inl:5931
Json * class_method(const std::string &cls, const Json &method)
The FunctionDef of method in module-level class cls, or null.
Definition annotation_conversion.inl:5644
std::string promote_to_float_operand(const Json &stmt, const std::string &type)
True division of an int or bool yields a float, and so does other arithmetic with a float operand,...
Definition annotation_conversion.inl:1931
bool annotate_from_call_sites(Json &root, Json &param, const std::pair< std::string, size_t > &key, std::set< std::string > types, const std::set< std::string > &top_level_funcs)
Annotates param when its call sites agree on one type.
Definition annotation_conversion.inl:5810
bool preprocess_constructor_calls(Json &node)
Definition annotation_conversion.inl:5483
static const std::string & unresolved_arg_type()
Sentinel recorded for a call-site argument this pass cannot type; never a valid Python type name,...
Definition python_annotation.h:77
bool preprocess_function_calls(Json &root)
Infer unannotated parameter types of top-level functions from the argument types at every plain f(arg...
Definition annotation_conversion.inl:5753
python_annotation(Json &ast, global_scope &gs)
Definition python_annotation.h:56
void preprocess_receiver_method_calls()
Types the unannotated parameters of a method from its obj.m(args) calls at module level,...
Definition annotation_conversion.inl:5671
Definition toml.hpp:1531
@ function
Definition converter_compare.cpp:885
const std::map< std::string, std::string > & builtin_functions()
Definition annotation_intrinsics.cpp:5
nlohmann::json find_function_recursive(const std::string &func_name, const nlohmann::json &body)
Definition annotation_parser.cpp:26
nlohmann::json find_lambda_in_body(const std::string &func_name, const nlohmann::json &body)
Definition annotation_parser.cpp:10
std::string get_base_var_name(const nlohmann::json &node)
Definition annotation_utils.cpp:63
bool has_return_none(const nlohmann::json &body)
Definition annotation_utils.cpp:36
std::string get_type_from_json(const nlohmann::json &value)
Definition annotation_utils.cpp:9
std::string invert_substrings(const std::string &input)
Definition annotation_utils.cpp:78
bool has_annotation(const nlohmann::json &node)
Definition annotation_utils.cpp:30
std::string infer_type_from_default_arg_shape(const nlohmann::json &args_node)
Definition annotation_utils.cpp:103
InferResult
Definition annotation_utils.h:19
static std::string node_name(const nlohmann::json &node)
Definition expr.cpp:520
uint256_t key
Definition solidity_mapping.c:1
unsigned stack_budget_guardt< Tag >::depth
Definition stack_budget.h:72
A function definition and the module AST it belongs to.
Definition function_values.h:22
Definition module.h:12