@@ -5,6 +5,7 @@ defmodule ElixirAnalyzer.TestSuite.DNAEncoding do
55
66 use ElixirAnalyzer.ExerciseTest
77 alias ElixirAnalyzer.Constants
8+ alias ElixirAnalyzer.Source
89
910 assert_no_call "does not call any Enum functions" do
1011 type :essential
@@ -29,4 +30,131 @@ defmodule ElixirAnalyzer.TestSuite.DNAEncoding do
2930 called_fn name: :for
3031 comment Constants . dna_encoding_use_recursion ( )
3132 end
33+
34+ check_source "uses tail call recursion" do
35+ type :essential
36+ suppress_if "does not call any Enum functions" , :fail
37+ suppress_if "does not call any Stream functions" , :fail
38+ suppress_if "does not call any List functions" , :fail
39+ suppress_if "does not use list comprehensions" , :fail
40+ comment Constants . dna_encoding_use_tail_call_recursion ( )
41+
42+ check ( % Source { code_ast: code_ast } ) do
43+ { _ , tail_call_recursive_functions } =
44+ Macro . prewalk (
45+ code_ast ,
46+ [ ] ,
47+ fn node , acc -> find_tail_call_recursive_functions ( node , acc ) end
48+ )
49+
50+ tail_call_recursive_functions = Enum . uniq ( tail_call_recursive_functions )
51+
52+ { _ , all_recursive_functions } =
53+ Macro . prewalk (
54+ code_ast ,
55+ [ ] ,
56+ fn node , acc -> find_all_recursive_functions ( node , acc ) end
57+ )
58+
59+ all_recursive_functions = Enum . uniq ( all_recursive_functions )
60+
61+ non_tail_call_recursive_functions = all_recursive_functions -- tail_call_recursive_functions
62+
63+ if non_tail_call_recursive_functions != [ ] || tail_call_recursive_functions == [ ] do
64+ { false ,
65+ % {
66+ non_tail_call_recursive_functions:
67+ format_function_names ( non_tail_call_recursive_functions ) ,
68+ tail_call_recursive_functions: format_function_names ( tail_call_recursive_functions )
69+ } }
70+ else
71+ true
72+ end
73+ end
74+ end
75+
76+ defp format_function_names ( list ) do
77+ if list == [ ] do
78+ "none"
79+ else
80+ Enum . map ( list , fn { name , arity } -> "`#{ name } /#{ arity } `" end )
81+ |> Enum . join ( ", " )
82+ end
83+ end
84+
85+ defp find_tail_call_recursive_functions ( node , acc ) do
86+ acc =
87+ case node do
88+ { op , _meta1 , [ { :when , _meta2 , [ { fn_name , _meta3 , args } | _ ] } , opts ] }
89+ when op in [ :def , :defp ] ->
90+ check_if_function_tail_call_recursive ( acc , fn_name , args , opts )
91+
92+ { op , _meta1 , [ { fn_name , _meta2 , args } , opts ] } when op in [ :def , :defp ] ->
93+ check_if_function_tail_call_recursive ( acc , fn_name , args , opts )
94+
95+ _ ->
96+ acc
97+ end
98+
99+ { node , acc }
100+ end
101+
102+ defp check_if_function_tail_call_recursive ( acc , fn_name , args , opts ) do
103+ fn_arity = length ( args )
104+
105+ last_call_in_function_def =
106+ case opts [ :do ] do
107+ { :__block , _ , calls } when is_list ( calls ) ->
108+ List . last ( calls )
109+
110+ calls when is_list ( calls ) ->
111+ List . last ( calls )
112+
113+ call ->
114+ call
115+ end
116+
117+ case last_call_in_function_def do
118+ { ^ fn_name , _meta3 , args } when length ( args ) == fn_arity ->
119+ [ { fn_name , fn_arity } | acc ]
120+
121+ _ ->
122+ acc
123+ end
124+ end
125+
126+ defp find_all_recursive_functions ( node , acc ) do
127+ acc =
128+ case node do
129+ { op , _meta1 , [ { :when , _meta2 , [ { fn_name , _meta3 , args } | _ ] } , opts ] }
130+ when op in [ :def , :defp ] ->
131+ check_if_function_recursive ( acc , fn_name , args , opts )
132+
133+ { op , _meta1 , [ { fn_name , _meta2 , args } , opts ] } when op in [ :def , :defp ] ->
134+ check_if_function_recursive ( acc , fn_name , args , opts )
135+
136+ _ ->
137+ acc
138+ end
139+
140+ { node , acc }
141+ end
142+
143+ defp check_if_function_recursive ( acc , fn_name , args , opts ) do
144+ fn_arity = length ( args )
145+
146+ { _ , any_nested_recursive_calls? } =
147+ Macro . prewalk ( opts [ :do ] , false , fn node , acc ->
148+ case node do
149+ { ^ fn_name , _ , args } when length ( args ) == fn_arity -> { node , true }
150+ _ -> { node , acc }
151+ end
152+ end )
153+
154+ if any_nested_recursive_calls? do
155+ [ { fn_name , fn_arity } | acc ]
156+ else
157+ acc
158+ end
159+ end
32160end
0 commit comments