1use crate::schema::{
2 normalize_literal_string, DatasetSchema, DatasetSchemaProvider, FsDatasetSchemaProvider,
3};
4use runmat_hir::{
5 HirClassMember, HirDiagnostic, HirDiagnosticSeverity, HirExpr, HirExprKind, HirLValue, HirStmt,
6 LoweringResult, VarId,
7};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Clone)]
11struct DatasetBinding {
12 arrays: HashMap<String, usize>,
13}
14
15#[derive(Clone)]
16struct ArrayBinding {
17 array_name: String,
18 rank: Option<usize>,
19}
20
21pub fn lint_data_api(result: &LoweringResult) -> Vec<HirDiagnostic> {
22 let provider = FsDatasetSchemaProvider;
23 lint_data_api_with_provider(result, &provider)
24}
25
26pub fn lint_data_api_with_provider(
27 result: &LoweringResult,
28 provider: &dyn DatasetSchemaProvider,
29) -> Vec<HirDiagnostic> {
30 let mut diags = Vec::new();
31 let mut non_tx_write_count = 0usize;
32 let mut tx_vars = HashSet::<VarId>::new();
33 collect_tx_bindings_from_stmts(&result.hir.body, &mut tx_vars);
34 for stmt in &result.hir.body {
35 walk_stmt_general(stmt, &mut diags, &mut non_tx_write_count, &tx_vars);
36 }
37
38 let mut datasets = HashMap::<VarId, DatasetBinding>::new();
39 let mut arrays = HashMap::<VarId, ArrayBinding>::new();
40 for stmt in &result.hir.body {
41 analyze_stmt_bindings(
42 stmt,
43 result,
44 provider,
45 &mut datasets,
46 &mut arrays,
47 &mut diags,
48 );
49 }
50
51 diags
52}
53
54fn collect_tx_bindings_from_stmts(stmts: &[HirStmt], tx_vars: &mut HashSet<VarId>) {
55 for stmt in stmts {
56 match stmt {
57 HirStmt::Assign(var_id, expr, _, _)
58 | HirStmt::AssignLValue(HirLValue::Var(var_id), expr, _, _) => {
59 if expr_is_begin_call(expr) {
60 tx_vars.insert(*var_id);
61 }
62 }
63 HirStmt::MultiAssign(var_ids, expr, _, _) => {
64 if expr_is_begin_call(expr) {
65 for var_id in var_ids.iter().flatten() {
66 tx_vars.insert(*var_id);
67 }
68 }
69 }
70 HirStmt::If {
71 then_body,
72 elseif_blocks,
73 else_body,
74 ..
75 } => {
76 collect_tx_bindings_from_stmts(then_body, tx_vars);
77 for (_, body) in elseif_blocks {
78 collect_tx_bindings_from_stmts(body, tx_vars);
79 }
80 if let Some(body) = else_body {
81 collect_tx_bindings_from_stmts(body, tx_vars);
82 }
83 }
84 HirStmt::While { body, .. }
85 | HirStmt::For { body, .. }
86 | HirStmt::Function { body, .. } => {
87 collect_tx_bindings_from_stmts(body, tx_vars);
88 }
89 HirStmt::Switch {
90 cases, otherwise, ..
91 } => {
92 for (_, body) in cases {
93 collect_tx_bindings_from_stmts(body, tx_vars);
94 }
95 if let Some(body) = otherwise {
96 collect_tx_bindings_from_stmts(body, tx_vars);
97 }
98 }
99 HirStmt::TryCatch {
100 try_body,
101 catch_body,
102 ..
103 } => {
104 collect_tx_bindings_from_stmts(try_body, tx_vars);
105 collect_tx_bindings_from_stmts(catch_body, tx_vars);
106 }
107 HirStmt::ClassDef { members, .. } => {
108 for member in members {
109 if let HirClassMember::Methods { body, .. } = member {
110 collect_tx_bindings_from_stmts(body, tx_vars);
111 }
112 }
113 }
114 HirStmt::ExprStmt(_, _, _)
115 | HirStmt::Break(_)
116 | HirStmt::Continue(_)
117 | HirStmt::Return(_)
118 | HirStmt::Global(_, _)
119 | HirStmt::Persistent(_, _)
120 | HirStmt::Import { .. }
121 | HirStmt::AssignLValue(_, _, _, _) => {}
122 }
123 }
124}
125
126fn expr_is_begin_call(expr: &HirExpr) -> bool {
127 match &expr.kind {
128 HirExprKind::MethodCall(_, method, _) | HirExprKind::DottedInvoke(_, method, _) => {
129 method == "begin"
130 }
131 HirExprKind::FuncCall(name, _) => name == "Dataset.begin",
132 _ => false,
133 }
134}
135
136fn literal_string(expr: &HirExpr) -> Option<String> {
137 match &expr.kind {
138 HirExprKind::String(s) => Some(normalize_literal_string(s)),
139 _ => None,
140 }
141}
142
143fn slice_rank(expr: &HirExpr) -> Option<usize> {
144 match &expr.kind {
145 HirExprKind::Cell(rows) => Some(rows.iter().map(|row| row.len()).sum()),
146 HirExprKind::Colon => Some(1),
147 _ => None,
148 }
149}
150
151fn infer_dataset_binding(
152 provider: &dyn DatasetSchemaProvider,
153 path: &str,
154) -> Option<DatasetBinding> {
155 let schema: DatasetSchema = provider.load_schema(path)?;
156 Some(DatasetBinding {
157 arrays: schema.arrays,
158 })
159}
160
161fn infer_binding_from_expr(
162 expr: &HirExpr,
163 datasets: &HashMap<VarId, DatasetBinding>,
164 provider: &dyn DatasetSchemaProvider,
165) -> (Option<DatasetBinding>, Option<ArrayBinding>) {
166 match &expr.kind {
167 HirExprKind::FuncCall(name, args) if name == "data.open" => {
168 if let Some(path_expr) = args.first() {
169 if let Some(path) = literal_string(path_expr) {
170 return (infer_dataset_binding(provider, &path), None);
171 }
172 }
173 (None, None)
174 }
175 HirExprKind::MethodCall(base, method, args)
176 | HirExprKind::DottedInvoke(base, method, args)
177 if method == "array" =>
178 {
179 if let HirExprKind::Var(ds_var) = base.kind {
180 if let Some(dataset) = datasets.get(&ds_var) {
181 if let Some(name_expr) = args.first() {
182 if let Some(name) = literal_string(name_expr) {
183 let rank = dataset.arrays.get(&name).copied();
184 return (
185 None,
186 Some(ArrayBinding {
187 array_name: name,
188 rank,
189 }),
190 );
191 }
192 }
193 }
194 }
195 (None, None)
196 }
197 HirExprKind::FuncCall(name, args) if name == "Dataset.array" => {
198 if let Some(base) = args.first() {
199 if let HirExprKind::Var(ds_var) = base.kind {
200 if let Some(dataset) = datasets.get(&ds_var) {
201 if let Some(name_expr) = args.get(1) {
202 if let Some(name) = literal_string(name_expr) {
203 let rank = dataset.arrays.get(&name).copied();
204 return (
205 None,
206 Some(ArrayBinding {
207 array_name: name,
208 rank,
209 }),
210 );
211 }
212 }
213 }
214 }
215 }
216 (None, None)
217 }
218 _ => (None, None),
219 }
220}
221
222fn analyze_data_expr(
223 expr: &HirExpr,
224 result: &LoweringResult,
225 datasets: &HashMap<VarId, DatasetBinding>,
226 arrays: &HashMap<VarId, ArrayBinding>,
227 diags: &mut Vec<HirDiagnostic>,
228) {
229 match &expr.kind {
230 HirExprKind::MethodCall(base, method, args)
231 | HirExprKind::DottedInvoke(base, method, args) => {
232 if method == "array" {
233 if let HirExprKind::Var(ds_var) = base.kind {
234 if let Some(dataset) = datasets.get(&ds_var) {
235 if let Some(name_expr) = args.first() {
236 if let Some(name) = literal_string(name_expr) {
237 if !dataset.arrays.contains_key(&name) {
238 diags.push(HirDiagnostic {
239 message: format!(
240 "array '{name}' is not present in inferred dataset schema"
241 ),
242 span: name_expr.span,
243 code: "lint.data.unknown_array_name",
244 severity: HirDiagnosticSeverity::Warning,
245 });
246 }
247 }
248 }
249 }
250 }
251 }
252
253 if method == "read" || method == "write" {
254 if let HirExprKind::Var(array_var) = base.kind {
255 if let Some(array_binding) = arrays.get(&array_var) {
256 if let Some(rank) = array_binding.rank {
257 let slice_arg = args.first();
258 if let Some(slice_expr) = slice_arg {
259 if let Some(actual_slice_rank) = slice_rank(slice_expr) {
260 if actual_slice_rank > rank {
261 let array_var_name = result
262 .var_names
263 .get(&array_var)
264 .cloned()
265 .unwrap_or_else(|| format!("v{}", array_var.0));
266 diags.push(HirDiagnostic {
267 message: format!(
268 "slice rank {actual_slice_rank} exceeds array rank {rank} for '{array_var_name}' ({})",
269 array_binding.array_name
270 ),
271 span: slice_expr.span,
272 code: "lint.data.invalid_slice_rank",
273 severity: HirDiagnosticSeverity::Warning,
274 });
275 }
276 }
277 }
278 }
279 }
280 }
281 }
282
283 analyze_data_expr(base, result, datasets, arrays, diags);
284 for arg in args {
285 analyze_data_expr(arg, result, datasets, arrays, diags);
286 }
287 }
288 HirExprKind::FuncCall(name, args) => {
289 if name == "Dataset.array" {
290 if let Some(base) = args.first() {
291 if let HirExprKind::Var(ds_var) = base.kind {
292 if let Some(dataset) = datasets.get(&ds_var) {
293 if let Some(name_expr) = args.get(1) {
294 if let Some(array_name) = literal_string(name_expr) {
295 if !dataset.arrays.contains_key(&array_name) {
296 diags.push(HirDiagnostic {
297 message: format!(
298 "array '{array_name}' is not present in inferred dataset schema"
299 ),
300 span: name_expr.span,
301 code: "lint.data.unknown_array_name",
302 severity: HirDiagnosticSeverity::Warning,
303 });
304 }
305 }
306 }
307 }
308 }
309 }
310 }
311
312 if name == "DataArray.read" || name == "DataArray.write" {
313 if let Some(base) = args.first() {
314 if let HirExprKind::Var(array_var) = base.kind {
315 if let Some(array_binding) = arrays.get(&array_var) {
316 if let Some(rank) = array_binding.rank {
317 let slice_arg = args.get(1);
318 if let Some(slice_expr) = slice_arg {
319 if let Some(actual_slice_rank) = slice_rank(slice_expr) {
320 if actual_slice_rank > rank {
321 let array_var_name = result
322 .var_names
323 .get(&array_var)
324 .cloned()
325 .unwrap_or_else(|| format!("v{}", array_var.0));
326 diags.push(HirDiagnostic {
327 message: format!(
328 "slice rank {actual_slice_rank} exceeds array rank {rank} for '{array_var_name}' ({})",
329 array_binding.array_name
330 ),
331 span: slice_expr.span,
332 code: "lint.data.invalid_slice_rank",
333 severity: HirDiagnosticSeverity::Warning,
334 });
335 }
336 }
337 }
338 }
339 }
340 }
341 }
342 }
343
344 for arg in args {
345 analyze_data_expr(arg, result, datasets, arrays, diags);
346 }
347 }
348 HirExprKind::Unary(_, inner) => analyze_data_expr(inner, result, datasets, arrays, diags),
349 HirExprKind::Binary(lhs, _, rhs) => {
350 analyze_data_expr(lhs, result, datasets, arrays, diags);
351 analyze_data_expr(rhs, result, datasets, arrays, diags);
352 }
353 HirExprKind::Tensor(rows) | HirExprKind::Cell(rows) => {
354 for row in rows {
355 for value in row {
356 analyze_data_expr(value, result, datasets, arrays, diags);
357 }
358 }
359 }
360 HirExprKind::Index(base, args) | HirExprKind::IndexCell(base, args) => {
361 analyze_data_expr(base, result, datasets, arrays, diags);
362 for arg in args {
363 analyze_data_expr(arg, result, datasets, arrays, diags);
364 }
365 }
366 HirExprKind::Range(start, step, end) => {
367 analyze_data_expr(start, result, datasets, arrays, diags);
368 if let Some(step) = step {
369 analyze_data_expr(step, result, datasets, arrays, diags);
370 }
371 analyze_data_expr(end, result, datasets, arrays, diags);
372 }
373 HirExprKind::Member(base, _) | HirExprKind::AnonFunc { body: base, .. } => {
374 analyze_data_expr(base, result, datasets, arrays, diags)
375 }
376 HirExprKind::MemberDynamic(base, field) => {
377 analyze_data_expr(base, result, datasets, arrays, diags);
378 analyze_data_expr(field, result, datasets, arrays, diags);
379 }
380 HirExprKind::FuncHandle(_)
381 | HirExprKind::MetaClass(_)
382 | HirExprKind::Var(_)
383 | HirExprKind::String(_)
384 | HirExprKind::Number(_)
385 | HirExprKind::Constant(_)
386 | HirExprKind::Colon
387 | HirExprKind::End => {}
388 }
389}
390
391fn analyze_stmt_bindings(
392 stmt: &HirStmt,
393 result: &LoweringResult,
394 provider: &dyn DatasetSchemaProvider,
395 datasets: &mut HashMap<VarId, DatasetBinding>,
396 arrays: &mut HashMap<VarId, ArrayBinding>,
397 diags: &mut Vec<HirDiagnostic>,
398) {
399 match stmt {
400 HirStmt::ExprStmt(expr, _, _) => analyze_data_expr(expr, result, datasets, arrays, diags),
401 HirStmt::Assign(var, expr, _, _) => {
402 analyze_data_expr(expr, result, datasets, arrays, diags);
403 let (dataset_binding, array_binding) =
404 infer_binding_from_expr(expr, datasets, provider);
405 if let Some(binding) = dataset_binding {
406 datasets.insert(*var, binding);
407 arrays.remove(var);
408 return;
409 }
410 if let Some(binding) = array_binding {
411 arrays.insert(*var, binding);
412 datasets.remove(var);
413 return;
414 }
415 datasets.remove(var);
416 arrays.remove(var);
417 }
418 HirStmt::MultiAssign(_, expr, _, _) => {
419 analyze_data_expr(expr, result, datasets, arrays, diags)
420 }
421 HirStmt::AssignLValue(lv, expr, _, _) => {
422 analyze_data_expr(expr, result, datasets, arrays, diags);
423 match lv {
424 HirLValue::Var(var) => {
425 datasets.remove(var);
426 arrays.remove(var);
427 }
428 HirLValue::Index(base, args) | HirLValue::IndexCell(base, args) => {
429 analyze_data_expr(base, result, datasets, arrays, diags);
430 for arg in args {
431 analyze_data_expr(arg, result, datasets, arrays, diags);
432 }
433 }
434 HirLValue::Member(base, _) => {
435 analyze_data_expr(base, result, datasets, arrays, diags)
436 }
437 HirLValue::MemberDynamic(base, field) => {
438 analyze_data_expr(base, result, datasets, arrays, diags);
439 analyze_data_expr(field, result, datasets, arrays, diags);
440 }
441 }
442 }
443 HirStmt::If {
444 cond,
445 then_body,
446 elseif_blocks,
447 else_body,
448 ..
449 } => {
450 analyze_data_expr(cond, result, datasets, arrays, diags);
451 for nested in then_body {
452 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
453 }
454 for (cond, body) in elseif_blocks {
455 analyze_data_expr(cond, result, datasets, arrays, diags);
456 for nested in body {
457 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
458 }
459 }
460 if let Some(body) = else_body {
461 for nested in body {
462 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
463 }
464 }
465 }
466 HirStmt::While { cond, body, .. } => {
467 analyze_data_expr(cond, result, datasets, arrays, diags);
468 for nested in body {
469 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
470 }
471 }
472 HirStmt::For { expr, body, .. } => {
473 analyze_data_expr(expr, result, datasets, arrays, diags);
474 for nested in body {
475 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
476 }
477 }
478 HirStmt::Switch {
479 expr,
480 cases,
481 otherwise,
482 ..
483 } => {
484 analyze_data_expr(expr, result, datasets, arrays, diags);
485 for (case_expr, body) in cases {
486 analyze_data_expr(case_expr, result, datasets, arrays, diags);
487 for nested in body {
488 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
489 }
490 }
491 if let Some(body) = otherwise {
492 for nested in body {
493 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
494 }
495 }
496 }
497 HirStmt::TryCatch {
498 try_body,
499 catch_body,
500 ..
501 } => {
502 for nested in try_body {
503 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
504 }
505 for nested in catch_body {
506 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
507 }
508 }
509 HirStmt::Function { body, .. } => {
510 for nested in body {
511 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
512 }
513 }
514 HirStmt::ClassDef { members, .. } => {
515 for member in members {
516 if let HirClassMember::Methods { body, .. } = member {
517 for nested in body {
518 analyze_stmt_bindings(nested, result, provider, datasets, arrays, diags);
519 }
520 }
521 }
522 }
523 HirStmt::Break(_)
524 | HirStmt::Continue(_)
525 | HirStmt::Return(_)
526 | HirStmt::Global(_, _)
527 | HirStmt::Persistent(_, _)
528 | HirStmt::Import { .. } => {}
529 }
530}
531
532fn walk_expr_general(
533 expr: &HirExpr,
534 diags: &mut Vec<HirDiagnostic>,
535 non_tx_write_count: &mut usize,
536 tx_vars: &HashSet<VarId>,
537) {
538 match &expr.kind {
539 HirExprKind::FuncCall(name, args) => {
540 if name == "data.open" {
541 let first = args.first();
542 let is_typed_open = args.len() > 1;
543 let first_is_literal =
544 matches!(first.map(|a| &a.kind), Some(HirExprKind::String(_)));
545 if !first_is_literal && !is_typed_open {
546 diags.push(HirDiagnostic {
547 message: "data.open with dynamic path should include explicit schema for type safety"
548 .to_string(),
549 span: expr.span,
550 code: "lint.data.no_untyped_open",
551 severity: HirDiagnosticSeverity::Warning,
552 });
553 }
554 }
555 for arg in args {
556 walk_expr_general(arg, diags, non_tx_write_count, tx_vars);
557 }
558 }
559 HirExprKind::MethodCall(base, method, args)
560 | HirExprKind::DottedInvoke(base, method, args) => {
561 if method == "write" {
562 let in_tx =
563 matches!(base.kind, HirExprKind::Var(var_id) if tx_vars.contains(&var_id));
564 if !in_tx {
565 *non_tx_write_count += 1;
566 if *non_tx_write_count > 1 {
567 diags.push(HirDiagnostic {
568 message:
569 "multiple data writes detected outside explicit transaction; consider ds.begin() + tx.commit()"
570 .to_string(),
571 span: expr.span,
572 code: "lint.data.no_multiwrite_outside_tx",
573 severity: HirDiagnosticSeverity::Warning,
574 });
575 }
576 }
577 }
578 walk_expr_general(base, diags, non_tx_write_count, tx_vars);
579 for arg in args {
580 walk_expr_general(arg, diags, non_tx_write_count, tx_vars);
581 }
582 }
583 HirExprKind::Unary(_, inner) => {
584 walk_expr_general(inner, diags, non_tx_write_count, tx_vars)
585 }
586 HirExprKind::Binary(lhs, _, rhs) => {
587 walk_expr_general(lhs, diags, non_tx_write_count, tx_vars);
588 walk_expr_general(rhs, diags, non_tx_write_count, tx_vars);
589 }
590 HirExprKind::Tensor(rows) | HirExprKind::Cell(rows) => {
591 for row in rows {
592 for value in row {
593 walk_expr_general(value, diags, non_tx_write_count, tx_vars);
594 }
595 }
596 }
597 HirExprKind::Index(base, args) | HirExprKind::IndexCell(base, args) => {
598 walk_expr_general(base, diags, non_tx_write_count, tx_vars);
599 for arg in args {
600 walk_expr_general(arg, diags, non_tx_write_count, tx_vars);
601 }
602 }
603 HirExprKind::Range(start, step, end) => {
604 walk_expr_general(start, diags, non_tx_write_count, tx_vars);
605 if let Some(step) = step {
606 walk_expr_general(step, diags, non_tx_write_count, tx_vars);
607 }
608 walk_expr_general(end, diags, non_tx_write_count, tx_vars);
609 }
610 HirExprKind::Member(base, _) | HirExprKind::AnonFunc { body: base, .. } => {
611 walk_expr_general(base, diags, non_tx_write_count, tx_vars)
612 }
613 HirExprKind::MemberDynamic(base, field) => {
614 walk_expr_general(base, diags, non_tx_write_count, tx_vars);
615 walk_expr_general(field, diags, non_tx_write_count, tx_vars);
616 }
617 HirExprKind::FuncHandle(_)
618 | HirExprKind::MetaClass(_)
619 | HirExprKind::Var(_)
620 | HirExprKind::String(_)
621 | HirExprKind::Number(_)
622 | HirExprKind::Constant(_)
623 | HirExprKind::Colon
624 | HirExprKind::End => {}
625 }
626}
627
628fn walk_stmt_general(
629 stmt: &HirStmt,
630 diags: &mut Vec<HirDiagnostic>,
631 non_tx_write_count: &mut usize,
632 tx_vars: &HashSet<VarId>,
633) {
634 match stmt {
635 HirStmt::ExprStmt(expr, _, _) => {
636 if matches!(
637 &expr.kind,
638 HirExprKind::MethodCall(_, method, _) | HirExprKind::DottedInvoke(_, method, _)
639 if method == "commit"
640 ) {
641 diags.push(HirDiagnostic {
642 message: "consider checking transaction commit outcomes in shared workflows"
643 .to_string(),
644 span: expr.span,
645 code: "lint.data.ignore_commit_result",
646 severity: HirDiagnosticSeverity::Information,
647 });
648 }
649 walk_expr_general(expr, diags, non_tx_write_count, tx_vars)
650 }
651 HirStmt::Assign(_, expr, _, _) => {
652 walk_expr_general(expr, diags, non_tx_write_count, tx_vars)
653 }
654 HirStmt::MultiAssign(_, expr, _, _) => {
655 walk_expr_general(expr, diags, non_tx_write_count, tx_vars)
656 }
657 HirStmt::AssignLValue(lv, expr, _, _) => {
658 match lv {
659 HirLValue::Var(_) => {}
660 HirLValue::Index(base, args) | HirLValue::IndexCell(base, args) => {
661 walk_expr_general(base, diags, non_tx_write_count, tx_vars);
662 for arg in args {
663 walk_expr_general(arg, diags, non_tx_write_count, tx_vars);
664 }
665 }
666 HirLValue::Member(base, _) => {
667 walk_expr_general(base, diags, non_tx_write_count, tx_vars)
668 }
669 HirLValue::MemberDynamic(base, field) => {
670 walk_expr_general(base, diags, non_tx_write_count, tx_vars);
671 walk_expr_general(field, diags, non_tx_write_count, tx_vars);
672 }
673 }
674 walk_expr_general(expr, diags, non_tx_write_count, tx_vars);
675 }
676 HirStmt::If {
677 cond,
678 then_body,
679 elseif_blocks,
680 else_body,
681 ..
682 } => {
683 walk_expr_general(cond, diags, non_tx_write_count, tx_vars);
684 for stmt in then_body {
685 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
686 }
687 for (cond, body) in elseif_blocks {
688 walk_expr_general(cond, diags, non_tx_write_count, tx_vars);
689 for stmt in body {
690 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
691 }
692 }
693 if let Some(body) = else_body {
694 for stmt in body {
695 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
696 }
697 }
698 }
699 HirStmt::While { cond, body, .. } => {
700 walk_expr_general(cond, diags, non_tx_write_count, tx_vars);
701 for stmt in body {
702 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
703 }
704 }
705 HirStmt::For { expr, body, .. } => {
706 walk_expr_general(expr, diags, non_tx_write_count, tx_vars);
707 for stmt in body {
708 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
709 }
710 }
711 HirStmt::Switch {
712 expr,
713 cases,
714 otherwise,
715 ..
716 } => {
717 walk_expr_general(expr, diags, non_tx_write_count, tx_vars);
718 for (case_expr, body) in cases {
719 walk_expr_general(case_expr, diags, non_tx_write_count, tx_vars);
720 for stmt in body {
721 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
722 }
723 }
724 if let Some(body) = otherwise {
725 for stmt in body {
726 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
727 }
728 }
729 }
730 HirStmt::TryCatch {
731 try_body,
732 catch_body,
733 ..
734 } => {
735 for stmt in try_body {
736 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
737 }
738 for stmt in catch_body {
739 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
740 }
741 }
742 HirStmt::Function { body, .. } => {
743 for stmt in body {
744 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
745 }
746 }
747 HirStmt::ClassDef { members, .. } => {
748 for member in members {
749 if let HirClassMember::Methods { body, .. } = member {
750 for stmt in body {
751 walk_stmt_general(stmt, diags, non_tx_write_count, tx_vars);
752 }
753 }
754 }
755 }
756 HirStmt::Break(_)
757 | HirStmt::Continue(_)
758 | HirStmt::Return(_)
759 | HirStmt::Global(_, _)
760 | HirStmt::Persistent(_, _)
761 | HirStmt::Import { .. } => {}
762 }
763}