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:" , target
170
+ snippet " Node:" , patterns[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 ) } branch"
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:" , resolved
200
+ snippet " Instead it is:" , type
201
+ snippet " The branch in question:" , branch
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:" , missing.join(" \n " )
230
+
231
+ snippet " The case in question is here:" , node
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." , extra.join(" \n " )
238
+
239
+ snippet " The case in question is here:" , node
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