Skip to content

Commit 19b2c30

Browse files
committed
type_checkers: access
1 parent f0725ad commit 19b2c30

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

src/type_checkers/access.cr

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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 unwind_access(node : Ast::Access, stack = [] of Ast::Variable) : Array(Ast::Variable)
23+
case item = node.expression
24+
when Ast::Access
25+
stack.unshift(item.field)
26+
unwind_access(item, stack)
27+
when Ast::Variable
28+
stack.unshift(item)
29+
end
30+
31+
stack
32+
end
33+
34+
def to_function_type(node : Ast::TypeVariant, parent : Ast::TypeDefinition)
35+
parent_type =
36+
resolve parent
37+
38+
option_type =
39+
resolve node
40+
41+
if node.parameters.empty?
42+
parent_type
43+
else
44+
parameters =
45+
case fields = node.fields
46+
when Array(Ast::TypeDefinitionField)
47+
fields.map do |field|
48+
type =
49+
resolve field.type
50+
51+
type.label = field.key.value
52+
type
53+
end
54+
else
55+
option_type.parameters.dup
56+
end
57+
58+
parameters << parent_type.as(Checkable)
59+
Comparer.normalize(Type.new("Function", parameters))
60+
end
61+
end
62+
63+
def check(node : Ast::Access) : Checkable
64+
possibility =
65+
case variable = node.expression
66+
when Ast::Access
67+
stack =
68+
unwind_access(node)
69+
70+
if stack.empty?
71+
nil
72+
else
73+
stack.join(".", &.value)
74+
end
75+
when Ast::Variable
76+
variable.value
77+
end
78+
79+
if possibility
80+
entity = scope.resolve(possibility, node).try(&.node)
81+
82+
if parent = ast.type_definitions.find(&.name.value.==(possibility))
83+
case fields = parent.fields
84+
when Array(Ast::TypeVariant)
85+
if option = fields.find(&.value.value.==(node.field.value))
86+
variables[node] = {option, parent}
87+
variables[node.field] = {option, parent}
88+
variables[node.expression] = {parent, parent}
89+
resolve(parent)
90+
return to_function_type(option, parent)
91+
else
92+
error! :type_variant_missing do
93+
snippet "I was looking for a variant of a type:", node.field
94+
snippet "The type in question:", parent
95+
end unless entity
96+
end
97+
end
98+
end
99+
100+
if entity
101+
if entity && possibility[0].ascii_uppercase?
102+
if target_node = scope.resolve(node.field.value, entity).try(&.node)
103+
variables[node.expression] = {entity, entity}
104+
check!(entity)
105+
variables[node] = {target_node, entity}
106+
variables[node.field] = {target_node, entity}
107+
return resolve target_node
108+
end
109+
end
110+
end
111+
end
112+
113+
target =
114+
resolve node.expression
115+
116+
error! :access_not_record do
117+
snippet "You are trying to access a field on an entity which is not " \
118+
"a record:", target
119+
snippet "The access in question is: ", node
120+
end unless target.is_a?(Record)
121+
122+
new_target = target.fields[node.field.value]?
123+
124+
error! :access_field_not_found do
125+
block do
126+
text "The accessed field"
127+
code node.field.value
128+
text "does not exists on the entity:"
129+
end
130+
131+
snippet target
132+
snippet "The access in question is here:", node
133+
end unless new_target
134+
135+
if item = component_records.find(&.last.==(target))
136+
component, _ = item
137+
138+
refs =
139+
component.refs.reduce({} of String => Ast::Node) do |memo, (variable, ref)|
140+
case ref
141+
when Ast::HtmlComponent
142+
components_touched.add(component)
143+
component_records
144+
.find(&.first.name.value.==(ref.component.value))
145+
.try do |record|
146+
memo[variable.value] = record.first
147+
end
148+
when Ast::HtmlElement
149+
lookups[node.field] = {variable, component}
150+
return Type.new("Maybe", [Type.new("Dom.Element")] of Checkable) if node.field.value == variable.value
151+
end
152+
153+
memo
154+
end
155+
156+
lookups[node.field] = {
157+
(component.gets.find(&.name.value.==(node.field.value)) ||
158+
component.functions.find(&.name.value.==(node.field.value)) ||
159+
component.properties.find(&.name.value.==(node.field.value)) ||
160+
refs[node.field.value]? ||
161+
component.states.find(&.name.value.==(node.field.value))).not_nil!,
162+
component,
163+
}
164+
165+
resolve lookups[node.field][0]
166+
else
167+
record_field_lookup[node.field] = target.name
168+
end
169+
170+
new_target
171+
end
172+
end
173+
end

0 commit comments

Comments
 (0)