ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
annotation_expr.inl
Go to the documentation of this file.
1// Out-of-class member definitions for `python_annotation<Json>` that
2// construct annotation JSON nodes: Name / Subscript / Tuple-subscript
3// annotations and the `update_assignment_node` /
4// `add_parameter_annotation` / `update_end_col_offset` mutators.
5//
6// This file is `#include`d from python_annotation.h after the class
7// definition. It must not be included elsewhere — the definitions
8// depend on the class layout declared in python_annotation.h.
9//
10// Bodies are verbatim from the previous in-class definitions; only
11// the `template <class Json>` line and the `python_annotation<Json>::`
12// qualifier are new. No semantic change.
13
14#pragma once
15
16template <class Json>
17Json python_annotation<Json>::create_name_annotation(
18 const std::string &type_id,
19 int lineno,
20 int col_offset,
21 int end_lineno,
22 int end_col_offset)
23{
24 return {
25 {"_type", "Name"},
26 {"id", type_id},
27 {"ctx", {{"_type", "Load"}}},
28 {"lineno", lineno},
29 {"col_offset", col_offset},
30 {"end_lineno", end_lineno},
31 {"end_col_offset", end_col_offset}};
32}
33
34template <class Json>
35Json python_annotation<Json>::create_subscript_annotation(
36 const std::string &base_type,
37 const std::string &element_type,
38 int lineno,
39 int col_offset,
40 int end_lineno)
41{
42 int base_end_col = col_offset + base_type.size();
43 int slice_col = base_end_col + 1; // After '['
44 int slice_end_col = slice_col + element_type.size();
45 int total_end_col = col_offset + base_type.size() + 1 + element_type.size() +
46 1; // type[element]
47
48 // dict[K, V] and tuple[T0, ..., Tn] carry their parameters in a Tuple
49 // slice. Split on top-level commas only, so a parameter that is itself a
50 // generic (dict[str, list[int]], tuple[int, dict[str, int]]) stays intact.
51 const std::vector<std::string> params =
53 if (params.size() > 1 && (base_type == "dict" || base_type == "tuple"))
54 {
55 // A parameter that is itself a parameterized generic must be built
56 // recursively into a nested Subscript rather than a flat Name — see the
57 // list/set note below (#4830).
58 auto build_elem = [&](const std::string &t, int col, int end_col) -> Json {
59 return t.find('[') != std::string::npos
60 ? create_annotation_from_type(t, lineno, col, end_lineno)
61 : create_name_annotation(t, lineno, col, end_lineno, end_col);
62 };
63
64 Json elts = Json::array();
65 int cur = slice_col;
66 for (const std::string &t : params)
67 {
68 const int end_col = cur + static_cast<int>(t.size());
69 elts.push_back(build_elem(t, cur, end_col));
70 cur = end_col + 2; // ", "
71 }
72
73 Json tuple_slice = {
74 {"_type", "Tuple"},
75 {"elts", std::move(elts)},
76 {"ctx", {{"_type", "Load"}}},
77 {"lineno", lineno},
78 {"col_offset", slice_col},
79 {"end_lineno", end_lineno},
80 {"end_col_offset", slice_end_col}};
81
82 return {
83 {"_type", "Subscript"},
84 {"value",
85 create_name_annotation(
86 base_type, lineno, col_offset, end_lineno, base_end_col)},
87 {"slice", tuple_slice},
88 {"ctx", {{"_type", "Load"}}},
89 {"lineno", lineno},
90 {"col_offset", col_offset},
91 {"end_lineno", end_lineno},
92 {"end_col_offset", total_end_col}};
93 }
94
95 // Default: single element type (for list[T], set[T], etc.).
96 // When the element type is itself a parameterized generic (e.g.
97 // list[list[int]] -> element "list[int]"), build the slice recursively so
98 // the result is a properly nested Subscript rather than a flat Name whose
99 // id literally contains brackets. Without this, downstream element-type
100 // resolution in the converter sees a malformed annotation and falls back
101 // to Any for the inner element, crashing on later arithmetic (#4830).
102 Json slice_node =
103 element_type.find('[') != std::string::npos
104 ? create_annotation_from_type(element_type, lineno, slice_col, end_lineno)
105 : create_name_annotation(
106 element_type, lineno, slice_col, end_lineno, slice_end_col);
107
108 return {
109 {"_type", "Subscript"},
110 {"value",
111 create_name_annotation(
112 base_type, lineno, col_offset, end_lineno, base_end_col)},
113 {"slice", slice_node},
114 {"ctx", {{"_type", "Load"}}},
115 {"lineno", lineno},
116 {"col_offset", col_offset},
117 {"end_lineno", end_lineno},
118 {"end_col_offset", total_end_col}};
119}
120
121// Build a `tuple[t0, t1, ...]` Subscript annotation node. Used by the
122// parameter-inference pass to specialise bare `tuple` annotations once
123// the element types have been recovered from call sites (GitHub #4515).
124template <class Json>
125Json python_annotation<Json>::create_tuple_subscript_annotation(
126 const std::vector<std::string> &elem_types,
127 int lineno,
128 int col_offset,
129 int end_lineno)
130{
131 static const std::string base_type = "tuple";
132 int base_end_col = col_offset + base_type.size();
133 int slice_col = base_end_col + 1;
134
135 Json elts = Json::array();
136 int cur = slice_col;
137 for (const std::string &t : elem_types)
138 {
139 int end_col = cur + t.size();
140 elts.push_back(create_name_annotation(t, lineno, cur, end_lineno, end_col));
141 cur = end_col + 2; // ", "
142 }
143 int slice_end_col = cur - 2;
144 int total_end_col = slice_end_col + 1;
145
146 Json slice = {
147 {"_type", "Tuple"},
148 {"elts", elts},
149 {"ctx", {{"_type", "Load"}}},
150 {"lineno", lineno},
151 {"col_offset", slice_col},
152 {"end_lineno", end_lineno},
153 {"end_col_offset", slice_end_col}};
154
155 return {
156 {"_type", "Subscript"},
157 {"value",
158 create_name_annotation(
159 base_type, lineno, col_offset, end_lineno, base_end_col)},
160 {"slice", slice},
161 {"ctx", {{"_type", "Load"}}},
162 {"lineno", lineno},
163 {"col_offset", col_offset},
164 {"end_lineno", end_lineno},
165 {"end_col_offset", total_end_col}};
166}
167
168template <class Json>
169Json python_annotation<Json>::create_annotation_from_type(
170 const std::string &inferred_type,
171 int lineno,
172 int col_offset,
173 int end_lineno)
174{
175 // Check if this is a generic type (e.g., list[dict])
176 size_t bracket_pos = inferred_type.find('[');
177
178 if (bracket_pos != std::string::npos)
179 {
180 // Generic type: extract base and element types
181 std::string base_type = inferred_type.substr(0, bracket_pos);
182 std::string element_type = inferred_type.substr(
183 bracket_pos + 1, inferred_type.length() - bracket_pos - 2);
184
185 return create_subscript_annotation(
186 base_type, element_type, lineno, col_offset, end_lineno);
187 }
188 else
189 {
190 // Simple type
191 int end_col_offset = col_offset + inferred_type.size();
192 return create_name_annotation(
193 inferred_type, lineno, col_offset, end_lineno, end_col_offset);
194 }
195}
196
197template <class Json>
198void python_annotation<Json>::update_assignment_node(
199 Json &element,
200 const std::string &inferred_type)
201{
202 auto target = element["targets"][0];
203 std::string id;
204
205 // Determine the ID based on the target type
206 if (target.contains("id"))
207 id = target["id"];
208 // Get LHS from members access on assignments. e.g.: x.data = 10
209 else if (target["_type"] == "Attribute")
210 {
211 // Only single-level obj.attr = ... is annotatable here. Nested writes
212 // like obj.a.b = ... mutate an already-declared field and need no
213 // inferred annotation; the inner value is an Attribute (no "id"),
214 // not a Name, so reading "id" would throw json::type_error.
215 if (!target["value"].is_object() || !target["value"].contains("id"))
216 return;
217 id = target["value"]["id"].template get<std::string>() + "." +
218 target["attr"].template get<std::string>();
219 }
220 else if (target.contains("slice"))
221 return; // No need to annotate subscript assignments (e.g. d["k"] = v).
222
223 // Update type field only after confirming this is an annotatable target.
224 element["_type"] = "AnnAssign";
225 // Mark as inferred to distinguish from explicit
226 // annotations like `x: Any = ...` during assignment type handling.
227 element["_inferred_annotation"] = true;
228
229 assert(!id.empty());
230
231 // Calculate column offset with null safety
232 int target_col_offset =
233 target.contains("col_offset") && !target["col_offset"].is_null()
234 ? target["col_offset"].template get<int>()
235 : 0;
236 int col_offset = target_col_offset + id.size() + 1;
237
238 // Get line number with null safety
239 int target_lineno = target.contains("lineno") && !target["lineno"].is_null()
240 ? target["lineno"].template get<int>()
241 : current_line_;
242
243 int target_end_lineno =
244 target.contains("end_lineno") && !target["end_lineno"].is_null()
245 ? target["end_lineno"].template get<int>()
246 : target_lineno;
247
248 element["annotation"] = create_annotation_from_type(
249 inferred_type, target_lineno, col_offset, target_end_lineno);
250
251 // Update element properties with null safety
252 int element_end_col_offset =
253 element.contains("end_col_offset") && !element["end_col_offset"].is_null()
254 ? element["end_col_offset"].template get<int>()
255 : col_offset + inferred_type.size();
256
257 element["end_col_offset"] = element_end_col_offset + inferred_type.size() + 1;
258 element["end_lineno"] = target_lineno;
259 element["simple"] = 1;
260
261 // Replace "targets" array with "target" object
262 element["target"] = std::move(target);
263 element.erase("targets");
264
265 // Remove unnecessary field
266 element.erase("type_comment");
267
268 // Update value fields with the correct offsets - with null safety
269 auto update_offsets = [&inferred_type](Json &value) {
270 if (value.contains("col_offset") && !value["col_offset"].is_null())
271 {
272 value["col_offset"] =
273 value["col_offset"].template get<int>() + inferred_type.size() + 1;
274 }
275 if (value.contains("end_col_offset") && !value["end_col_offset"].is_null())
276 {
277 value["end_col_offset"] =
278 value["end_col_offset"].template get<int>() + inferred_type.size() + 1;
279 }
280 };
281
282 update_offsets(element["value"]);
283
284 // Adjust column offsets for function calls with arguments
285 if (element["value"].contains("args"))
286 for (auto &arg : element["value"]["args"])
287 update_offsets(arg);
288
289 // Adjust column offsets in function call node
290 if (element["value"].contains("func"))
291 update_offsets(element["value"]["func"]);
292}
293
294template <class Json>
295void python_annotation<Json>::add_parameter_annotation(
296 Json &param,
297 const std::string &type)
298{
299 int col_offset = param["col_offset"].template get<int>() +
300 param["arg"].template get<std::string>().length() + 1;
301
302 param["annotation"] = create_annotation_from_type(
303 type,
304 param["lineno"].template get<int>(),
305 col_offset,
306 param["lineno"].template get<int>());
307
308 // Update the parameter's end_col_offset to account for the annotation
309 param["end_col_offset"] =
310 param["end_col_offset"].template get<int>() + type.size() + 1;
311}
312
313template <class Json>
314void python_annotation<Json>::update_end_col_offset(Json &ast)
315{
316 int max_col_offset = ast["end_col_offset"];
317 for (auto &elem : ast["body"])
318 {
319 if (elem["end_col_offset"] > max_col_offset)
320 max_col_offset = elem["end_col_offset"];
321 }
322 ast["end_col_offset"] = max_col_offset;
323}
void base_type(type2tc &type, const namespacet &ns)
Definition base_type.cpp:6
Definition toml.hpp:1531
std::vector< std::string > split_top_level_params(const std::string &s)
Split a rendered parameter list on commas that are not inside brackets, trimming each part.
Definition json_utils.h:982
list[float] empty(int shape)
Definition numpy.py:29
lineno
Definition threading_lowering.py:600
type
Definition transform.py:9