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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +115 -5
  3. data/LICENSE.md +2 -2
  4. data/README.md +53 -106
  5. data/lib/disable_ssl_verify.rb +5 -0
  6. data/lib/extensions/argf.rb +8 -0
  7. data/lib/extensions/boolean.rb +48 -0
  8. data/lib/extensions/class.rb +10 -0
  9. data/lib/extensions/enumerable.rb +42 -0
  10. data/lib/extensions/integer.rb +7 -0
  11. data/lib/extensions/llvm_module.rb +101 -0
  12. data/lib/extensions/module.rb +34 -0
  13. data/lib/extensions/string.rb +29 -0
  14. data/lib/stone/ast/block.rb +88 -0
  15. data/lib/stone/ast/boolean_literal.rb +43 -0
  16. data/lib/stone/ast/computed_property_definition.rb +32 -0
  17. data/lib/stone/ast/constant_definition.rb +124 -0
  18. data/lib/stone/ast/expression.rb +12 -0
  19. data/lib/stone/ast/function_call.rb +291 -0
  20. data/lib/stone/ast/function_type_annotation.rb +31 -0
  21. data/lib/stone/ast/integer_literal.rb +46 -0
  22. data/lib/stone/ast/lambda.rb +126 -0
  23. data/lib/stone/ast/null_literal.rb +29 -0
  24. data/lib/stone/ast/program_unit/top_function.rb +209 -0
  25. data/lib/stone/ast/program_unit.rb +412 -0
  26. data/lib/stone/ast/property_access.rb +557 -0
  27. data/lib/stone/ast/record_definition.rb +180 -0
  28. data/lib/stone/ast/record_instantiation.rb +141 -0
  29. data/lib/stone/ast/reference.rb +145 -0
  30. data/lib/stone/ast/string_literal.rb +79 -0
  31. data/lib/stone/ast/two_phase_processing.rb +36 -0
  32. data/lib/stone/ast/type_annotation.rb +26 -0
  33. data/lib/stone/ast/type_declaration.rb +28 -0
  34. data/lib/stone/ast/type_of_expression.rb +41 -0
  35. data/lib/stone/ast/type_reference.rb +28 -0
  36. data/lib/stone/ast/union_type_annotation.rb +26 -0
  37. data/lib/stone/ast.rb +64 -0
  38. data/lib/stone/built_ins.rb +245 -0
  39. data/lib/stone/error/argument_error.rb +7 -0
  40. data/lib/stone/error/arity_error.rb +7 -0
  41. data/lib/stone/error/overflow.rb +18 -0
  42. data/lib/stone/error/property_error.rb +7 -0
  43. data/lib/stone/error/reference_error.rb +4 -0
  44. data/lib/stone/error/type_error.rb +7 -0
  45. data/lib/stone/error.rb +12 -0
  46. data/lib/stone/grammar.rb +124 -0
  47. data/lib/stone/libc.rb +27 -0
  48. data/lib/stone/prelude.stone +11 -0
  49. data/lib/stone/rtti.rb +182 -0
  50. data/lib/stone/scope.rb +85 -0
  51. data/lib/stone/transform.rb +410 -0
  52. data/lib/stone/type.rb +323 -0
  53. data/lib/stone/type_context.rb +38 -0
  54. data/lib/stone/type_registry.rb +73 -0
  55. data/lib/stone/types.rb +104 -0
  56. data/lib/stone.rb +70 -0
  57. metadata +54 -24
  58. data/CHANGELOG.md +0 -28
  59. data/Rakefile +0 -5
  60. data/config/default.yml +0 -277
  61. data/lib/rubocop/boochtek/plugin.rb +0 -43
  62. data/lib/rubocop/boochtek/version.rb +0 -7
  63. data/lib/rubocop/boochtek.rb +0 -18
  64. data/lib/rubocop/cop/boochtek/compact_endless_methods.rb +0 -103
  65. 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