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
+ alias VariableScope = Tuple (String , Checkable , Ast ::Node )
23
+
24
+ def destructuring_type_mismatch (expected : Checkable , got : Checkable , node : Ast ::Node )
25
+ error! :destructuring_type_mismatch do
26
+ block " A value does not match its supposed type in a destructuring."
27
+
28
+ expected expected, got
29
+
30
+ snippet " The destructuring:" , node
31
+ end
32
+ end
33
+
34
+ def destructure (
35
+ node : Nil ,
36
+ condition : Checkable ,
37
+ variables : Array (VariableScope ) = [] of VariableScope ,
38
+ )
39
+ variables
40
+ end
41
+
42
+ def destructure (
43
+ node : Ast ::Discard ,
44
+ condition : Checkable ,
45
+ variables : Array (VariableScope ) = [] of VariableScope ,
46
+ )
47
+ variables
48
+ end
49
+
50
+ def destructure (
51
+ node : Ast ::Node ,
52
+ condition : Checkable ,
53
+ variables : Array (VariableScope ) = [] of VariableScope ,
54
+ )
55
+ type =
56
+ resolve(node)
57
+
58
+ destructuring_type_mismatch(
59
+ expected: condition,
60
+ node: node,
61
+ got: type ) unless Comparer .compare(type , condition)
62
+
63
+ variables
64
+ end
65
+
66
+ def destructure (
67
+ node : Ast ::Variable ,
68
+ condition : Checkable ,
69
+ variables : Array (VariableScope ) = [] of VariableScope ,
70
+ )
71
+ cache[node] = condition
72
+ variables.tap(& .push({node.value, condition, node}))
73
+ end
74
+
75
+ def destructure (
76
+ node : Ast ::ArrayDestructuring ,
77
+ condition : Checkable ,
78
+ variables : Array (VariableScope ) = [] of VariableScope ,
79
+ )
80
+ destructuring_type_mismatch(
81
+ expected: ARRAY ,
82
+ got: condition,
83
+ node: node,
84
+ ) unless Comparer .compare(ARRAY , condition)
85
+
86
+ spreads =
87
+ node.items.select(Ast ::Spread ).size
88
+
89
+ error! :destructuring_multiple_spreads do
90
+ block " An array destructuring can only contain one spread notation "
91
+
92
+ block do
93
+ text " This array destructuring contains"
94
+ bold spreads.to_s
95
+ text " spread notations:"
96
+ end
97
+
98
+ snippet node
99
+ end if spreads > 1
100
+
101
+ node.items.each_with_index do |item , index |
102
+ case item
103
+ when Ast ::Spread
104
+ if index == (node.items.size - 1 )
105
+ cache[item] = condition
106
+ case variable = item.variable
107
+ when Ast ::Variable
108
+ variables << {variable.value, condition, item}
109
+ end
110
+ else
111
+ error! :destructuring_multiple_spreads do
112
+ block " The spread notation can only appear as the last item "
113
+
114
+ snippet node
115
+ end
116
+ end
117
+ else
118
+ destructure(item, condition.parameters[0 ], variables)
119
+ end
120
+ end
121
+
122
+ variables
123
+ end
124
+
125
+ def destructure (
126
+ node : Ast ::TupleDestructuring ,
127
+ condition : Checkable ,
128
+ variables : Array (VariableScope ) = [] of VariableScope ,
129
+ )
130
+ destructuring_type_mismatch(
131
+ expected: Type .new(" Tuple" ),
132
+ got: condition,
133
+ node: node,
134
+ ) unless condition.name == " Tuple"
135
+
136
+ error! :destructuring_tuple_mismatch do
137
+ block do
138
+ text " This destructuring of a tuple does not match the given "
139
+ bold node.items.size.to_s
140
+ text " items."
141
+ end
142
+
143
+ snippet " Instead it is this:" , condition
144
+ snippet " The destructuring:" , node
145
+ end if node.items.size > condition.parameters.size
146
+
147
+ node.items.each_with_index do |item , index |
148
+ destructure(item, condition.parameters[index], variables)
149
+ end
150
+
151
+ variables
152
+ end
153
+
154
+ def destructure (
155
+ node : Ast ::TypeDestructuring ,
156
+ condition : Checkable ,
157
+ variables : Array (VariableScope ) = [] of VariableScope ,
158
+ )
159
+ cache[node] = condition
160
+
161
+ name =
162
+ node.name.try(& .value) || condition.name
163
+
164
+ type_definition =
165
+ ast.type_definitions.find(& .name.value.== (name))
166
+
167
+ variant =
168
+ if type_definition
169
+ case fields = type_definition.fields
170
+ when Array (Ast ::TypeVariant )
171
+ fields.find(& .value.value.== (node.variant.value))
172
+ end
173
+ end
174
+
175
+ if type_definition && variant
176
+ lookups[node] = {variant, type_definition}
177
+
178
+ type = resolve(type_definition)
179
+
180
+ unified =
181
+ Comparer .compare(type , condition)
182
+
183
+ destructuring_type_mismatch(
184
+ expected: condition,
185
+ node: node,
186
+ got: type ,
187
+ ) unless unified
188
+
189
+ case fields = variant.fields
190
+ when Array (Ast ::TypeDefinitionField )
191
+ node.items.each_with_index do |param , index |
192
+ field =
193
+ fields[index]
194
+
195
+ type =
196
+ resolve(field.type)
197
+
198
+ destructure(param, type , variables)
199
+ end
200
+ else
201
+ node.items.each_with_index do |param , index |
202
+ case param
203
+ when Ast ::Variable
204
+ error! :destructuring_no_parameter do
205
+ block do
206
+ text " You are trying to destructure the"
207
+ bold index.to_s
208
+ text " parameter from the type variant:"
209
+ bold variant.value.value
210
+ end
211
+
212
+ block do
213
+ text " The variant only has"
214
+ bold variant.parameters.size.to_s
215
+ text " parameters."
216
+ end
217
+
218
+ snippet " You are trying to destructure it here:" , param
219
+ snippet " The option is defined here:" , variant
220
+ end unless variant.parameters[index]?
221
+
222
+ variant_type =
223
+ resolve(variant.parameters[index]).not_nil!
224
+
225
+ mapping = {} of String => Checkable
226
+
227
+ type_definition.parameters.each_with_index do |param2 , index2 |
228
+ mapping[param2.value] = condition.parameters[index2]
229
+ end
230
+
231
+ resolved_type =
232
+ Comparer .fill(variant_type, mapping).not_nil!
233
+
234
+ destructure(param, resolved_type, variables)
235
+ else
236
+ sub_type =
237
+ case item = variant.parameters[index]
238
+ when Ast ::Type
239
+ resolve(item)
240
+ when Ast ::TypeVariable
241
+ unified.parameters[type_definition.parameters.index! { |variable | variable.value == item.value }]
242
+ else
243
+ VOID
244
+ end
245
+
246
+ destructure(param, sub_type, variables)
247
+ end
248
+ end
249
+ end
250
+
251
+ return variables
252
+ elsif node.items.empty? &&
253
+ (entity_name = node.name.try(& .value)) &&
254
+ (parent = scope.resolve(entity_name, node).try(& .node)) &&
255
+ (entity = scope.resolve(node.variant.value, parent).try(& .node))
256
+ check!(parent)
257
+ lookups[node] = {entity, parent}
258
+ return destructure(entity, condition, variables)
259
+ elsif type_definition
260
+ error! :destructuring_type_variant_missing do
261
+ block do
262
+ text " could not find the variant"
263
+ bold %( "#{ node.variant.value } ")
264
+ text " of type"
265
+ bold %( "#{ type_definition.name.value } ")
266
+ text " for a destructuring:"
267
+ end
268
+
269
+ snippet node
270
+ snippet " The type is defined here:" , type_definition
271
+ end
272
+ end
273
+
274
+ error! :destructuring_type_missing do
275
+ snippet " could not find the type for a destructuring with the name:" , name.to_s
276
+ snippet " The destructuring in question is here:" , node
277
+ end
278
+ end
279
+ end
280
+ end
0 commit comments