Skip to content

Commit 2d50de8

Browse files
committed
type_checkers: case
1 parent 0c75097 commit 2d50de8

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed

src/type_checkers/case.cr

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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

Comments
 (0)