|
8 | 8 | open Aast
|
9 | 9 | open Hh_prelude
|
10 | 10 |
|
| 11 | +type method_prop = { |
| 12 | + method_name: string; |
| 13 | + class_name: string; |
| 14 | + pos: Pos.t; |
| 15 | +} |
| 16 | + |
| 17 | +type fun_prop = { |
| 18 | + fun_name: string; |
| 19 | + pos: Pos.t; |
| 20 | +} |
| 21 | + |
| 22 | +type is_fun_or_method = |
| 23 | + | Is_fun of fun_prop |
| 24 | + | Is_method of method_prop |
| 25 | + |
11 | 26 | let same_name_ignoring_ns (name1 : string) (name2 : string) =
|
12 | 27 | String.equal (Utils.strip_ns name1) (Utils.strip_ns name2)
|
13 | 28 |
|
14 |
| -let check_expr_fun (expr_ : Tast.expr_) (fun_name : string) = |
15 |
| - match expr_ with |
16 |
| - | Aast.Call ((_, _, Aast.Id (_, name)), _, _, _) -> |
17 |
| - same_name_ignoring_ns name fun_name |
18 |
| - | _ -> false |
19 |
| - |
20 |
| -let check_expr_method |
21 |
| - (expr_ : Tast.expr_) (method_name : string) (c_name : string) = |
22 |
| - match expr_ with |
23 |
| - | Aast.Call |
24 |
| - ( ( _, |
| 29 | +let check_expr (expr_ : Tast.expr_) (is_fun_or_method : is_fun_or_method) = |
| 30 | + match is_fun_or_method with |
| 31 | + | Is_fun f_prop -> |
| 32 | + (match expr_ with |
| 33 | + | Aast.Call ((_, _, Aast.Id (_, name)), _, _, _) -> |
| 34 | + same_name_ignoring_ns name f_prop.fun_name |
| 35 | + | _ -> false) |
| 36 | + | Is_method m_prop -> |
| 37 | + (match expr_ with |
| 38 | + | Aast.Call |
| 39 | + ( ( _, |
| 40 | + _, |
| 41 | + Aast.Obj_get |
| 42 | + ((_, _, Aast.This), (_, _, Aast.Id (_, name)), _, Aast.Is_method) |
| 43 | + ), |
25 | 44 | _,
|
26 |
| - Aast.Obj_get |
27 |
| - ((_, _, Aast.This), (_, _, Aast.Id (_, name)), _, Aast.Is_method) ), |
28 |
| - _, |
29 |
| - _, |
30 |
| - _ ) -> |
31 |
| - same_name_ignoring_ns name method_name |
32 |
| - | Aast.Call |
33 |
| - ( (_, _, Aast.Class_const ((_, _, Aast.CI (_, name_of_class)), (_, name))), |
34 |
| - _, |
35 |
| - _, |
36 |
| - _ ) |
37 |
| - when same_name_ignoring_ns name_of_class c_name -> |
38 |
| - same_name_ignoring_ns name method_name |
39 |
| - | Aast.Call |
40 |
| - ((_, _, Aast.Class_const ((_, _, Aast.CIstatic), (_, name))), _, _, _) -> |
41 |
| - same_name_ignoring_ns name method_name |
42 |
| - | Aast.Call |
43 |
| - ((_, _, Aast.Class_const ((_, _, Aast.CIself), (_, name))), _, _, _) -> |
44 |
| - same_name_ignoring_ns name method_name |
45 |
| - | _ -> false |
| 45 | + _, |
| 46 | + _ ) -> |
| 47 | + same_name_ignoring_ns name m_prop.method_name |
| 48 | + | Aast.Call |
| 49 | + ( ( _, |
| 50 | + _, |
| 51 | + Aast.Class_const ((_, _, Aast.CI (_, name_of_class)), (_, name)) ), |
| 52 | + _, |
| 53 | + _, |
| 54 | + _ ) |
| 55 | + when same_name_ignoring_ns name_of_class m_prop.class_name -> |
| 56 | + same_name_ignoring_ns name m_prop.method_name |
| 57 | + | Aast.Call |
| 58 | + ((_, _, Aast.Class_const ((_, _, Aast.CIstatic), (_, name))), _, _, _) |
| 59 | + -> |
| 60 | + same_name_ignoring_ns name m_prop.method_name |
| 61 | + | Aast.Call |
| 62 | + ((_, _, Aast.Class_const ((_, _, Aast.CIself), (_, name))), _, _, _) -> |
| 63 | + same_name_ignoring_ns name m_prop.method_name |
| 64 | + | _ -> false) |
46 | 65 |
|
47 |
| -let is_recursion_in_fun ((_, stmt_) : Tast.stmt) (fun_name : string) = |
| 66 | +let is_recursive_call |
| 67 | + ((_, stmt_) : Tast.stmt) (is_fun_or_method : is_fun_or_method) = |
48 | 68 | match stmt_ with
|
49 | 69 | | Aast.Return (Some (_, _, Aast.Await (_, _, await_exp))) ->
|
50 |
| - check_expr_fun await_exp fun_name |
51 |
| - | Aast.Return (Some (_, _, e)) -> check_expr_fun e fun_name |
| 70 | + check_expr await_exp is_fun_or_method |
| 71 | + | Aast.Return (Some (_, _, e)) -> check_expr e is_fun_or_method |
| 72 | + | Aast.Throw (_ty, _pos, expr_) -> check_expr expr_ is_fun_or_method |
52 | 73 | | Aast.Expr (_ty, _pos, Aast.Await (_, _, await_exp)) ->
|
53 |
| - check_expr_fun await_exp fun_name |
54 |
| - | Aast.Expr (_ty, _pos, expr_) -> check_expr_fun expr_ fun_name |
| 74 | + check_expr await_exp is_fun_or_method |
| 75 | + | Aast.Expr (_ty, _pos, expr_) -> check_expr expr_ is_fun_or_method |
55 | 76 | | _ -> false
|
56 | 77 |
|
57 |
| -let is_recursion_in_method |
58 |
| - ((_, stmt_) : Tast.stmt) (method_name : string) (c_name : string) = |
59 |
| - match stmt_ with |
60 |
| - | Aast.Return (Some (_, _, Aast.Await (_, _, await_exp))) -> |
61 |
| - check_expr_method await_exp method_name c_name |
62 |
| - | Aast.Return (Some (_, _, e)) -> check_expr_method e method_name c_name |
63 |
| - | Aast.Expr (_ty, _pos, Aast.Await (_, _, await_exp)) -> |
64 |
| - check_expr_method await_exp method_name c_name |
65 |
| - | Aast.Expr (_ty, _pos, expr_) -> check_expr_method expr_ method_name c_name |
66 |
| - | _ -> false |
| 78 | +(* This function returns true if the recursion can be terminated. This occurs when we find at least one return or throw statment that is followed by any expression other than a recursive call*) |
| 79 | +let can_terminate_visitor is_fun_or_method = |
| 80 | + object |
| 81 | + inherit [_] Aast.reduce as super |
67 | 82 |
|
68 |
| -let check_unconditional_recursion m c_name = |
69 |
| - match m.m_body.fb_ast with |
70 |
| - | head :: _ -> |
71 |
| - if is_recursion_in_method head (snd m.m_name) c_name then |
72 |
| - Lints_errors.unconditional_recursion m.m_span |
73 |
| - else |
| 83 | + method zero = false |
| 84 | + |
| 85 | + method plus = ( || ) |
| 86 | + |
| 87 | + method! on_stmt env s = |
| 88 | + match snd s with |
| 89 | + | Aast.Return _ -> not (is_recursive_call s is_fun_or_method) |
| 90 | + | Aast.Throw _ -> not (is_recursive_call s is_fun_or_method) |
| 91 | + | _ -> super#on_stmt env s |
| 92 | + end |
| 93 | + |
| 94 | +let can_terminate (s : Tast.stmt) (is_fun_or_method : is_fun_or_method) : bool = |
| 95 | + (can_terminate_visitor is_fun_or_method)#on_stmt () s |
| 96 | + |
| 97 | +let rec check_unconditional_recursion |
| 98 | + (stmt_list : Tast.stmt list) (is_fun_or_method : is_fun_or_method) = |
| 99 | + match stmt_list with |
| 100 | + | head :: tail -> |
| 101 | + if can_terminate head is_fun_or_method then |
74 | 102 | ()
|
| 103 | + else if is_recursive_call head is_fun_or_method then |
| 104 | + match is_fun_or_method with |
| 105 | + | Is_fun f_prop -> Lints_errors.unconditional_recursion f_prop.pos |
| 106 | + | Is_method m_prop -> Lints_errors.unconditional_recursion m_prop.pos |
| 107 | + else |
| 108 | + check_unconditional_recursion tail is_fun_or_method |
75 | 109 | | [] -> ()
|
76 | 110 |
|
77 | 111 | let handler =
|
78 | 112 | object
|
79 | 113 | inherit Tast_visitor.handler_base
|
80 | 114 |
|
81 | 115 | method! at_fun_def _env f =
|
82 |
| - match f.fd_fun.f_body.fb_ast with |
83 |
| - | head :: _ -> |
84 |
| - if is_recursion_in_fun head (snd f.fd_fun.f_name) then |
85 |
| - Lints_errors.unconditional_recursion f.fd_fun.f_span |
86 |
| - else |
87 |
| - () |
88 |
| - | [] -> () |
| 116 | + let f_properties : fun_prop = |
| 117 | + { fun_name = snd f.fd_fun.f_name; pos = f.fd_fun.f_span } |
| 118 | + in |
| 119 | + let is_f_or_m : is_fun_or_method = Is_fun f_properties in |
| 120 | + check_unconditional_recursion f.fd_fun.f_body.fb_ast is_f_or_m |
89 | 121 |
|
90 | 122 | method! at_class_ _env c =
|
91 | 123 | List.iter c.c_methods ~f:(fun c_method ->
|
92 |
| - check_unconditional_recursion c_method (snd c.c_name)) |
| 124 | + let m_properties : method_prop = |
| 125 | + { |
| 126 | + method_name = snd c_method.m_name; |
| 127 | + class_name = snd c.c_name; |
| 128 | + pos = c_method.m_span; |
| 129 | + } |
| 130 | + in |
| 131 | + let is_f_or_m : is_fun_or_method = Is_method m_properties in |
| 132 | + check_unconditional_recursion c_method.m_body.fb_ast is_f_or_m) |
93 | 133 | end
|
0 commit comments