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