rubocop-boochtek 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +115 -5
- data/LICENSE.md +2 -2
- data/README.md +53 -106
- data/lib/disable_ssl_verify.rb +5 -0
- data/lib/extensions/argf.rb +8 -0
- data/lib/extensions/boolean.rb +48 -0
- data/lib/extensions/class.rb +10 -0
- data/lib/extensions/enumerable.rb +42 -0
- data/lib/extensions/integer.rb +7 -0
- data/lib/extensions/llvm_module.rb +101 -0
- data/lib/extensions/module.rb +34 -0
- data/lib/extensions/string.rb +29 -0
- data/lib/stone/ast/block.rb +88 -0
- data/lib/stone/ast/boolean_literal.rb +43 -0
- data/lib/stone/ast/computed_property_definition.rb +32 -0
- data/lib/stone/ast/constant_definition.rb +124 -0
- data/lib/stone/ast/expression.rb +12 -0
- data/lib/stone/ast/function_call.rb +291 -0
- data/lib/stone/ast/function_type_annotation.rb +31 -0
- data/lib/stone/ast/integer_literal.rb +46 -0
- data/lib/stone/ast/lambda.rb +126 -0
- data/lib/stone/ast/null_literal.rb +29 -0
- data/lib/stone/ast/program_unit/top_function.rb +209 -0
- data/lib/stone/ast/program_unit.rb +412 -0
- data/lib/stone/ast/property_access.rb +557 -0
- data/lib/stone/ast/record_definition.rb +180 -0
- data/lib/stone/ast/record_instantiation.rb +141 -0
- data/lib/stone/ast/reference.rb +145 -0
- data/lib/stone/ast/string_literal.rb +79 -0
- data/lib/stone/ast/two_phase_processing.rb +36 -0
- data/lib/stone/ast/type_annotation.rb +26 -0
- data/lib/stone/ast/type_declaration.rb +28 -0
- data/lib/stone/ast/type_of_expression.rb +41 -0
- data/lib/stone/ast/type_reference.rb +28 -0
- data/lib/stone/ast/union_type_annotation.rb +26 -0
- data/lib/stone/ast.rb +64 -0
- data/lib/stone/built_ins.rb +245 -0
- data/lib/stone/error/argument_error.rb +7 -0
- data/lib/stone/error/arity_error.rb +7 -0
- data/lib/stone/error/overflow.rb +18 -0
- data/lib/stone/error/property_error.rb +7 -0
- data/lib/stone/error/reference_error.rb +4 -0
- data/lib/stone/error/type_error.rb +7 -0
- data/lib/stone/error.rb +12 -0
- data/lib/stone/grammar.rb +124 -0
- data/lib/stone/libc.rb +27 -0
- data/lib/stone/prelude.stone +11 -0
- data/lib/stone/rtti.rb +182 -0
- data/lib/stone/scope.rb +85 -0
- data/lib/stone/transform.rb +410 -0
- data/lib/stone/type.rb +323 -0
- data/lib/stone/type_context.rb +38 -0
- data/lib/stone/type_registry.rb +73 -0
- data/lib/stone/types.rb +104 -0
- data/lib/stone.rb +70 -0
- metadata +54 -24
- data/CHANGELOG.md +0 -28
- data/Rakefile +0 -5
- data/config/default.yml +0 -277
- data/lib/rubocop/boochtek/plugin.rb +0 -43
- data/lib/rubocop/boochtek/version.rb +0 -7
- data/lib/rubocop/boochtek.rb +0 -18
- data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
- data/lib/rubocop-boochtek.rb +0 -3
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
require "stone/ast/expression"
|
|
2
|
+
require "stone/error/property_error"
|
|
3
|
+
require "stone/rtti"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module Stone
|
|
7
|
+
class AST
|
|
8
|
+
class PropertyAccess < Stone::AST::Expression
|
|
9
|
+
|
|
10
|
+
TYPE_PROPERTIES = %w[as_String record? primitive? kind size fields].freeze
|
|
11
|
+
FIELD_LIST_PROPERTIES = %w[first name type rest].freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :receiver, :property
|
|
14
|
+
|
|
15
|
+
def initialize(receiver, property)
|
|
16
|
+
@receiver = receiver
|
|
17
|
+
@property = property
|
|
18
|
+
@name = :property_access
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def type(context = nil)
|
|
22
|
+
receiver_type = @receiver.type(context)
|
|
23
|
+
return nil unless receiver_type
|
|
24
|
+
|
|
25
|
+
return_type = receiver_type.property_return_type(@property)
|
|
26
|
+
fail Stone::PropertyError, "Property '#{@property}' not found for type '#{receiver_type.name}'" unless return_type
|
|
27
|
+
|
|
28
|
+
return_type
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_llir(builder, mod, scope = Stone::Scope.top_level)
|
|
32
|
+
# 1. Check if this is a record field access (highest priority)
|
|
33
|
+
return access_record_field(builder, mod, scope) if record_field_access?(mod)
|
|
34
|
+
|
|
35
|
+
# 2. Check if receiver is a union containing record(s) - enables chained access like o.value.x
|
|
36
|
+
return access_field_on_union_record(builder, mod, scope) if receiver_is_union_with_record?(mod)
|
|
37
|
+
|
|
38
|
+
# Resolve the receiver type for subsequent checks
|
|
39
|
+
receiver_type = resolve_node_type(@receiver, mod)
|
|
40
|
+
|
|
41
|
+
# Try different property access strategies
|
|
42
|
+
handle_type_property(builder, mod, scope, receiver_type) ||
|
|
43
|
+
handle_field_list_property(builder, mod, scope, receiver_type) ||
|
|
44
|
+
handle_byte_count(mod) ||
|
|
45
|
+
handle_computed_property(builder, mod, scope, receiver_type) ||
|
|
46
|
+
fail_property_not_found(receiver_type)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def handle_type_property(builder, mod, scope, receiver_type)
|
|
50
|
+
return nil unless TYPE_PROPERTIES.include?(@property)
|
|
51
|
+
return nil unless type_receiver?(receiver_type)
|
|
52
|
+
|
|
53
|
+
type_ptr = @receiver.to_llir(builder, mod, scope)
|
|
54
|
+
generate_type_property(builder, mod, type_ptr)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private def type_receiver?(receiver_type)
|
|
58
|
+
# Direct type match
|
|
59
|
+
return true if receiver_type == Stone::Type::Type
|
|
60
|
+
|
|
61
|
+
# Check if receiver is a property access that returns Type
|
|
62
|
+
# (handles cases like fields.first.type)
|
|
63
|
+
if @receiver.is_a?(PropertyAccess)
|
|
64
|
+
receiver_property = @receiver.property
|
|
65
|
+
return true if receiver_property == "type"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
TYPE_PROPERTY_GENERATORS = {
|
|
72
|
+
"as_String" => :generate_type_as_string, "size" => :generate_type_size,
|
|
73
|
+
"kind" => :generate_type_kind, "record?" => :generate_type_record_check,
|
|
74
|
+
"primitive?" => :generate_type_primitive_check, "fields" => :generate_type_fields
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
77
|
+
private def generate_type_property(builder, _mod, type_ptr)
|
|
78
|
+
method_name = TYPE_PROPERTY_GENERATORS[@property]
|
|
79
|
+
__send__(method_name, builder, Stone::RTTI.type_struct_type, type_ptr)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def generate_type_as_string(builder, type_struct, type_ptr)
|
|
83
|
+
# Load the name pointer from the type struct (field 0)
|
|
84
|
+
name_ptr_ptr = builder.struct_gep2(type_struct, type_ptr, 0, "name_ptr_ptr")
|
|
85
|
+
# Return pointer directly - no ptr2int (CHERI-safe)
|
|
86
|
+
builder.load2(LLVM::Type.pointer, name_ptr_ptr, "name_ptr")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private def generate_type_size(builder, type_struct, type_ptr)
|
|
90
|
+
# Load the size field (field 1)
|
|
91
|
+
size_ptr = builder.struct_gep2(type_struct, type_ptr, 1, "size_ptr")
|
|
92
|
+
builder.load2(LLVM::Int64.type, size_ptr, "type_size")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private def generate_type_kind(builder, type_struct, type_ptr)
|
|
96
|
+
# Load the kind field (field 2) and extend to i64
|
|
97
|
+
kind_ptr = builder.struct_gep2(type_struct, type_ptr, 2, "kind_ptr")
|
|
98
|
+
kind_i8 = builder.load2(LLVM::Int8.type, kind_ptr, "kind_i8")
|
|
99
|
+
builder.zext(kind_i8, LLVM::Int64.type, "type_kind")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private def generate_type_record_check(builder, type_struct, type_ptr)
|
|
103
|
+
# Check if kind == 1 (record)
|
|
104
|
+
kind_ptr = builder.struct_gep2(type_struct, type_ptr, 2, "kind_ptr")
|
|
105
|
+
kind_i8 = builder.load2(LLVM::Int8.type, kind_ptr, "kind_i8")
|
|
106
|
+
builder.icmp(:eq, kind_i8, LLVM::Int8.from_i(Stone::RTTI::KIND_RECORD), "is_record")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private def generate_type_primitive_check(builder, type_struct, type_ptr)
|
|
110
|
+
# Check if kind == 0 (primitive)
|
|
111
|
+
kind_ptr = builder.struct_gep2(type_struct, type_ptr, 2, "kind_ptr")
|
|
112
|
+
kind_i8 = builder.load2(LLVM::Int8.type, kind_ptr, "kind_i8")
|
|
113
|
+
builder.icmp(:eq, kind_i8, LLVM::Int8.from_i(Stone::RTTI::KIND_PRIMITIVE), "is_primitive")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private def generate_type_fields(builder, type_struct, type_ptr)
|
|
117
|
+
fields_ptr_ptr = builder.struct_gep2(type_struct, type_ptr, 3, "fields_ptr_ptr")
|
|
118
|
+
builder.load2(LLVM::Type.pointer, fields_ptr_ptr, "fields_ptr")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private def handle_field_list_property(builder, mod, scope, receiver_type)
|
|
122
|
+
# Check if receiver type is FieldList or if we can infer it from expression structure
|
|
123
|
+
return nil unless FIELD_LIST_PROPERTIES.include?(@property)
|
|
124
|
+
return nil unless field_list_receiver?(receiver_type)
|
|
125
|
+
|
|
126
|
+
field_list_ptr = @receiver.to_llir(builder, mod, scope)
|
|
127
|
+
generate_field_list_property(builder, field_list_ptr)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private def field_list_receiver?(receiver_type)
|
|
131
|
+
# Direct type match
|
|
132
|
+
return true if receiver_type == Stone::Type::FieldList
|
|
133
|
+
|
|
134
|
+
# Check if receiver is a property access that returns FieldList
|
|
135
|
+
# (handles cases like type.fields, fields.first, fields.rest)
|
|
136
|
+
if @receiver.is_a?(PropertyAccess)
|
|
137
|
+
receiver_property = @receiver.property
|
|
138
|
+
return true if receiver_property == "fields" || FIELD_LIST_PROPERTIES.include?(receiver_property)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# For Reference receivers, we can't easily determine type at compile time
|
|
142
|
+
# Check if any ancestor in property chain indicates FieldList context
|
|
143
|
+
return receiver_references_field_list? if @receiver.is_a?(Reference)
|
|
144
|
+
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Heuristic: if the variable name suggests it's a FieldList value
|
|
149
|
+
# This is a workaround for incomplete type inference
|
|
150
|
+
private def receiver_references_field_list?
|
|
151
|
+
# Common variable names for field lists
|
|
152
|
+
identifier = @receiver.identifier
|
|
153
|
+
identifier.include?("field") || identifier.include?("Field")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
FIELD_LIST_PROPERTY_GENERATORS = {
|
|
157
|
+
"name" => :generate_field_list_name, "type" => :generate_field_list_type,
|
|
158
|
+
"rest" => :generate_field_list_rest
|
|
159
|
+
}.freeze
|
|
160
|
+
|
|
161
|
+
private def generate_field_list_property(builder, field_list_ptr)
|
|
162
|
+
return field_list_ptr if @property == "first" # .first returns the FieldList itself
|
|
163
|
+
|
|
164
|
+
method_name = FIELD_LIST_PROPERTY_GENERATORS[@property]
|
|
165
|
+
__send__(method_name, builder, Stone::RTTI.field_list_struct_type, field_list_ptr)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private def generate_field_list_name(builder, field_list_struct, field_list_ptr)
|
|
169
|
+
name_ptr_ptr = builder.struct_gep2(field_list_struct, field_list_ptr, 0, "field_name_ptr_ptr")
|
|
170
|
+
# Return pointer directly - no ptr2int (CHERI-safe)
|
|
171
|
+
builder.load2(LLVM::Type.pointer, name_ptr_ptr, "field_name_ptr")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private def generate_field_list_type(builder, field_list_struct, field_list_ptr)
|
|
175
|
+
type_ptr_ptr = builder.struct_gep2(field_list_struct, field_list_ptr, 1, "field_type_ptr_ptr")
|
|
176
|
+
builder.load2(LLVM::Type.pointer, type_ptr_ptr, "field_type_ptr")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private def generate_field_list_rest(builder, field_list_struct, field_list_ptr)
|
|
180
|
+
rest_ptr_ptr = builder.struct_gep2(field_list_struct, field_list_ptr, 2, "field_rest_ptr_ptr")
|
|
181
|
+
builder.load2(LLVM::Type.pointer, rest_ptr_ptr, "field_rest_ptr")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private def handle_byte_count(mod)
|
|
185
|
+
return nil unless @property == "byte_count"
|
|
186
|
+
|
|
187
|
+
string_literal = get_string_literal(mod)
|
|
188
|
+
LLVM::Int64.from_i(string_literal.bytesize) if string_literal
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private def handle_computed_property(builder, mod, scope, receiver_type)
|
|
192
|
+
return nil unless receiver_type
|
|
193
|
+
|
|
194
|
+
computed_func = lookup_computed_property_function(mod, receiver_type)
|
|
195
|
+
return nil unless computed_func
|
|
196
|
+
|
|
197
|
+
receiver_value = @receiver.to_llir(builder, mod, scope)
|
|
198
|
+
builder.call(computed_func, receiver_value, "#{@property}_result")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private def lookup_computed_property_function(mod, receiver_type)
|
|
202
|
+
mod.lookup_function("#{receiver_type.name}@#{@property}")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
private def fail_property_not_found(receiver_type)
|
|
206
|
+
type_name = receiver_type&.name || "Unknown"
|
|
207
|
+
fail Stone::PropertyError, "Property '#{@property}' not found for type '#{type_name}'"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private def get_string_literal(mod)
|
|
211
|
+
# If receiver is a StringLiteral, return it directly
|
|
212
|
+
return @receiver if @receiver.is_a?(StringLiteral)
|
|
213
|
+
|
|
214
|
+
# If receiver is a Reference to a string constant, look it up
|
|
215
|
+
return mod.string_constants[@receiver.identifier] if @receiver.is_a?(Reference)
|
|
216
|
+
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private def resolve_node_type(node, mod)
|
|
221
|
+
Stone::AST::TypeResolver.resolve_node_type(node, mod)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private def record_field_access?(mod)
|
|
225
|
+
# Check if receiver is a Reference to a record instance
|
|
226
|
+
return Stone::AST::RecordHelpers.record_instance?(@receiver, mod) if @receiver.is_a?(Reference)
|
|
227
|
+
|
|
228
|
+
# Check if receiver is a FunctionCall that returns a record
|
|
229
|
+
return mod.record_type?(@receiver.function_name) if @receiver.is_a?(FunctionCall)
|
|
230
|
+
|
|
231
|
+
# Check if receiver is a PropertyAccess that returns a record type
|
|
232
|
+
return receiver_property_returns_record?(mod) if @receiver.is_a?(PropertyAccess)
|
|
233
|
+
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
private def receiver_property_returns_record?(mod)
|
|
238
|
+
field_type = get_receiver_field_type(mod)
|
|
239
|
+
field_type && mod.record_type?(field_type)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Check if receiver returns a union type that contains record(s)
|
|
243
|
+
# This enables chained access like o.value.x where value is Point | Null
|
|
244
|
+
private def receiver_is_union_with_record?(mod)
|
|
245
|
+
receiver_type = safe_get_receiver_type(mod)
|
|
246
|
+
return false unless receiver_type&.union?
|
|
247
|
+
|
|
248
|
+
# Check if any non-null alternative is a record with this property
|
|
249
|
+
receiver_type.alternatives.any? do |alt|
|
|
250
|
+
next false if alt.name == "Null"
|
|
251
|
+
|
|
252
|
+
alt.record? && alt.property_return_type(@property)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Safely get the receiver's type, returning nil if type resolution fails.
|
|
257
|
+
# This prevents errors during speculative checks like receiver_is_union_with_record?.
|
|
258
|
+
private def safe_get_receiver_type(mod)
|
|
259
|
+
@receiver.type(mod)
|
|
260
|
+
rescue Stone::PropertyError, Stone::TypeError
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Access a field on a record extracted from a union type
|
|
265
|
+
# For o.value.x where value is Point | Null:
|
|
266
|
+
# The receiver (o.value) already extracts the record pointer from the union
|
|
267
|
+
# via extract_homogeneous_union, so we just use that pointer directly.
|
|
268
|
+
private def access_field_on_union_record(builder, mod, scope)
|
|
269
|
+
receiver_type = @receiver.type(mod)
|
|
270
|
+
record_type = find_record_type_in_union(receiver_type, mod)
|
|
271
|
+
record_def = mod.record_types[record_type.name]
|
|
272
|
+
|
|
273
|
+
# Get the already-extracted record pointer from the receiver
|
|
274
|
+
# (receiver.to_llir already handles union extraction)
|
|
275
|
+
record_ptr = @receiver.to_llir(builder, mod, scope)
|
|
276
|
+
|
|
277
|
+
# Load the record struct and access the field
|
|
278
|
+
record_struct = builder.load2(record_def.llvm_type(mod), record_ptr, "record_from_union")
|
|
279
|
+
field_index = record_def.field_index(@property)
|
|
280
|
+
builder.extract_value(record_struct, field_index, "#{@property}_value")
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private def find_record_type_in_union(union_type, _mod)
|
|
284
|
+
union_type.alternatives.find do |alt|
|
|
285
|
+
next false if alt.name == "Null"
|
|
286
|
+
|
|
287
|
+
alt.record? && alt.property_return_type(@property)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private def get_receiver_field_type(mod)
|
|
292
|
+
return nil unless @receiver.is_a?(PropertyAccess)
|
|
293
|
+
|
|
294
|
+
# Get the record type that the receiver's receiver is accessing
|
|
295
|
+
parent_record_type = @receiver.get_record_type_name(mod)
|
|
296
|
+
return nil unless parent_record_type
|
|
297
|
+
|
|
298
|
+
# Look up the field type for the receiver's property
|
|
299
|
+
record_def = mod.record_types[parent_record_type]
|
|
300
|
+
return nil unless record_def
|
|
301
|
+
|
|
302
|
+
field = record_def.fields.find { |f| f[:name] == @receiver.property }
|
|
303
|
+
field&.dig(:type_name)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private def access_record_field(builder, mod, scope)
|
|
307
|
+
record_def = lookup_record_definition(mod, get_record_type_name(mod))
|
|
308
|
+
field_index = get_field_index(record_def, record_def.assigned_name)
|
|
309
|
+
|
|
310
|
+
# For unions that need runtime type tag, return pointer directly into record memory
|
|
311
|
+
# instead of extracting (which loses the type tag somehow)
|
|
312
|
+
annotation = record_def.field_type_annotation(@property)
|
|
313
|
+
if Stone::AST::FieldHelpers.union_annotation?(annotation)
|
|
314
|
+
union_type = Stone::AST::FieldHelpers.resolve_field_type({type: annotation})
|
|
315
|
+
if union_type.needs_runtime_type_tag?
|
|
316
|
+
return access_union_field_by_pointer(builder, mod, scope, record_def, field_index)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
receiver_value = load_receiver_struct(builder, mod, scope, record_def)
|
|
321
|
+
field_value = builder.extract_value(receiver_value, field_index, "#{@property}_value")
|
|
322
|
+
maybe_extract_union_payload(builder, mod, field_value, record_def)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Return pointer directly to union field in record memory (for runtime type dispatch)
|
|
326
|
+
private def access_union_field_by_pointer(builder, mod, scope, record_def, field_index)
|
|
327
|
+
receiver_ptr = get_receiver_pointer(builder, mod, scope, record_def)
|
|
328
|
+
# GEP to get pointer to the union field within the record
|
|
329
|
+
builder.struct_gep2(record_def.llvm_type(mod), receiver_ptr, field_index, "union_field_ptr")
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Get pointer to receiver (record instance)
|
|
333
|
+
private def get_receiver_pointer(builder, mod, scope, record_def)
|
|
334
|
+
receiver_value = @receiver.to_llir(builder, mod, scope)
|
|
335
|
+
return receiver_value if receiver_value.type.kind == :pointer
|
|
336
|
+
|
|
337
|
+
# If receiver is a struct value, allocate and store it to get a pointer
|
|
338
|
+
ptr = builder.alloca(record_def.llvm_type(mod), "receiver_ptr")
|
|
339
|
+
builder.store(receiver_value, ptr)
|
|
340
|
+
ptr
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
private def load_receiver_struct(builder, mod, scope, record_def)
|
|
344
|
+
receiver_value = @receiver.to_llir(builder, mod, scope)
|
|
345
|
+
return receiver_value unless receiver_value.type.kind == :pointer
|
|
346
|
+
|
|
347
|
+
builder.load2(record_def.llvm_type(mod), receiver_value, "loaded_struct")
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
private def maybe_extract_union_payload(builder, mod, field_value, record_def)
|
|
351
|
+
annotation = record_def.field_type_annotation(@property)
|
|
352
|
+
return field_value unless Stone::AST::FieldHelpers.union_annotation?(annotation)
|
|
353
|
+
|
|
354
|
+
union_type = Stone::AST::FieldHelpers.resolve_field_type({type: annotation})
|
|
355
|
+
extract_union_payload(builder, mod, field_value, union_type)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
private def extract_union_payload(builder, mod, union_value, union_type)
|
|
359
|
+
# For unions that need runtime type tag (e.g., Bool | Int), return pointer to union struct.
|
|
360
|
+
# Ruby side will read both type tag and payload to properly interpret the value.
|
|
361
|
+
return return_union_struct_pointer(builder, union_value, union_type) if union_type.needs_runtime_type_tag?
|
|
362
|
+
|
|
363
|
+
# For homogeneous unions, extract the payload with phi merge
|
|
364
|
+
return extract_homogeneous_union(builder, mod, union_value, union_type) if union_type.homogeneous?
|
|
365
|
+
|
|
366
|
+
# For mixed-type unions (e.g., Int | String), extract payload as i64.
|
|
367
|
+
# Both Int (8 bytes) and String pointers (8 bytes) fit in i64.
|
|
368
|
+
# Ruby-side conversion uses the type tag to interpret the value correctly.
|
|
369
|
+
extract_mixed_union_payload(builder, mod, union_value, union_type)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
private def return_union_struct_pointer(builder, union_value, union_type)
|
|
373
|
+
# Allocate union on stack and return pointer to it.
|
|
374
|
+
# Ruby side will read type tag (field 0) and payload (field 1) to interpret correctly.
|
|
375
|
+
union_ptr = builder.alloca(union_type.llvm_type, "tagged_union_ptr")
|
|
376
|
+
builder.store(union_value, union_ptr)
|
|
377
|
+
union_ptr
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private def extract_mixed_union_payload(builder, _mod, union_value, union_type)
|
|
381
|
+
# Allocate union on stack to get pointer for GEP
|
|
382
|
+
union_ptr = builder.alloca(union_type.llvm_type, "mixed_union_for_extract")
|
|
383
|
+
builder.store(union_value, union_ptr)
|
|
384
|
+
|
|
385
|
+
# Get payload pointer (index 1 in the {ptr, [N x i8]} struct)
|
|
386
|
+
payload_ptr = builder.struct_gep2(union_type.llvm_type, union_ptr, 1, "mixed_payload_ptr")
|
|
387
|
+
|
|
388
|
+
# Load payload as i64 (works for both Int values and pointer addresses)
|
|
389
|
+
builder.load2(LLVM::Int64.type, payload_ptr, "mixed_payload_i64")
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
private def extract_homogeneous_union(builder, mod, union_value, union_type)
|
|
393
|
+
# Allocate union on stack to get pointer for GEP
|
|
394
|
+
union_ptr = builder.alloca(union_type.llvm_type, "union_for_extract")
|
|
395
|
+
builder.store(union_value, union_ptr)
|
|
396
|
+
|
|
397
|
+
# Get type tag to determine how to interpret payload
|
|
398
|
+
tag_ptr = builder.struct_gep2(union_type.llvm_type, union_ptr, 0, "extract_tag_ptr")
|
|
399
|
+
type_tag = builder.load2(LLVM::Type.pointer, tag_ptr, "type_tag")
|
|
400
|
+
|
|
401
|
+
# Get payload pointer
|
|
402
|
+
payload_ptr = builder.struct_gep2(union_type.llvm_type, union_ptr, 1, "extract_payload_ptr")
|
|
403
|
+
|
|
404
|
+
# Generate runtime type dispatch to extract payload correctly
|
|
405
|
+
extract_with_type_dispatch(builder, mod, type_tag, payload_ptr, union_type)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
private def extract_with_type_dispatch(builder, mod, type_tag, payload_ptr, union_type)
|
|
409
|
+
dispatch = TypeDispatchBuilder.new(builder, mod, union_type)
|
|
410
|
+
result_type = union_type.common_llvm_result_type
|
|
411
|
+
dispatch.build_switch(type_tag)
|
|
412
|
+
phi_incoming = dispatch.build_extraction_blocks(payload_ptr) { |alt| load_payload_as_type(builder, payload_ptr, alt, result_type) }
|
|
413
|
+
dispatch.build_merge_phi(phi_incoming)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Builds LLVM switch/phi dispatch for union type extraction
|
|
417
|
+
class TypeDispatchBuilder
|
|
418
|
+
def initialize(builder, mod, union_type)
|
|
419
|
+
@builder = builder
|
|
420
|
+
@mod = mod
|
|
421
|
+
@union_type = union_type
|
|
422
|
+
@alternatives = union_type.alternatives
|
|
423
|
+
@result_type = union_type.common_llvm_result_type
|
|
424
|
+
create_basic_blocks
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def build_switch(type_tag)
|
|
428
|
+
cases = @alternatives.each_with_index.to_h { |alt, i| [Stone::RTTI.type_constant_for(@mod, alt), @alt_blocks[i]] }
|
|
429
|
+
@builder.switch(type_tag, default_block, cases)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def build_extraction_blocks(_payload_ptr)
|
|
433
|
+
@alternatives.each_with_index.to_h do |alt, index|
|
|
434
|
+
@builder.position_at_end(@alt_blocks[index])
|
|
435
|
+
raw_value = yield(alt)
|
|
436
|
+
converted = convert_to_result_type(raw_value)
|
|
437
|
+
@builder.br(@merge_block)
|
|
438
|
+
[@alt_blocks[index], converted]
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def build_merge_phi(phi_incoming)
|
|
443
|
+
@builder.position_at_end(@merge_block)
|
|
444
|
+
@builder.phi(@result_type, phi_incoming, "extracted_payload")
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
private def create_basic_blocks
|
|
448
|
+
current_func = @builder.insert_block.parent
|
|
449
|
+
@alt_blocks = @alternatives.map { |alt| current_func.basic_blocks.append("extract_#{alt.name.downcase}") }
|
|
450
|
+
@merge_block = current_func.basic_blocks.append("extract_merge")
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
private def default_block
|
|
454
|
+
null_idx = @alternatives.index { |alt| alt.name == "Null" }
|
|
455
|
+
null_idx ? @alt_blocks[null_idx] : @alt_blocks.first
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Convert value to match @result_type for phi merge.
|
|
459
|
+
# Only called for homogeneous unions where all alternatives have compatible types.
|
|
460
|
+
# - If result is i64: integers get widened, Null returns i64(0)
|
|
461
|
+
# - If result is ptr: all values are already pointers
|
|
462
|
+
# No int2ptr or ptr2int conversions needed (CHERI-safe).
|
|
463
|
+
private def convert_to_result_type(value)
|
|
464
|
+
return value if value.type == @result_type
|
|
465
|
+
return widen_int(value) if value.type.kind == :integer && @result_type.kind == :integer
|
|
466
|
+
|
|
467
|
+
value
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private def widen_int(value)
|
|
471
|
+
value.type.width < 64 ? @builder.zext(value, LLVM::Int64.type, "zext_to_i64") : value
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
private def load_payload_as_type(builder, payload_ptr, type, result_type)
|
|
476
|
+
if type.name == "Null"
|
|
477
|
+
# Return appropriate zero value based on result type
|
|
478
|
+
return result_type.kind == :pointer ? LLVM::Type.pointer.null_pointer : LLVM::Int64.from_i(0)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
builder.load2(type.payload_llvm_type, payload_ptr, "#{type.name.downcase}_payload")
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Check if this property access is on a union-typed field (for Type.of() support)
|
|
485
|
+
def union_field_access?(mod)
|
|
486
|
+
return false unless record_field_access?(mod)
|
|
487
|
+
|
|
488
|
+
record_type_name = get_record_type_name(mod)
|
|
489
|
+
record_def = mod.record_types[record_type_name]
|
|
490
|
+
return false unless record_def
|
|
491
|
+
|
|
492
|
+
field_annotation = record_def.field_type_annotation(@property)
|
|
493
|
+
Stone::AST::FieldHelpers.union_annotation?(field_annotation)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Extract the type tag from a union field (for Type.of() support)
|
|
497
|
+
def extract_union_type_tag(builder, mod, scope)
|
|
498
|
+
record_type_name = get_record_type_name(mod)
|
|
499
|
+
record_def = lookup_record_definition(mod, record_type_name)
|
|
500
|
+
field_index = get_field_index(record_def, record_type_name)
|
|
501
|
+
|
|
502
|
+
# Evaluate the receiver to get the record struct
|
|
503
|
+
receiver_value = @receiver.to_llir(builder, mod, scope)
|
|
504
|
+
|
|
505
|
+
# If receiver is a pointer (recursive field), load the struct first
|
|
506
|
+
if receiver_value.type.kind == :pointer
|
|
507
|
+
struct_type = record_def.llvm_type(mod)
|
|
508
|
+
receiver_value = builder.load2(struct_type, receiver_value, "loaded_struct")
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Extract the union struct from the record
|
|
512
|
+
union_value = builder.extract_value(receiver_value, field_index, "#{@property}_union")
|
|
513
|
+
|
|
514
|
+
# Extract the type tag (index 0) from the union struct
|
|
515
|
+
builder.extract_value(union_value, 0, "union_type_tag")
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def returns_string_field?(mod)
|
|
519
|
+
return false unless record_field_access?(mod)
|
|
520
|
+
|
|
521
|
+
record_type_name = get_record_type_name(mod)
|
|
522
|
+
record_def = mod.record_types[record_type_name]
|
|
523
|
+
return false unless record_def
|
|
524
|
+
|
|
525
|
+
field_def = record_def.fields.find { |f| f[:name] == @property }
|
|
526
|
+
field_def && field_def[:type_name] == "String"
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def get_record_type_name(mod)
|
|
530
|
+
case @receiver
|
|
531
|
+
when Reference
|
|
532
|
+
mod.record_instance_type(@receiver.identifier)
|
|
533
|
+
when FunctionCall
|
|
534
|
+
@receiver.function_name
|
|
535
|
+
when PropertyAccess
|
|
536
|
+
# Receiver is a PropertyAccess - get the field type it returns
|
|
537
|
+
get_receiver_field_type(mod)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
private def lookup_record_definition(mod, record_type_name)
|
|
542
|
+
record_def = mod.record_types[record_type_name]
|
|
543
|
+
fail "Unknown record type: #{record_type_name}" unless record_def
|
|
544
|
+
|
|
545
|
+
record_def
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
private def get_field_index(record_def, record_type_name)
|
|
549
|
+
field_index = record_def.field_index(@property)
|
|
550
|
+
fail Stone::PropertyError, "Property '#{@property}' not found for record type '#{record_type_name}'" unless field_index
|
|
551
|
+
|
|
552
|
+
field_index
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
end
|