Skip to content

Commit 8220c91

Browse files
committed
type_checkers: destructuring
1 parent eb19e87 commit 8220c91

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

src/type_checkers/destructuring.cr

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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

Comments
 (0)