1+ #  -----------------------------------------------------------------------
2+ #  This file is part of MoonScript
3+ # 
4+ #  MoonSript is free software: you can redistribute it and/or modify
5+ #  it under the terms of the GNU General Public License as published by
6+ #  the Free Software Foundation, either version 3 of the License, or
7+ #  (at your option) any later version.
8+ # 
9+ #  MoonSript is distributed in the hope that it will be useful,
10+ #  but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12+ #  GNU General Public License for more details.
13+ # 
14+ #  You should have received a copy of the GNU General Public License
15+ #  along with MoonSript.  If not, see <https://www.gnu.org/licenses/>.
16+ # 
17+ #  Copyright (C) 2025 Krisna Pranav, MoonScript Developers
18+ #  -----------------------------------------------------------------------
19+ 
20+ module  MoonScript 
21+     class  TypeChecker 
22+       def  to_pattern (node : Ast ::ArrayDestructuring ) : ExhaustivenessChecker ::Pattern 
23+         if  node.items.empty?
24+           ExhaustivenessChecker ::PEmptyArray .new
25+         else 
26+           list = 
27+             if  node.items.any?(Ast ::Spread )
28+               ExhaustivenessChecker ::PDiscard .new
29+             else 
30+               ExhaustivenessChecker ::PEmptyArray .new
31+             end 
32+   
33+           node.items.reverse.each do  |element |
34+             case  element
35+             when  Ast ::Spread 
36+               next 
37+             else 
38+               first =  to_pattern(element)
39+               list =  ExhaustivenessChecker ::PArray .new(first, list)
40+             end 
41+           end 
42+   
43+           list
44+         end 
45+       end 
46+   
47+       def  to_pattern (node : Ast ::TupleDestructuring ) : ExhaustivenessChecker ::Pattern 
48+         ExhaustivenessChecker ::PTuple .new(node.items.map { |item | to_pattern(item) })
49+       end 
50+   
51+       def  to_pattern (node : Ast ::TypeDestructuring ) : ExhaustivenessChecker ::Pattern 
52+         if  type  =  ast.type_definitions.find(& .name.value.== (cache[node].name))
53+           case  fields =  type .fields
54+           when  Array (Ast ::TypeVariant )
55+             if  index =  fields.index(& .value.value.== (node.variant.value))
56+               ExhaustivenessChecker ::PConstructor .new(
57+                 arguments:  node.items.map { |item | to_pattern(item) },
58+                 constructor:  ExhaustivenessChecker ::CVariant .new(
59+                   type:  to_pattern_type(cache[node]),
60+                   index:  index))
61+             end 
62+           end 
63+         end  ||  ExhaustivenessChecker ::PString .new(node.source)
64+       end 
65+   
66+       def  to_pattern_type (type  : Checkable ) : ExhaustivenessChecker ::Checkable 
67+         case  type 
68+         in  Variable 
69+           ExhaustivenessChecker ::TypeVariable .new(type .name)
70+         in  Record 
71+           ExhaustivenessChecker ::Type .new(type .name)
72+         in  Type 
73+           ExhaustivenessChecker ::Type .new(
74+             type .name,
75+             type .parameters.map(&- > to_pattern_type(Checkable )))
76+         end 
77+       end 
78+   
79+       def  to_pattern_type (node : Ast ::Node ) : ExhaustivenessChecker ::Checkable 
80+         case  node
81+         when  Ast ::TypeVariable 
82+           ExhaustivenessChecker ::TypeVariable .new(node.value)
83+         when  Ast ::Type 
84+           ExhaustivenessChecker ::Type .new(
85+             node.name.value,
86+             node.parameters.map(&- > to_pattern_type(Ast ::Node )))
87+         when  Ast ::TypeDefinitionField 
88+           to_pattern_type(node.type)
89+         else 
90+           raise  " WTF" 
91+         end 
92+       end 
93+   
94+       def  to_pattern (node : Ast ::Spread ) : ExhaustivenessChecker ::Pattern 
95+         raise  " SPREAD" 
96+       end 
97+   
98+       def  to_pattern (node : Ast ::Variable ) : ExhaustivenessChecker ::Pattern 
99+         ExhaustivenessChecker ::PVariable .new(node.value)
100+       end 
101+   
102+       def  to_pattern (node : Ast ::Node ) : ExhaustivenessChecker ::Pattern 
103+         case  node
104+         when  Ast ::ArrayLiteral 
105+           if  node.items.empty?
106+             ExhaustivenessChecker ::PEmptyArray .new
107+           end 
108+         end  ||  ExhaustivenessChecker ::PString .new(node.source)
109+       end 
110+   
111+       def  to_pattern (node : Ast ::Discard ) : ExhaustivenessChecker ::Pattern 
112+         ExhaustivenessChecker ::PDiscard .new
113+       end 
114+   
115+       def  to_pattern (node : Nil ) : ExhaustivenessChecker ::Pattern 
116+         ExhaustivenessChecker ::PDiscard .new
117+       end 
118+   
119+       def  check_exhaustiveness (target : Checkable , patterns : Array (Ast ::Node ?))
120+         compiler =  ExhaustivenessChecker ::Compiler .new(
121+           - > (type  : ExhaustivenessChecker ::Checkable ) : Array (ExhaustivenessChecker ::Variant ) |  Nil  {
122+             if  defi =  ast.type_definitions.find(& .name.value.== (type .name))
123+               case  fields =  defi.fields
124+               when  Array (Ast ::TypeVariant )
125+                 fields.map do  |variant |
126+                   parameters = 
127+                     variant.parameters.map do  |param |
128+                       case  param
129+                       when  Ast ::TypeVariable 
130+                         case  type 
131+                         when  ExhaustivenessChecker ::Type 
132+                           type .parameters[defi.parameters.index!(& .value.== (param.value))]
133+                         end 
134+                       end  ||  to_pattern_type(param)
135+                     end 
136+   
137+                   ExhaustivenessChecker ::Variant .new(parameters)
138+                 end 
139+               end 
140+             end 
141+           },
142+           - > (name : String , index : Int32 ) : String  |  Nil  {
143+             if  defi =  ast.type_definitions.find(& .name.value.== (name))
144+               case  fields =  defi.fields
145+               when  Array (Ast ::TypeVariant )
146+                 fields[index].value.value
147+               end 
148+             end 
149+           })
150+   
151+         type  = 
152+           to_pattern_type(target)
153+   
154+         variable = 
155+           compiler.new_variable(type )
156+   
157+         rows = 
158+           patterns.map_with_index do  |pattern , index |
159+             ExhaustivenessChecker ::Row .new(
160+               [ExhaustivenessChecker ::Column .new(variable, to_pattern(pattern))],
161+               nil ,
162+               ExhaustivenessChecker ::Body .new([] of {String , ExhaustivenessChecker ::Variable }, index))
163+           end 
164+   
165+         compiler.compile(rows)
166+       rescue  e
167+         error! :blah  do 
168+           block e.message.to_s
169+           snippet " Type:" 
170+           snippet " Node:" 0 ].not_nil!
171+         end 
172+       end 
173+   
174+       def  check (node : Ast ::Case ) : Checkable 
175+         condition = 
176+           resolve node.condition
177+   
178+         first = 
179+           resolve node.branches.first, condition
180+   
181+         unified = 
182+           node
183+             .branches[1 ..]
184+             .each_with_index
185+             .reduce(first) do  |resolved , (branch , index )|
186+               type  = 
187+                 resolve branch, condition
188+   
189+               unified_branch = 
190+                 Comparer .compare(type , resolved)
191+   
192+               error! :case_branch_not_matches  do 
193+                 block do 
194+                   text " The return type of the" 
195+                   bold " #{ ordinal(index +  2 ) } " 
196+                   text " of a case expression does not match the type of the 1st branch." 
197+                 end 
198+   
199+                 snippet " expecting the type of the 1st branch:" 
200+                 snippet " Instead it is:" type 
201+                 snippet " The branch in question:" 
202+               end  unless  unified_branch
203+   
204+               unified_branch
205+             end 
206+   
207+         begin 
208+           patterns = 
209+             node.branches.map(& .pattern)
210+   
211+           match = 
212+             check_exhaustiveness(condition, patterns)
213+   
214+           missing = 
215+             if  match.diagnostics.missing?
216+               match.missing_patterns
217+             else 
218+               [] of String 
219+             end 
220+   
221+           extra = 
222+             node.branches.each_with_index.reject do  |_ , index |
223+               match.diagnostics.reachable.includes?(index)
224+             end .map { |item | formatter.format!(item[0 ]) }.to_a
225+   
226+           error! :case_not_exhaustive  do 
227+             snippet " Not all possibilities of a case expression are covered. " 
228+                     " To cover all remaining possibilities create branches " 
229+                     " for the following cases:" " \n " 
230+   
231+             snippet " The case in question is here:" 
232+           end  unless  missing.empty?
233+   
234+           error! :case_unnecessary  do 
235+             snippet " All possibilities of the case expression are covered so " 
236+                     " these branches are not needed and can be safely " 
237+                     " removed." " \n " 
238+   
239+             snippet " The case in question is here:" 
240+           end  unless  extra.empty?
241+         rescue  exception : Error 
242+           raise  exception
243+         rescue  exception
244+           error! :case_exhaustiveness_error  do 
245+             block(exception.to_s +  '\n'  +  exception.backtrace.join('\n' ))
246+             snippet node
247+           end 
248+         end 
249+   
250+         unified
251+       end 
252+     end 
253+   end 
0 commit comments