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