mt-lang 0.3.24 → 0.3.26

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -1
  3. data/lib/milk_tea/core/bindings/attribute_binding.rb +51 -49
  4. data/lib/milk_tea/core/bindings/function_binding.rb +3 -1
  5. data/lib/milk_tea/core/bindings/module_binding.rb +42 -40
  6. data/lib/milk_tea/core/bindings/value_binding.rb +16 -14
  7. data/lib/milk_tea/core/bindings.rb +4 -9
  8. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +11 -11
  9. data/lib/milk_tea/core/c_backend/expressions.rb +11 -11
  10. data/lib/milk_tea/core/c_backend/feature_detection.rb +15 -19
  11. data/lib/milk_tea/core/c_backend/format_helpers.rb +1 -1
  12. data/lib/milk_tea/core/c_backend/reinterpret.rb +10 -10
  13. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1 -1
  14. data/lib/milk_tea/core/c_backend/statements.rb +20 -20
  15. data/lib/milk_tea/core/c_backend/type_declaration.rb +4 -4
  16. data/lib/milk_tea/core/c_backend/type_system.rb +1 -1
  17. data/lib/milk_tea/core/c_backend.rb +3 -3
  18. data/lib/milk_tea/core/compile_time.rb +41 -70
  19. data/lib/milk_tea/core/control_flow/builder.rb +3 -3
  20. data/lib/milk_tea/core/control_flow/constant_propagation.rb +1 -1
  21. data/lib/milk_tea/core/control_flow/graph.rb +1 -1
  22. data/lib/milk_tea/core/control_flow/termination.rb +1 -1
  23. data/lib/milk_tea/core/cst.rb +16 -0
  24. data/lib/milk_tea/core/flow_scope.rb +13 -0
  25. data/lib/milk_tea/core/lexer/character_classes.rb +2 -2
  26. data/lib/milk_tea/core/lexer/format_strings.rb +5 -5
  27. data/lib/milk_tea/core/lexer/heredocs.rb +6 -6
  28. data/lib/milk_tea/core/lexer/indentation.rb +4 -4
  29. data/lib/milk_tea/core/lexer/numbers.rb +1 -1
  30. data/lib/milk_tea/core/lexer/strings.rb +4 -4
  31. data/lib/milk_tea/core/lexer/symbols.rb +3 -3
  32. data/lib/milk_tea/core/lexer/trivia.rb +4 -4
  33. data/lib/milk_tea/core/lexer.rb +17 -17
  34. data/lib/milk_tea/core/lowering/async/analysis.rb +210 -208
  35. data/lib/milk_tea/core/lowering/async/async_lowering.rb +1406 -0
  36. data/lib/milk_tea/core/lowering/async/frame_builder.rb +615 -613
  37. data/lib/milk_tea/core/lowering/async/normalization.rb +410 -408
  38. data/lib/milk_tea/core/lowering/block.rb +982 -980
  39. data/lib/milk_tea/core/lowering/calls.rb +1360 -1358
  40. data/lib/milk_tea/core/lowering/declarations.rb +175 -173
  41. data/lib/milk_tea/core/lowering/dyn.rb +171 -169
  42. data/lib/milk_tea/core/lowering/events.rb +946 -944
  43. data/lib/milk_tea/core/lowering/expressions.rb +1514 -1512
  44. data/lib/milk_tea/core/lowering/foreign_cstr.rb +180 -178
  45. data/lib/milk_tea/core/lowering/functions.rb +186 -184
  46. data/lib/milk_tea/core/lowering/loops.rb +1004 -1002
  47. data/lib/milk_tea/core/lowering/lowering_context.rb +1 -1
  48. data/lib/milk_tea/core/lowering/proc.rb +355 -353
  49. data/lib/milk_tea/core/lowering/resolve.rb +2127 -2125
  50. data/lib/milk_tea/core/lowering/scans.rb +162 -168
  51. data/lib/milk_tea/core/lowering/str_buffer.rb +104 -102
  52. data/lib/milk_tea/core/lowering/utils.rb +964 -963
  53. data/lib/milk_tea/core/lowering.rb +22 -22
  54. data/lib/milk_tea/core/module_binder.rb +1 -1
  55. data/lib/milk_tea/core/module_loader.rb +2 -2
  56. data/lib/milk_tea/core/parser/attributes.rb +1 -1
  57. data/lib/milk_tea/core/parser/blocks.rb +2 -2
  58. data/lib/milk_tea/core/parser/declarations.rb +15 -15
  59. data/lib/milk_tea/core/parser/expressions.rb +20 -20
  60. data/lib/milk_tea/core/parser/recovery.rb +1 -1
  61. data/lib/milk_tea/core/parser/statements.rb +3 -3
  62. data/lib/milk_tea/core/parser/types.rb +4 -4
  63. data/lib/milk_tea/core/parser.rb +39 -21
  64. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +6 -6
  65. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +5 -5
  66. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -2
  67. data/lib/milk_tea/core/semantic_analyzer/calls.rb +1 -1
  68. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +4 -4
  69. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +37 -37
  70. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +4 -4
  71. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +4 -4
  72. data/lib/milk_tea/core/semantic_analyzer/generics.rb +4 -4
  73. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +7 -7
  74. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +59 -59
  75. data/lib/milk_tea/core/semantic_analyzer/statements.rb +3 -3
  76. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +1 -1
  77. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +12 -12
  78. data/lib/milk_tea/core/types/layout.rb +3 -1
  79. data/lib/milk_tea/core/types/visitor.rb +351 -349
  80. data/lib/milk_tea/core.rb +0 -2
  81. data/lib/milk_tea/tooling/cli/commands/bindgen.rb +1 -1
  82. data/lib/milk_tea/tooling/cli/commands/check.rb +5 -5
  83. data/lib/milk_tea/tooling/cli/commands/debug.rb +3 -3
  84. data/lib/milk_tea/tooling/cli/commands/deps.rb +1 -1
  85. data/lib/milk_tea/tooling/cli/commands/emit_c.rb +2 -2
  86. data/lib/milk_tea/tooling/cli/commands/format.rb +6 -6
  87. data/lib/milk_tea/tooling/cli/commands/lint.rb +3 -3
  88. data/lib/milk_tea/tooling/cli/commands/lower.rb +3 -3
  89. data/lib/milk_tea/tooling/cli/commands/parse.rb +3 -3
  90. data/lib/milk_tea/tooling/cli/commands/test.rb +3 -3
  91. data/lib/milk_tea/tooling/cli/commands/toolchain.rb +1 -1
  92. data/lib/milk_tea/tooling/cli.rb +22 -34
  93. data/lib/milk_tea/tooling/linter/flow_rules.rb +3 -3
  94. metadata +4 -5
  95. data/lib/milk_tea/core/cst_builder.rb +0 -19
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +0 -1404
  97. data/lib/milk_tea/core/token_stream.rb +0 -30
@@ -1,1773 +1,1795 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MilkTea
4
- module LowererExpressions
5
- def prepare_expression_with_cleanups(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
6
- env[:prepared_expression_cleanups] ||= []
7
- start_index = env[:prepared_expression_cleanups].length
8
- setup, prepared_expression = prepare_expression_for_inline_lowering(
9
- expression,
10
- env:,
11
- expected_type:,
12
- allow_root_statement_foreign:,
13
- materialize_array_calls:,
14
- allow_void_propagation:,
15
- )
16
- cleanup_count = env[:prepared_expression_cleanups].length - start_index
17
- cleanups = cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(start_index, cleanup_count) : []
18
- [setup, prepared_expression, cleanups || []]
19
- end
20
-
21
- def prepare_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
22
- return [[], expression] unless expression
23
-
24
- if expression.is_a?(AST::Call) &&
25
- (foreign_call = foreign_call_info(expression, env)) && !allow_root_statement_foreign &&
26
- foreign_call_requires_statement_lowering?(expression, foreign_call[:binding], env:)
27
- type = infer_expression_type(expression, env:, expected_type:)
28
- setup, value = lower_foreign_call_statement(foreign_call, env:, expected_type: type, statement_position: false)
29
- return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
30
- end
31
-
32
- case expression
33
- when AST::FormatString
34
- prepare_format_string_expression_for_inline_lowering(expression, env:)
35
- when AST::MemberAccess
36
- receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
37
- [receiver_setup, AST::MemberAccess.new(receiver:, member: expression.member)]
38
- when AST::IndexAccess
39
- receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
40
- index_setup, index = prepare_expression_for_inline_lowering(expression.index, env:)
41
- [receiver_setup + index_setup, AST::IndexAccess.new(receiver:, index:)]
42
- when AST::UnaryOp
43
- return prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: allow_void_propagation) if expression.operator == "?"
44
-
45
- operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type:)
46
- [operand_setup, AST::UnaryOp.new(operator: expression.operator, operand:)]
47
- when AST::BinaryOp
48
- prepare_binary_expression_for_inline_lowering(expression, env:, expected_type:)
49
- when AST::IfExpr
50
- prepare_if_expression_for_inline_lowering(expression, env:, expected_type:)
51
- when AST::MatchExpr
52
- prepare_match_expression_for_inline_lowering(expression, env:, expected_type:)
53
- when AST::UnsafeExpr
54
- prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
55
- when AST::Call
56
- prepare_call_expression_for_inline_lowering(
4
+ module Lowering
5
+ module Expressions
6
+ def prepare_expression_with_cleanups(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
7
+ env[:prepared_expression_cleanups] ||= []
8
+ start_index = env[:prepared_expression_cleanups].length
9
+ setup, prepared_expression = prepare_expression_for_inline_lowering(
57
10
  expression,
58
11
  env:,
59
12
  expected_type:,
60
13
  allow_root_statement_foreign:,
61
14
  materialize_array_calls:,
15
+ allow_void_propagation:,
62
16
  )
63
- when AST::ProcExpr
64
- proc_type = infer_expression_type(expression, env:, expected_type:)
65
- setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
66
- materialize_prepared_expression(setup, value, env:, type: proc_type, prefix: "proc_expr")
67
- when AST::PrefixCast
68
- setup, prepared_expr = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
69
- [setup, AST::PrefixCast.new(target_type: expression.target_type, expression: prepared_expr)]
70
- else
71
- [[], expression]
17
+ cleanup_count = env[:prepared_expression_cleanups].length - start_index
18
+ cleanups = cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(start_index, cleanup_count) : []
19
+ [setup, prepared_expression, cleanups || []]
72
20
  end
73
- end
74
21
 
75
- def prepare_call_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true)
76
- kind, _callee_name, _receiver, callee_type, binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
22
+ def prepare_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true, allow_void_propagation: false)
23
+ return [[], expression] unless expression
77
24
 
78
- if binding && binding.respond_to?(:ast) && kind != :variant_arm_ctor && foreign_function_binding?(binding) && !allow_root_statement_foreign && foreign_call_requires_statement_lowering?(expression, binding, env:)
79
- type = infer_expression_type(expression, env:, expected_type:)
80
- setup, value = lower_foreign_call_statement({ call: expression, binding: binding }, env:, expected_type: type, statement_position: false)
81
- return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
82
- end
25
+ if expression.is_a?(AST::Call) &&
26
+ (foreign_call = foreign_call_info(expression, env)) && !allow_root_statement_foreign &&
27
+ foreign_call_requires_statement_lowering?(expression, foreign_call[:binding], env:)
28
+ type = infer_expression_type(expression, env:, expected_type:)
29
+ setup, value = lower_foreign_call_statement(foreign_call, env:, expected_type: type, statement_position: false)
30
+ return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
31
+ end
83
32
 
84
- callee_setup, callee = prepare_expression_for_inline_lowering(expression.callee, env:)
85
- argument_setup = []
86
- arguments = expression.arguments.map.with_index do |argument, index|
87
- expected_arg_type = kind == :function || kind == :method || kind == :associated_method || kind == :callable_value ?
88
- (index < callee_type.params.length ? callee_type.params[index].type : nil) : nil
89
- argument_value = argument.value
90
- argument_value = wrap_task_expression_in_root_proc(argument_value, env:) if task_expression_root_proc_bridge?(argument_value, expected_arg_type, env:)
91
- argument_value = wrap_expression_in_ref_of(argument_value) if implicit_ref_argument_bridge?(argument_value, expected_arg_type, env:)
92
- setup, prepared_value = prepare_expression_for_inline_lowering(argument_value, env:, expected_type: expected_arg_type)
93
- argument_setup.concat(setup)
94
- AST::Argument.new(name: argument.name, value: prepared_value)
33
+ case expression
34
+ when AST::FormatString
35
+ prepare_format_string_expression_for_inline_lowering(expression, env:)
36
+ when AST::MemberAccess
37
+ receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
38
+ [receiver_setup, AST::MemberAccess.new(receiver:, member: expression.member)]
39
+ when AST::IndexAccess
40
+ receiver_setup, receiver = prepare_expression_for_inline_lowering(expression.receiver, env:)
41
+ index_setup, index = prepare_expression_for_inline_lowering(expression.index, env:)
42
+ [receiver_setup + index_setup, AST::IndexAccess.new(receiver:, index:)]
43
+ when AST::UnaryOp
44
+ return prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: allow_void_propagation) if expression.operator == "?"
45
+
46
+ operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type:)
47
+ [operand_setup, AST::UnaryOp.new(operator: expression.operator, operand:)]
48
+ when AST::BinaryOp
49
+ prepare_binary_expression_for_inline_lowering(expression, env:, expected_type:)
50
+ when AST::IfExpr
51
+ prepare_if_expression_for_inline_lowering(expression, env:, expected_type:)
52
+ when AST::MatchExpr
53
+ prepare_match_expression_for_inline_lowering(expression, env:, expected_type:)
54
+ when AST::UnsafeExpr
55
+ prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
56
+ when AST::Call
57
+ prepare_call_expression_for_inline_lowering(
58
+ expression,
59
+ env:,
60
+ expected_type:,
61
+ allow_root_statement_foreign:,
62
+ materialize_array_calls:,
63
+ )
64
+ when AST::ProcExpr
65
+ proc_type = infer_expression_type(expression, env:, expected_type:)
66
+ setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
67
+ materialize_prepared_expression(setup, value, env:, type: proc_type, prefix: "proc_expr")
68
+ when AST::PrefixCast
69
+ setup, prepared_expr = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type:)
70
+ [setup, AST::PrefixCast.new(target_type: expression.target_type, expression: prepared_expr)]
71
+ else
72
+ [[], expression]
73
+ end
95
74
  end
96
75
 
97
- prepared_call = AST::Call.new(callee:, arguments:)
98
- return [callee_setup + argument_setup, prepared_call] unless materialize_array_calls && callee_type.respond_to?(:return_type) && array_type?(callee_type.return_type)
99
-
100
- call_type = infer_expression_type(prepared_call, env:, expected_type:)
101
- materialize_prepared_expression(
102
- callee_setup + argument_setup,
103
- lower_expression(prepared_call, env:, expected_type: call_type),
104
- env:,
105
- type: call_type,
106
- prefix: "array_call",
107
- )
108
- end
76
+ def prepare_call_expression_for_inline_lowering(expression, env:, expected_type: nil, allow_root_statement_foreign: false, materialize_array_calls: true)
77
+ kind, _callee_name, _receiver, callee_type, binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
109
78
 
110
- def prepare_format_string_expression_for_inline_lowering(format_string, env:)
111
- unless format_string_has_dynamic_parts?(format_string)
112
- return [[], AST::StringLiteral.new(lexeme: "", value: format_string_static_text(format_string), cstring: false)]
113
- end
79
+ if binding && binding.respond_to?(:ast) && kind != :variant_arm_ctor && foreign_function_binding?(binding) && !allow_root_statement_foreign && foreign_call_requires_statement_lowering?(expression, binding, env:)
80
+ type = infer_expression_type(expression, env:, expected_type:)
81
+ setup, value = lower_foreign_call_statement({ call: expression, binding: binding }, env:, expected_type: type, statement_position: false)
82
+ return materialize_prepared_expression(setup, value, env:, type:, prefix: "foreign_expr")
83
+ end
114
84
 
115
- setup, temp_name = build_dynamic_format_string_temp_setup(format_string, env:)
116
- temp_value = IR::Name.new(name: temp_name, type: @ctx.types.fetch("str"), pointer: false)
117
- (env[:prepared_expression_cleanups] ||= []) << [
118
- IR::ExpressionStmt.new(
119
- expression: IR::Call.new(
120
- callee: "mt_format_str_release",
121
- arguments: [temp_value],
122
- type: @ctx.types.fetch("void"),
123
- ),
124
- ),
125
- ]
85
+ callee_setup, callee = prepare_expression_for_inline_lowering(expression.callee, env:)
86
+ argument_setup = []
87
+ arguments = expression.arguments.map.with_index do |argument, index|
88
+ expected_arg_type = kind == :function || kind == :method || kind == :associated_method || kind == :callable_value ?
89
+ (index < callee_type.params.length ? callee_type.params[index].type : nil) : nil
90
+ argument_value = argument.value
91
+ argument_value = wrap_task_expression_in_root_proc(argument_value, env:) if task_expression_root_proc_bridge?(argument_value, expected_arg_type, env:)
92
+ argument_value = wrap_expression_in_ref_of(argument_value) if implicit_ref_argument_bridge?(argument_value, expected_arg_type, env:)
93
+ setup, prepared_value = prepare_expression_for_inline_lowering(argument_value, env:, expected_type: expected_arg_type)
94
+ argument_setup.concat(setup)
95
+ AST::Argument.new(name: argument.name, value: prepared_value)
96
+ end
126
97
 
127
- [setup, AST::Identifier.new(name: temp_name)]
128
- end
98
+ prepared_call = AST::Call.new(callee:, arguments:)
99
+ return [callee_setup + argument_setup, prepared_call] unless materialize_array_calls && callee_type.respond_to?(:return_type) && array_type?(callee_type.return_type)
129
100
 
130
- def build_dynamic_format_string_temp_setup(format_string, env:)
131
- string_type = @ctx.types.fetch("str")
132
- dest_name = env[:current_local_name] || env[:lowering_target_name]
133
- if dest_name
134
- env[:fmt_counter] ||= {}
135
- env[:fmt_counter][dest_name] = (env[:fmt_counter][dest_name] || 0) + 1
136
- suffix = env[:fmt_counter][dest_name] > 1 ? "_#{env[:fmt_counter][dest_name]}" : ""
137
- base = "__fmt_#{dest_name}#{suffix}"
138
- else
139
- base = fresh_c_temp_name(env, "fmt_str")
140
- end
141
- temp_name = base
142
- cap_name = "#{base}_cap"
143
- off_name = "#{base}_off"
144
- register_prepared_temp!(env, temp_name, string_type, cstr_backed: true)
145
- total_cap_value = IR::Name.new(name: cap_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
146
- result_value = IR::Name.new(name: temp_name, type: string_type, pointer: false)
147
- offset_value = IR::Name.new(name: off_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
148
-
149
- setup, format_parts = build_dynamic_format_string_parts(format_string, env:)
150
- literal_capacity = format_parts.sum { |part| part[:kind] == :text ? part[:value].bytesize : 0 }
151
-
152
- setup << IR::LocalDecl.new(
153
- name: cap_name, linkage_name: cap_name, type: @ctx.types.fetch("ptr_uint"),
154
- value: IR::IntegerLiteral.new(value: literal_capacity, type: @ctx.types.fetch("ptr_uint")),
155
- )
156
-
157
- format_parts.each do |part|
158
- next if part[:kind] == :text
159
- part_len = format_string_part_length_expression(part, env:)
160
- setup << IR::Assignment.new(
161
- target: total_cap_value, operator: "=",
162
- value: IR::Binary.new(operator: "+", left: total_cap_value, right: part_len, type: @ctx.types.fetch("ptr_uint")),
101
+ call_type = infer_expression_type(prepared_call, env:, expected_type:)
102
+ materialize_prepared_expression(
103
+ callee_setup + argument_setup,
104
+ lower_expression(prepared_call, env:, expected_type: call_type),
105
+ env:,
106
+ type: call_type,
107
+ prefix: "array_call",
163
108
  )
164
109
  end
165
110
 
166
- setup << IR::LocalDecl.new(
167
- name: temp_name, linkage_name: temp_name, type: string_type,
168
- value: IR::Call.new(callee: "mt_format_str_make", arguments: [total_cap_value], type: string_type),
169
- )
170
- setup << IR::LocalDecl.new(
171
- name: off_name, linkage_name: off_name, type: @ctx.types.fetch("ptr_uint"),
172
- value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")),
173
- )
174
-
175
- format_parts.each do |part|
176
- setup.concat(format_string_part_append_statements(part, result_value, offset_value, env:))
111
+ def prepare_format_string_expression_for_inline_lowering(format_string, env:)
112
+ unless format_string_has_dynamic_parts?(format_string)
113
+ return [[], AST::StringLiteral.new(lexeme: "", value: format_string_static_text(format_string), cstring: false)]
114
+ end
115
+
116
+ setup, temp_name = build_dynamic_format_string_temp_setup(format_string, env:)
117
+ temp_value = IR::Name.new(name: temp_name, type: @ctx.types.fetch("str"), pointer: false)
118
+ (env[:prepared_expression_cleanups] ||= []) << [
119
+ IR::ExpressionStmt.new(
120
+ expression: IR::Call.new(
121
+ callee: "mt_format_str_release",
122
+ arguments: [temp_value],
123
+ type: @ctx.types.fetch("void"),
124
+ ),
125
+ ),
126
+ ]
127
+
128
+ [setup, AST::Identifier.new(name: temp_name)]
177
129
  end
178
130
 
179
- [setup, temp_name]
180
- end
131
+ def build_dynamic_format_string_temp_setup(format_string, env:)
132
+ string_type = @ctx.types.fetch("str")
133
+ dest_name = env[:current_local_name] || env[:lowering_target_name]
134
+ if dest_name
135
+ env[:fmt_counter] ||= {}
136
+ env[:fmt_counter][dest_name] = (env[:fmt_counter][dest_name] || 0) + 1
137
+ suffix = env[:fmt_counter][dest_name] > 1 ? "_#{env[:fmt_counter][dest_name]}" : ""
138
+ base = "__fmt_#{dest_name}#{suffix}"
139
+ else
140
+ base = fresh_c_temp_name(env, "fmt_str")
141
+ end
142
+ temp_name = base
143
+ cap_name = "#{base}_cap"
144
+ off_name = "#{base}_off"
145
+ register_prepared_temp!(env, temp_name, string_type, cstr_backed: true)
146
+ total_cap_value = IR::Name.new(name: cap_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
147
+ result_value = IR::Name.new(name: temp_name, type: string_type, pointer: false)
148
+ offset_value = IR::Name.new(name: off_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
181
149
 
182
- def format_string_static_text(format_string)
183
- format_string.parts.filter_map do |part|
184
- next unless part.is_a?(AST::FormatTextPart)
150
+ setup, format_parts = build_dynamic_format_string_parts(format_string, env:)
151
+ literal_capacity = format_parts.sum { |part| part[:kind] == :text ? part[:value].bytesize : 0 }
185
152
 
186
- part.value
187
- end.join
188
- end
153
+ setup << IR::LocalDecl.new(
154
+ name: cap_name, linkage_name: cap_name, type: @ctx.types.fetch("ptr_uint"),
155
+ value: IR::IntegerLiteral.new(value: literal_capacity, type: @ctx.types.fetch("ptr_uint")),
156
+ )
189
157
 
190
- def build_dynamic_format_string_parts(format_string, env:)
191
- format_parts = []
192
- setup = []
158
+ format_parts.each do |part|
159
+ next if part[:kind] == :text
160
+ part_len = format_string_part_length_expression(part, env:)
161
+ setup << IR::Assignment.new(
162
+ target: total_cap_value, operator: "=",
163
+ value: IR::Binary.new(operator: "+", left: total_cap_value, right: part_len, type: @ctx.types.fetch("ptr_uint")),
164
+ )
165
+ end
193
166
 
194
- format_string.parts.each do |part|
195
- if part.is_a?(AST::FormatTextPart)
196
- next if part.value.empty?
167
+ setup << IR::LocalDecl.new(
168
+ name: temp_name, linkage_name: temp_name, type: string_type,
169
+ value: IR::Call.new(callee: "mt_format_str_make", arguments: [total_cap_value], type: string_type),
170
+ )
171
+ setup << IR::LocalDecl.new(
172
+ name: off_name, linkage_name: off_name, type: @ctx.types.fetch("ptr_uint"),
173
+ value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")),
174
+ )
197
175
 
198
- format_parts << { kind: :text, value: part.value }
199
- next
176
+ format_parts.each do |part|
177
+ setup.concat(format_string_part_append_statements(part, result_value, offset_value, env:))
200
178
  end
201
179
 
202
- expression_setup, prepared_expression = prepare_expression_for_inline_lowering(part.expression, env:)
203
- setup.concat(expression_setup)
204
- value_type = infer_expression_type(prepared_expression, env:)
180
+ [setup, temp_name]
181
+ end
205
182
 
206
- if part.format_spec
207
- case part.format_spec[:kind]
208
- when :precision
209
- precision = part.format_spec[:value]
210
- append_argument_type = @ctx.types.fetch("double")
211
- parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
212
- setup << IR::LocalDecl.new(
213
- name: parameter_linkage_name,
214
- linkage_name: parameter_linkage_name,
215
- type: append_argument_type,
216
- value: cast_expression(
217
- lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
218
- append_argument_type,
219
- ),
220
- )
221
- format_parts << {
222
- kind: :precision_expression,
223
- append_function_name: "append_double_precision",
224
- parameter_linkage_name: parameter_linkage_name,
225
- parameter_type: append_argument_type,
226
- precision: precision,
227
- }
228
- when :hex
229
- append_function_name, append_argument_type = format_string_hex_append_plan(value_type, uppercase: part.format_spec[:uppercase])
230
- parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
231
- setup << IR::LocalDecl.new(
232
- name: parameter_linkage_name,
233
- linkage_name: parameter_linkage_name,
234
- type: append_argument_type,
235
- value: cast_expression(
236
- lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
237
- append_argument_type,
238
- ),
239
- )
240
- format_parts << {
241
- kind: :expression,
242
- append_function_name: append_function_name,
243
- parameter_linkage_name: parameter_linkage_name,
244
- parameter_type: append_argument_type,
245
- }
246
- when :oct
247
- append_function_name, append_argument_type = format_string_oct_append_plan(value_type, uppercase: part.format_spec[:uppercase])
248
- parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
249
- setup << IR::LocalDecl.new(
250
- name: parameter_linkage_name,
251
- linkage_name: parameter_linkage_name,
252
- type: append_argument_type,
253
- value: cast_expression(
254
- lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
255
- append_argument_type,
256
- ),
257
- )
258
- format_parts << {
259
- kind: :expression,
260
- append_function_name: append_function_name,
261
- parameter_linkage_name: parameter_linkage_name,
262
- parameter_type: append_argument_type,
263
- }
264
- when :bin
265
- append_function_name, append_argument_type = format_string_bin_append_plan(value_type, uppercase: part.format_spec[:uppercase])
183
+ def format_string_static_text(format_string)
184
+ format_string.parts.filter_map do |part|
185
+ next unless part.is_a?(AST::FormatTextPart)
186
+
187
+ part.value
188
+ end.join
189
+ end
190
+
191
+ def build_dynamic_format_string_parts(format_string, env:)
192
+ format_parts = []
193
+ setup = []
194
+
195
+ format_string.parts.each do |part|
196
+ if part.is_a?(AST::FormatTextPart)
197
+ next if part.value.empty?
198
+
199
+ format_parts << { kind: :text, value: part.value }
200
+ next
201
+ end
202
+
203
+ expression_setup, prepared_expression = prepare_expression_for_inline_lowering(part.expression, env:)
204
+ setup.concat(expression_setup)
205
+ value_type = infer_expression_type(prepared_expression, env:)
206
+
207
+ if part.format_spec
208
+ case part.format_spec[:kind]
209
+ when :precision
210
+ precision = part.format_spec[:value]
211
+ append_argument_type = @ctx.types.fetch("double")
212
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
213
+ setup << IR::LocalDecl.new(
214
+ name: parameter_linkage_name,
215
+ linkage_name: parameter_linkage_name,
216
+ type: append_argument_type,
217
+ value: cast_expression(
218
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
219
+ append_argument_type,
220
+ ),
221
+ )
222
+ format_parts << {
223
+ kind: :precision_expression,
224
+ append_function_name: "append_double_precision",
225
+ parameter_linkage_name: parameter_linkage_name,
226
+ parameter_type: append_argument_type,
227
+ precision: precision,
228
+ }
229
+ when :hex
230
+ append_function_name, append_argument_type = format_string_hex_append_plan(value_type, uppercase: part.format_spec[:uppercase])
231
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
232
+ setup << IR::LocalDecl.new(
233
+ name: parameter_linkage_name,
234
+ linkage_name: parameter_linkage_name,
235
+ type: append_argument_type,
236
+ value: cast_expression(
237
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
238
+ append_argument_type,
239
+ ),
240
+ )
241
+ format_parts << {
242
+ kind: :expression,
243
+ append_function_name: append_function_name,
244
+ parameter_linkage_name: parameter_linkage_name,
245
+ parameter_type: append_argument_type,
246
+ }
247
+ when :oct
248
+ append_function_name, append_argument_type = format_string_oct_append_plan(value_type, uppercase: part.format_spec[:uppercase])
249
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
250
+ setup << IR::LocalDecl.new(
251
+ name: parameter_linkage_name,
252
+ linkage_name: parameter_linkage_name,
253
+ type: append_argument_type,
254
+ value: cast_expression(
255
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
256
+ append_argument_type,
257
+ ),
258
+ )
259
+ format_parts << {
260
+ kind: :expression,
261
+ append_function_name: append_function_name,
262
+ parameter_linkage_name: parameter_linkage_name,
263
+ parameter_type: append_argument_type,
264
+ }
265
+ when :bin
266
+ append_function_name, append_argument_type = format_string_bin_append_plan(value_type, uppercase: part.format_spec[:uppercase])
267
+ parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
268
+ setup << IR::LocalDecl.new(
269
+ name: parameter_linkage_name,
270
+ linkage_name: parameter_linkage_name,
271
+ type: append_argument_type,
272
+ value: cast_expression(
273
+ lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
274
+ append_argument_type,
275
+ ),
276
+ )
277
+ format_parts << {
278
+ kind: :expression,
279
+ append_function_name: append_function_name,
280
+ parameter_linkage_name: parameter_linkage_name,
281
+ parameter_type: append_argument_type,
282
+ }
283
+ else
284
+ raise LoweringError.new("unsupported format spec #{part.format_spec.inspect}", line: 0, column: 0, path: @ctx.current_analysis_path)
285
+ end
286
+ else
287
+ append_plan = format_string_append_plan(value_type, context: "formatted string interpolation of #{value_type}")
266
288
  parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
267
289
  setup << IR::LocalDecl.new(
268
290
  name: parameter_linkage_name,
269
291
  linkage_name: parameter_linkage_name,
270
- type: append_argument_type,
292
+ type: append_plan[:append_argument_type],
271
293
  value: cast_expression(
272
294
  lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
273
- append_argument_type,
295
+ append_plan[:append_argument_type],
274
296
  ),
275
297
  )
276
- format_parts << {
277
- kind: :expression,
278
- append_function_name: append_function_name,
279
- parameter_linkage_name: parameter_linkage_name,
280
- parameter_type: append_argument_type,
281
- }
282
- else
283
- raise LoweringError.new("unsupported format spec #{part.format_spec.inspect}", line: 0, column: 0, path: @ctx.current_analysis_path)
284
- end
285
- else
286
- append_plan = format_string_append_plan(value_type, context: "formatted string interpolation of #{value_type}")
287
- parameter_linkage_name = fresh_c_temp_name(env, "fmt_part")
288
- setup << IR::LocalDecl.new(
289
- name: parameter_linkage_name,
290
- linkage_name: parameter_linkage_name,
291
- type: append_plan[:append_argument_type],
292
- value: cast_expression(
293
- lower_contextual_expression(prepared_expression, env:, expected_type: value_type),
294
- append_plan[:append_argument_type],
295
- ),
296
- )
297
298
 
298
- if append_plan[:kind] == :custom
299
- register_prepared_temp!(env, parameter_linkage_name, append_plan[:append_argument_type])
300
- part_info = {
301
- kind: :custom_expression,
302
- parameter_linkage_name: parameter_linkage_name,
303
- parameter_type: append_plan[:append_argument_type],
304
- format_binding: append_plan[:binding],
305
- append_output_type: append_plan[:append_output_type],
306
- }
307
- expected_length_linkage_name = fresh_c_temp_name(env, "fmt_part_len")
308
- setup << IR::LocalDecl.new(
309
- name: expected_length_linkage_name,
310
- linkage_name: expected_length_linkage_name,
311
- type: @ctx.types.fetch("ptr_uint"),
312
- value: IR::Call.new(
313
- callee: append_plan[:binding].length_callee_name,
314
- arguments: [format_string_custom_receiver_argument(part_info, hook: :length, env:)],
299
+ if append_plan[:kind] == :custom
300
+ register_prepared_temp!(env, parameter_linkage_name, append_plan[:append_argument_type])
301
+ part_info = {
302
+ kind: :custom_expression,
303
+ parameter_linkage_name: parameter_linkage_name,
304
+ parameter_type: append_plan[:append_argument_type],
305
+ format_binding: append_plan[:binding],
306
+ append_output_type: append_plan[:append_output_type],
307
+ }
308
+ expected_length_linkage_name = fresh_c_temp_name(env, "fmt_part_len")
309
+ setup << IR::LocalDecl.new(
310
+ name: expected_length_linkage_name,
311
+ linkage_name: expected_length_linkage_name,
315
312
  type: @ctx.types.fetch("ptr_uint"),
316
- ),
317
- )
318
- format_parts << part_info.merge(expected_length_linkage_name:)
319
- else
320
- format_parts << {
321
- kind: :expression,
322
- append_function_name: append_plan[:append_function_name],
323
- parameter_linkage_name: parameter_linkage_name,
324
- parameter_type: append_plan[:append_argument_type],
325
- }
313
+ value: IR::Call.new(
314
+ callee: append_plan[:binding].length_callee_name,
315
+ arguments: [format_string_custom_receiver_argument(part_info, hook: :length, env:)],
316
+ type: @ctx.types.fetch("ptr_uint"),
317
+ ),
318
+ )
319
+ format_parts << part_info.merge(expected_length_linkage_name:)
320
+ else
321
+ format_parts << {
322
+ kind: :expression,
323
+ append_function_name: append_plan[:append_function_name],
324
+ parameter_linkage_name: parameter_linkage_name,
325
+ parameter_type: append_plan[:append_argument_type],
326
+ }
327
+ end
326
328
  end
327
329
  end
328
- end
329
-
330
- [setup, format_parts]
331
- end
332
-
333
- def format_string_has_dynamic_parts?(format_string)
334
- format_string.parts.any? { |part| part.is_a?(AST::FormatExprPart) }
335
- end
336
330
 
337
- def explicit_format_sink_call_info(expression, env)
338
- return unless expression.is_a?(AST::Call)
331
+ [setup, format_parts]
332
+ end
339
333
 
340
- kind, _callee_name, receiver, callee_type, callee_binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
334
+ def format_string_has_dynamic_parts?(format_string)
335
+ format_string.parts.any? { |part| part.is_a?(AST::FormatExprPart) }
336
+ end
341
337
 
342
- case kind
343
- when :function
344
- return unless callee_binding&.owner&.module_name == "std.fmt"
345
- return unless expression.arguments.length == 2
338
+ def explicit_format_sink_call_info(expression, env)
339
+ return unless expression.is_a?(AST::Call)
346
340
 
347
- operation = case callee_binding.name
348
- when "append_format"
349
- :append
350
- when "assign_format"
351
- :assign
352
- end
353
- return unless operation
341
+ kind, _callee_name, receiver, callee_type, callee_binding = resolve_callee(expression.callee, env, arguments: expression.arguments)
354
342
 
355
- format_string = expression.arguments.fetch(1).value
356
- return unless format_string.is_a?(AST::FormatString)
343
+ case kind
344
+ when :function
345
+ return unless callee_binding&.owner&.module_name == "std.fmt"
346
+ return unless expression.arguments.length == 2
357
347
 
358
- {
359
- operation:,
360
- sink_expression: expression.arguments.fetch(0).value,
361
- sink_expected_type: callee_type.params.fetch(0).type,
362
- format_string:,
363
- sink_kind: :string,
364
- method_call: false,
365
- callee_type:,
366
- callee_binding:,
367
- }
368
- when :method
369
- return unless callee_binding&.owner&.module_name == "std.string"
370
- return unless string_builder_type?(callee_type.receiver_type)
371
- return unless expression.arguments.length == 1
372
-
373
- operation = case callee_binding.name
374
- when "append_format"
375
- :append
376
- when "assign_format"
377
- :assign
378
- end
379
- return unless operation
380
-
381
- format_string = expression.arguments.fetch(0).value
382
- return unless format_string.is_a?(AST::FormatString)
383
-
384
- {
385
- operation:,
386
- sink_expression: receiver,
387
- sink_expected_type: callee_type.receiver_type,
388
- format_string:,
389
- sink_kind: :string,
390
- method_call: true,
391
- callee_type:,
392
- callee_binding:,
393
- }
394
- when :str_buffer_append_format, :str_buffer_assign_format
395
- return unless expression.arguments.length == 1
396
-
397
- format_string = expression.arguments.fetch(0).value
398
- return unless format_string.is_a?(AST::FormatString)
399
-
400
- {
401
- operation: kind == :str_buffer_assign_format ? :assign : :append,
402
- sink_expression: receiver,
403
- sink_expected_type: callee_type.receiver_type,
404
- format_string:,
405
- sink_kind: :str_buffer,
406
- method_call: false,
407
- callee_type:,
408
- callee_binding: nil,
409
- }
410
- end
411
- end
412
-
413
- def explicit_format_sink_target(info, prepared_sink_expression, env:)
414
- case info[:sink_kind]
415
- when :string
416
- sink_value = if info[:method_call]
417
- lower_method_receiver_argument(prepared_sink_expression, info[:callee_type], info[:callee_binding], env:)
418
- else
419
- lower_contextual_expression(prepared_sink_expression, env:, expected_type: info[:sink_expected_type])
348
+ operation = case callee_binding.name
349
+ when "append_format"
350
+ :append
351
+ when "assign_format"
352
+ :assign
420
353
  end
421
-
422
- { kind: :string, value: sink_value }
423
- when :str_buffer
424
- lowered_receiver = lower_expression(prepared_sink_expression, env:)
425
- {
426
- kind: :str_buffer,
427
- receiver: lowered_receiver,
428
- data_pointer: lower_str_buffer_data_pointer_from_lowered(lowered_receiver),
429
- len_pointer: lower_str_buffer_len_pointer_from_lowered(lowered_receiver),
430
- dirty_pointer: lower_str_buffer_dirty_pointer_from_lowered(lowered_receiver),
431
- capacity: IR::IntegerLiteral.new(value: str_buffer_capacity(lowered_receiver.type), type: @ctx.types.fetch("ptr_uint")),
432
- }
433
- else
434
- raise LoweringError.new("unsupported explicit format sink #{info[:sink_kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
354
+ return unless operation
355
+
356
+ format_string = expression.arguments.fetch(1).value
357
+ return unless format_string.is_a?(AST::FormatString)
358
+
359
+ {
360
+ operation:,
361
+ sink_expression: expression.arguments.fetch(0).value,
362
+ sink_expected_type: callee_type.params.fetch(0).type,
363
+ format_string:,
364
+ sink_kind: :string,
365
+ method_call: false,
366
+ callee_type:,
367
+ callee_binding:,
368
+ }
369
+ when :method
370
+ return unless callee_binding&.owner&.module_name == "std.string"
371
+ return unless string_builder_type?(callee_type.receiver_type)
372
+ return unless expression.arguments.length == 1
373
+
374
+ operation = case callee_binding.name
375
+ when "append_format"
376
+ :append
377
+ when "assign_format"
378
+ :assign
379
+ end
380
+ return unless operation
381
+
382
+ format_string = expression.arguments.fetch(0).value
383
+ return unless format_string.is_a?(AST::FormatString)
384
+
385
+ {
386
+ operation:,
387
+ sink_expression: receiver,
388
+ sink_expected_type: callee_type.receiver_type,
389
+ format_string:,
390
+ sink_kind: :string,
391
+ method_call: true,
392
+ callee_type:,
393
+ callee_binding:,
394
+ }
395
+ when :str_buffer_append_format, :str_buffer_assign_format
396
+ return unless expression.arguments.length == 1
397
+
398
+ format_string = expression.arguments.fetch(0).value
399
+ return unless format_string.is_a?(AST::FormatString)
400
+
401
+ {
402
+ operation: kind == :str_buffer_assign_format ? :assign : :append,
403
+ sink_expression: receiver,
404
+ sink_expected_type: callee_type.receiver_type,
405
+ format_string:,
406
+ sink_kind: :str_buffer,
407
+ method_call: false,
408
+ callee_type:,
409
+ callee_binding: nil,
410
+ }
411
+ end
435
412
  end
436
- end
437
413
 
438
- def explicit_format_sink_target_buffer_view(sink_target)
439
- case sink_target[:kind]
440
- when :string
441
- sink_target[:value]
442
- when :str_buffer
443
- IR::AggregateLiteral.new(
444
- type: @ctx.types.fetch("str"),
445
- fields: [
446
- IR::AggregateField.new(name: "data", value: sink_target[:data_pointer]),
447
- IR::AggregateField.new(name: "len", value: sink_target[:capacity]),
448
- ],
449
- )
450
- else
451
- raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
414
+ def explicit_format_sink_target(info, prepared_sink_expression, env:)
415
+ case info[:sink_kind]
416
+ when :string
417
+ sink_value = if info[:method_call]
418
+ lower_method_receiver_argument(prepared_sink_expression, info[:callee_type], info[:callee_binding], env:)
419
+ else
420
+ lower_contextual_expression(prepared_sink_expression, env:, expected_type: info[:sink_expected_type])
421
+ end
422
+
423
+ { kind: :string, value: sink_value }
424
+ when :str_buffer
425
+ lowered_receiver = lower_expression(prepared_sink_expression, env:)
426
+ {
427
+ kind: :str_buffer,
428
+ receiver: lowered_receiver,
429
+ data_pointer: lower_str_buffer_data_pointer_from_lowered(lowered_receiver),
430
+ len_pointer: lower_str_buffer_len_pointer_from_lowered(lowered_receiver),
431
+ dirty_pointer: lower_str_buffer_dirty_pointer_from_lowered(lowered_receiver),
432
+ capacity: IR::IntegerLiteral.new(value: str_buffer_capacity(lowered_receiver.type), type: @ctx.types.fetch("ptr_uint")),
433
+ }
434
+ else
435
+ raise LoweringError.new("unsupported explicit format sink #{info[:sink_kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
436
+ end
452
437
  end
453
- end
454
438
 
455
- def lower_explicit_format_sink_expression_statement(expression, env:, line:)
456
- info = explicit_format_sink_call_info(expression, env)
457
- return unless info
439
+ def explicit_format_sink_target_buffer_view(sink_target)
440
+ case sink_target[:kind]
441
+ when :string
442
+ sink_target[:value]
443
+ when :str_buffer
444
+ IR::AggregateLiteral.new(
445
+ type: @ctx.types.fetch("str"),
446
+ fields: [
447
+ IR::AggregateField.new(name: "data", value: sink_target[:data_pointer]),
448
+ IR::AggregateField.new(name: "len", value: sink_target[:capacity]),
449
+ ],
450
+ )
451
+ else
452
+ raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
453
+ end
454
+ end
458
455
 
459
- sink_setup, prepared_sink_expression, sink_cleanups = prepare_expression_with_cleanups(
460
- info[:sink_expression],
461
- env:,
462
- expected_type: info[:sink_expected_type],
463
- allow_root_statement_foreign: true,
464
- )
465
- sink_target = explicit_format_sink_target(info, prepared_sink_expression, env:)
456
+ def lower_explicit_format_sink_expression_statement(expression, env:, line:)
457
+ info = explicit_format_sink_call_info(expression, env)
458
+ return unless info
466
459
 
467
- unless format_string_has_dynamic_parts?(info[:format_string])
468
- return sink_setup + [
469
- IR::ExpressionStmt.new(
470
- expression: explicit_format_sink_runtime_call(
471
- operation: info[:operation],
472
- sink_target:,
473
- text_value: IR::StringLiteral.new(
474
- value: format_string_static_text(info[:format_string]),
475
- type: @ctx.types.fetch("str"),
476
- cstring: false,
460
+ sink_setup, prepared_sink_expression, sink_cleanups = prepare_expression_with_cleanups(
461
+ info[:sink_expression],
462
+ env:,
463
+ expected_type: info[:sink_expected_type],
464
+ allow_root_statement_foreign: true,
465
+ )
466
+ sink_target = explicit_format_sink_target(info, prepared_sink_expression, env:)
467
+
468
+ unless format_string_has_dynamic_parts?(info[:format_string])
469
+ return sink_setup + [
470
+ IR::ExpressionStmt.new(
471
+ expression: explicit_format_sink_runtime_call(
472
+ operation: info[:operation],
473
+ sink_target:,
474
+ text_value: IR::StringLiteral.new(
475
+ value: format_string_static_text(info[:format_string]),
476
+ type: @ctx.types.fetch("str"),
477
+ cstring: false,
478
+ ),
477
479
  ),
480
+ line:,
481
+ path: @ctx.current_analysis_path,
478
482
  ),
479
- line:,
480
- path: @ctx.current_analysis_path,
481
- ),
482
- *sink_cleanups.flat_map(&:itself),
483
- ]
484
- end
485
-
486
- format_cleanup_start = (env[:prepared_expression_cleanups] ||= []).length
487
- format_setup, format_parts = build_dynamic_format_string_parts(info[:format_string], env:)
488
- format_cleanup_count = env[:prepared_expression_cleanups].length - format_cleanup_start
489
- format_cleanups = format_cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(format_cleanup_start, format_cleanup_count) : []
490
- copied_part_setup, copied_parts, copied_part_cleanups = copy_explicit_format_sink_str_parts(
491
- format_parts,
492
- env:,
493
- sink_kind: info[:sink_kind],
494
- )
495
-
496
- sink_statements = sink_setup + format_setup + copied_part_setup
497
- case sink_target[:kind]
498
- when :string
499
- if info[:operation] == :assign
500
- sink_statements << IR::ExpressionStmt.new(
501
- expression: IR::Call.new(callee: "std_string_String_clear", arguments: [sink_target[:value]], type: @ctx.types.fetch("void")),
502
- line:,
503
- path: @ctx.current_analysis_path,
504
- )
483
+ *sink_cleanups.flat_map(&:itself),
484
+ ]
505
485
  end
506
486
 
507
- copied_parts.each do |part|
508
- sink_statements << IR::ExpressionStmt.new(
509
- expression: explicit_format_sink_append_call(part, sink_value: sink_target[:value], env:),
510
- line:,
511
- path: @ctx.current_analysis_path,
512
- )
513
- end
514
- when :str_buffer
515
- if info[:operation] == :assign
516
- sink_statements << IR::ExpressionStmt.new(
517
- expression: IR::Call.new(
518
- callee: "mt_str_buffer_clear",
487
+ format_cleanup_start = (env[:prepared_expression_cleanups] ||= []).length
488
+ format_setup, format_parts = build_dynamic_format_string_parts(info[:format_string], env:)
489
+ format_cleanup_count = env[:prepared_expression_cleanups].length - format_cleanup_start
490
+ format_cleanups = format_cleanup_count.positive? ? env[:prepared_expression_cleanups].slice!(format_cleanup_start, format_cleanup_count) : []
491
+ copied_part_setup, copied_parts, copied_part_cleanups = copy_explicit_format_sink_str_parts(
492
+ format_parts,
493
+ env:,
494
+ sink_kind: info[:sink_kind],
495
+ )
496
+
497
+ sink_statements = sink_setup + format_setup + copied_part_setup
498
+ case sink_target[:kind]
499
+ when :string
500
+ if info[:operation] == :assign
501
+ sink_statements << IR::ExpressionStmt.new(
502
+ expression: IR::Call.new(callee: "std_string_String_clear", arguments: [sink_target[:value]], type: @ctx.types.fetch("void")),
503
+ line:,
504
+ path: @ctx.current_analysis_path,
505
+ )
506
+ end
507
+
508
+ copied_parts.each do |part|
509
+ sink_statements << IR::ExpressionStmt.new(
510
+ expression: explicit_format_sink_append_call(part, sink_value: sink_target[:value], env:),
511
+ line:,
512
+ path: @ctx.current_analysis_path,
513
+ )
514
+ end
515
+ when :str_buffer
516
+ if info[:operation] == :assign
517
+ sink_statements << IR::ExpressionStmt.new(
518
+ expression: IR::Call.new(
519
+ callee: "mt_str_buffer_clear",
520
+ arguments: [
521
+ sink_target[:data_pointer],
522
+ sink_target[:capacity],
523
+ sink_target[:len_pointer],
524
+ sink_target[:dirty_pointer],
525
+ ],
526
+ type: @ctx.types.fetch("void"),
527
+ ),
528
+ line:,
529
+ path: @ctx.current_analysis_path,
530
+ )
531
+ end
532
+
533
+ offset_name = fresh_c_temp_name(env, "fmt_sink_offset")
534
+ offset_value = IR::Name.new(name: offset_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
535
+ offset_init = if info[:operation] == :assign
536
+ IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))
537
+ else
538
+ IR::Call.new(
539
+ callee: "mt_str_buffer_len",
519
540
  arguments: [
520
541
  sink_target[:data_pointer],
521
542
  sink_target[:capacity],
522
543
  sink_target[:len_pointer],
523
544
  sink_target[:dirty_pointer],
524
545
  ],
525
- type: @ctx.types.fetch("void"),
526
- ),
527
- line:,
528
- path: @ctx.current_analysis_path,
546
+ type: @ctx.types.fetch("ptr_uint"),
547
+ )
548
+ end
549
+ sink_statements << IR::LocalDecl.new(name: offset_name, linkage_name: offset_name, type: @ctx.types.fetch("ptr_uint"), value: offset_init)
550
+
551
+ target_value = explicit_format_sink_target_buffer_view(sink_target)
552
+ copied_parts.each do |part|
553
+ sink_statements.concat(format_string_part_append_statements(part, target_value, offset_value, env:))
554
+ end
555
+ sink_statements << IR::Assignment.new(
556
+ target: IR::Unary.new(operator: "*", operand: sink_target[:len_pointer], type: @ctx.types.fetch("ptr_uint")),
557
+ operator: "=",
558
+ value: offset_value,
529
559
  )
560
+ else
561
+ raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
530
562
  end
531
563
 
532
- offset_name = fresh_c_temp_name(env, "fmt_sink_offset")
533
- offset_value = IR::Name.new(name: offset_name, type: @ctx.types.fetch("ptr_uint"), pointer: false)
534
- offset_init = if info[:operation] == :assign
535
- IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint"))
536
- else
564
+ sink_statements.concat(copied_part_cleanups)
565
+ sink_statements.concat(sink_cleanups.flat_map(&:itself))
566
+ sink_statements.concat(format_cleanups.flat_map(&:itself))
567
+ sink_statements
568
+ end
569
+
570
+ def explicit_format_sink_runtime_call(operation:, sink_target:, text_value:)
571
+ case sink_target[:kind]
572
+ when :string
573
+ callee = operation == :assign ? "std_string_String_assign" : "std_string_String_append"
574
+ IR::Call.new(callee:, arguments: [sink_target[:value], text_value], type: @ctx.types.fetch("void"))
575
+ when :str_buffer
576
+ callee = operation == :assign ? "mt_str_buffer_assign" : "mt_str_buffer_append"
537
577
  IR::Call.new(
538
- callee: "mt_str_buffer_len",
578
+ callee:,
539
579
  arguments: [
580
+ text_value,
540
581
  sink_target[:data_pointer],
541
582
  sink_target[:capacity],
542
583
  sink_target[:len_pointer],
543
584
  sink_target[:dirty_pointer],
544
585
  ],
545
- type: @ctx.types.fetch("ptr_uint"),
586
+ type: @ctx.types.fetch("void"),
546
587
  )
588
+ else
589
+ raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
547
590
  end
548
- sink_statements << IR::LocalDecl.new(name: offset_name, linkage_name: offset_name, type: @ctx.types.fetch("ptr_uint"), value: offset_init)
549
-
550
- target_value = explicit_format_sink_target_buffer_view(sink_target)
551
- copied_parts.each do |part|
552
- sink_statements.concat(format_string_part_append_statements(part, target_value, offset_value, env:))
553
- end
554
- sink_statements << IR::Assignment.new(
555
- target: IR::Unary.new(operator: "*", operand: sink_target[:len_pointer], type: @ctx.types.fetch("ptr_uint")),
556
- operator: "=",
557
- value: offset_value,
558
- )
559
- else
560
- raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
561
591
  end
562
592
 
563
- sink_statements.concat(copied_part_cleanups)
564
- sink_statements.concat(sink_cleanups.flat_map(&:itself))
565
- sink_statements.concat(format_cleanups.flat_map(&:itself))
566
- sink_statements
567
- end
593
+ def copy_explicit_format_sink_str_parts(format_parts, env:, sink_kind:)
594
+ setup = []
595
+ cleanup = []
568
596
 
569
- def explicit_format_sink_runtime_call(operation:, sink_target:, text_value:)
570
- case sink_target[:kind]
571
- when :string
572
- callee = operation == :assign ? "std_string_String_assign" : "std_string_String_append"
573
- IR::Call.new(callee:, arguments: [sink_target[:value], text_value], type: @ctx.types.fetch("void"))
574
- when :str_buffer
575
- callee = operation == :assign ? "mt_str_buffer_assign" : "mt_str_buffer_append"
576
- IR::Call.new(
577
- callee:,
578
- arguments: [
579
- text_value,
580
- sink_target[:data_pointer],
581
- sink_target[:capacity],
582
- sink_target[:len_pointer],
583
- sink_target[:dirty_pointer],
584
- ],
585
- type: @ctx.types.fetch("void"),
586
- )
587
- else
588
- raise LoweringError.new("unsupported explicit format sink #{sink_target[:kind]}", line: 0, column: 0, path: @ctx.current_analysis_path)
589
- end
590
- end
597
+ copied_parts = format_parts.map do |part|
598
+ next part unless part[:kind] == :expression
591
599
 
592
- def copy_explicit_format_sink_str_parts(format_parts, env:, sink_kind:)
593
- setup = []
594
- cleanup = []
600
+ should_copy = part[:append_function_name] == "append" ||
601
+ (sink_kind == :str_buffer && part[:append_function_name] == "append_cstr")
602
+ next part unless should_copy
595
603
 
596
- copied_parts = format_parts.map do |part|
597
- next part unless part[:kind] == :expression
604
+ parameter = format_string_part_parameter_expression(part)
605
+ copy_name = fresh_c_temp_name(env, "fmt_sink_str")
606
+ copy_value = IR::Name.new(name: copy_name, type: @ctx.types.fetch("str"), pointer: false)
607
+ register_prepared_temp!(env, copy_name, @ctx.types.fetch("str"), cstr_backed: true)
598
608
 
599
- should_copy = part[:append_function_name] == "append" ||
600
- (sink_kind == :str_buffer && part[:append_function_name] == "append_cstr")
601
- next part unless should_copy
609
+ length_value = if part[:append_function_name] == "append"
610
+ IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
611
+ else
612
+ IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
613
+ end
614
+ append_callee = part[:append_function_name] == "append" ? "mt_format_append_str" : "mt_format_append_cstr"
602
615
 
603
- parameter = format_string_part_parameter_expression(part)
604
- copy_name = fresh_c_temp_name(env, "fmt_sink_str")
605
- copy_value = IR::Name.new(name: copy_name, type: @ctx.types.fetch("str"), pointer: false)
606
- register_prepared_temp!(env, copy_name, @ctx.types.fetch("str"), cstr_backed: true)
616
+ setup << IR::LocalDecl.new(
617
+ name: copy_name,
618
+ linkage_name: copy_name,
619
+ type: @ctx.types.fetch("str"),
620
+ value: IR::Call.new(
621
+ callee: "mt_format_str_make",
622
+ arguments: [length_value],
623
+ type: @ctx.types.fetch("str"),
624
+ ),
625
+ )
626
+ setup << IR::ExpressionStmt.new(
627
+ expression: IR::Call.new(
628
+ callee: append_callee,
629
+ arguments: [copy_value, IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")), parameter],
630
+ type: @ctx.types.fetch("ptr_uint"),
631
+ ),
632
+ )
633
+ cleanup << IR::ExpressionStmt.new(
634
+ expression: IR::Call.new(callee: "mt_format_str_release", arguments: [copy_value], type: @ctx.types.fetch("void")),
635
+ )
607
636
 
608
- length_value = if part[:append_function_name] == "append"
609
- IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
610
- else
611
- IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
637
+ part.merge(parameter_linkage_name: copy_name, parameter_type: @ctx.types.fetch("str"), append_function_name: "append")
612
638
  end
613
- append_callee = part[:append_function_name] == "append" ? "mt_format_append_str" : "mt_format_append_cstr"
614
-
615
- setup << IR::LocalDecl.new(
616
- name: copy_name,
617
- linkage_name: copy_name,
618
- type: @ctx.types.fetch("str"),
619
- value: IR::Call.new(
620
- callee: "mt_format_str_make",
621
- arguments: [length_value],
622
- type: @ctx.types.fetch("str"),
623
- ),
624
- )
625
- setup << IR::ExpressionStmt.new(
626
- expression: IR::Call.new(
627
- callee: append_callee,
628
- arguments: [copy_value, IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("ptr_uint")), parameter],
629
- type: @ctx.types.fetch("ptr_uint"),
630
- ),
631
- )
632
- cleanup << IR::ExpressionStmt.new(
633
- expression: IR::Call.new(callee: "mt_format_str_release", arguments: [copy_value], type: @ctx.types.fetch("void")),
634
- )
635
639
 
636
- part.merge(parameter_linkage_name: copy_name, parameter_type: @ctx.types.fetch("str"), append_function_name: "append")
640
+ [setup, copied_parts, cleanup]
637
641
  end
638
642
 
639
- [setup, copied_parts, cleanup]
640
- end
643
+ def explicit_format_sink_append_call(part, sink_value:, env:)
644
+ if part[:kind] == :text
645
+ return IR::Call.new(
646
+ callee: "std_string_String_append",
647
+ arguments: [sink_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
648
+ type: @ctx.types.fetch("void"),
649
+ )
650
+ end
641
651
 
642
- def explicit_format_sink_append_call(part, sink_value:, env:)
643
- if part[:kind] == :text
644
- return IR::Call.new(
645
- callee: "std_string_String_append",
646
- arguments: [sink_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
647
- type: @ctx.types.fetch("void"),
648
- )
649
- end
652
+ parameter = format_string_part_parameter_expression(part)
650
653
 
651
- parameter = format_string_part_parameter_expression(part)
654
+ if part[:kind] == :precision_expression
655
+ return IR::Call.new(
656
+ callee: "std_fmt_append_double_precision",
657
+ arguments: [sink_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
658
+ type: @ctx.types.fetch("void"),
659
+ )
660
+ end
652
661
 
653
- if part[:kind] == :precision_expression
654
- return IR::Call.new(
655
- callee: "std_fmt_append_double_precision",
656
- arguments: [sink_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
657
- type: @ctx.types.fetch("void"),
658
- )
659
- end
662
+ if part[:kind] == :custom_expression
663
+ return IR::Call.new(
664
+ callee: part[:format_binding].append_callee_name,
665
+ arguments: [
666
+ format_string_custom_receiver_argument(part, hook: :append, env:),
667
+ sink_value,
668
+ ],
669
+ type: @ctx.types.fetch("void"),
670
+ )
671
+ end
660
672
 
661
- if part[:kind] == :custom_expression
662
- return IR::Call.new(
663
- callee: part[:format_binding].append_callee_name,
664
- arguments: [
665
- format_string_custom_receiver_argument(part, hook: :append, env:),
666
- sink_value,
667
- ],
668
- type: @ctx.types.fetch("void"),
669
- )
670
- end
673
+ callee = case part[:append_function_name]
674
+ when "append"
675
+ "std_string_String_append"
676
+ when "append_cstr"
677
+ "std_fmt_append_cstr"
678
+ else
679
+ "std_fmt_#{part[:append_function_name]}"
680
+ end
671
681
 
672
- callee = case part[:append_function_name]
673
- when "append"
674
- "std_string_String_append"
675
- when "append_cstr"
676
- "std_fmt_append_cstr"
677
- else
678
- "std_fmt_#{part[:append_function_name]}"
682
+ IR::Call.new(callee:, arguments: [sink_value, parameter], type: @ctx.types.fetch("void"))
679
683
  end
680
684
 
681
- IR::Call.new(callee:, arguments: [sink_value, parameter], type: @ctx.types.fetch("void"))
682
- end
683
-
684
- def format_string_part_length_expression(part, env:)
685
- parameter = format_string_part_parameter_expression(part)
685
+ def format_string_part_length_expression(part, env:)
686
+ parameter = format_string_part_parameter_expression(part)
686
687
 
687
- if part[:kind] == :precision_expression
688
- return IR::Call.new(
689
- callee: "mt_format_double_precision_len",
690
- arguments: [parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
691
- type: @ctx.types.fetch("ptr_uint"),
692
- )
693
- end
688
+ if part[:kind] == :precision_expression
689
+ return IR::Call.new(
690
+ callee: "mt_format_double_precision_len",
691
+ arguments: [parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
692
+ type: @ctx.types.fetch("ptr_uint"),
693
+ )
694
+ end
694
695
 
695
- if part[:kind] == :custom_expression
696
- return IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
697
- end
696
+ if part[:kind] == :custom_expression
697
+ return IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
698
+ end
698
699
 
699
- case part[:append_function_name]
700
- when "append"
701
- IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
702
- when "append_cstr"
703
- IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
704
- else
705
- IR::Call.new(callee: mt_format_length_c_name(part[:append_function_name]), arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
700
+ case part[:append_function_name]
701
+ when "append"
702
+ IR::Member.new(receiver: parameter, member: "len", type: @ctx.types.fetch("ptr_uint"))
703
+ when "append_cstr"
704
+ IR::Call.new(callee: "mt_format_cstr_len", arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
705
+ else
706
+ IR::Call.new(callee: format_length_linkage_name(part[:append_function_name]), arguments: [parameter], type: @ctx.types.fetch("ptr_uint"))
707
+ end
706
708
  end
707
- end
708
709
 
709
- def format_string_part_append_statements(part, result_value, offset_value, env:)
710
- if part[:kind] == :custom_expression
711
- output_type = part[:append_output_type]
712
- output_ref_type = Types::Registry.generic_instance("ref", [output_type])
713
- output_value_name = fresh_c_temp_name(env, "fmt_part_output")
714
- output_value = IR::Name.new(name: output_value_name, type: output_type, pointer: false)
715
- output_len = IR::Member.new(receiver: output_value, member: "len", type: @ctx.types.fetch("ptr_uint"))
716
- expected_length = IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
717
- data_pointer = format_string_result_data_pointer(result_value)
718
- slice_data_pointer = cast_expression(
719
- IR::Binary.new(operator: "+", left: data_pointer, right: offset_value, type: pointer_to(@ctx.types.fetch("char"))),
720
- output_type.field("data"),
721
- )
710
+ def format_string_part_append_statements(part, result_value, offset_value, env:)
711
+ if part[:kind] == :custom_expression
712
+ output_type = part[:append_output_type]
713
+ output_ref_type = Types::Registry.generic_instance("ref", [output_type])
714
+ output_value_name = fresh_c_temp_name(env, "fmt_part_output")
715
+ output_value = IR::Name.new(name: output_value_name, type: output_type, pointer: false)
716
+ output_len = IR::Member.new(receiver: output_value, member: "len", type: @ctx.types.fetch("ptr_uint"))
717
+ expected_length = IR::Name.new(name: part[:expected_length_linkage_name], type: @ctx.types.fetch("ptr_uint"), pointer: false)
718
+ data_pointer = format_string_result_data_pointer(result_value)
719
+ slice_data_pointer = cast_expression(
720
+ IR::Binary.new(operator: "+", left: data_pointer, right: offset_value, type: pointer_to(@ctx.types.fetch("char"))),
721
+ output_type.field("data"),
722
+ )
722
723
 
723
- return [
724
- IR::LocalDecl.new(
725
- name: output_value_name,
726
- linkage_name: output_value_name,
727
- type: output_type,
728
- value: IR::AggregateLiteral.new(
724
+ return [
725
+ IR::LocalDecl.new(
726
+ name: output_value_name,
727
+ linkage_name: output_value_name,
729
728
  type: output_type,
730
- fields: [
731
- IR::AggregateField.new(name: "data", value: slice_data_pointer),
732
- IR::AggregateField.new(
733
- name: "len",
734
- value: IR::IntegerLiteral.new(value: 0, type: output_type.field("len")),
735
- ),
736
- IR::AggregateField.new(name: "capacity", value: expected_length),
737
- IR::AggregateField.new(
738
- name: "owns_storage",
739
- value: IR::BooleanLiteral.new(value: false, type: output_type.field("owns_storage")),
740
- ),
741
- ],
729
+ value: IR::AggregateLiteral.new(
730
+ type: output_type,
731
+ fields: [
732
+ IR::AggregateField.new(name: "data", value: slice_data_pointer),
733
+ IR::AggregateField.new(
734
+ name: "len",
735
+ value: IR::IntegerLiteral.new(value: 0, type: output_type.field("len")),
736
+ ),
737
+ IR::AggregateField.new(name: "capacity", value: expected_length),
738
+ IR::AggregateField.new(
739
+ name: "owns_storage",
740
+ value: IR::BooleanLiteral.new(value: false, type: output_type.field("owns_storage")),
741
+ ),
742
+ ],
743
+ ),
742
744
  ),
743
- ),
744
- IR::ExpressionStmt.new(
745
- expression: IR::Call.new(
746
- callee: part[:format_binding].append_callee_name,
747
- arguments: [
748
- format_string_custom_receiver_argument(part, hook: :append, env:),
749
- IR::AddressOf.new(expression: output_value, type: output_ref_type),
750
- ],
751
- type: @ctx.types.fetch("void"),
745
+ IR::ExpressionStmt.new(
746
+ expression: IR::Call.new(
747
+ callee: part[:format_binding].append_callee_name,
748
+ arguments: [
749
+ format_string_custom_receiver_argument(part, hook: :append, env:),
750
+ IR::AddressOf.new(expression: output_value, type: output_ref_type),
751
+ ],
752
+ type: @ctx.types.fetch("void"),
753
+ ),
752
754
  ),
753
- ),
754
- IR::IfStmt.new(
755
- condition: IR::Binary.new(operator: "!=", left: output_len, right: expected_length, type: @ctx.types.fetch("bool")),
756
- then_body: [
757
- IR::ExpressionStmt.new(
758
- expression: IR::Call.new(
759
- callee: "mt_fatal",
760
- arguments: [
761
- IR::StringLiteral.new(
762
- value: "custom format hook length mismatch",
763
- type: @ctx.types.fetch("cstr"),
764
- cstring: true,
765
- ),
766
- ],
767
- type: @ctx.types.fetch("void"),
755
+ IR::IfStmt.new(
756
+ condition: IR::Binary.new(operator: "!=", left: output_len, right: expected_length, type: @ctx.types.fetch("bool")),
757
+ then_body: [
758
+ IR::ExpressionStmt.new(
759
+ expression: IR::Call.new(
760
+ callee: "mt_fatal",
761
+ arguments: [
762
+ IR::StringLiteral.new(
763
+ value: "custom format hook length mismatch",
764
+ type: @ctx.types.fetch("cstr"),
765
+ cstring: true,
766
+ ),
767
+ ],
768
+ type: @ctx.types.fetch("void"),
769
+ ),
768
770
  ),
771
+ ],
772
+ else_body: nil,
773
+ ),
774
+ IR::Assignment.new(
775
+ target: offset_value,
776
+ operator: "=",
777
+ value: IR::Binary.new(
778
+ operator: "+",
779
+ left: offset_value,
780
+ right: output_len,
781
+ type: @ctx.types.fetch("ptr_uint"),
769
782
  ),
770
- ],
771
- else_body: nil,
772
- ),
773
- IR::Assignment.new(
774
- target: offset_value,
775
- operator: "=",
776
- value: IR::Binary.new(
777
- operator: "+",
778
- left: offset_value,
779
- right: output_len,
780
- type: @ctx.types.fetch("ptr_uint"),
781
783
  ),
782
- ),
784
+ IR::Assignment.new(
785
+ target: IR::Index.new(receiver: data_pointer, index: offset_value, type: @ctx.types.fetch("char")),
786
+ operator: "=",
787
+ value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("char")),
788
+ ),
789
+ ]
790
+ end
791
+
792
+ [
783
793
  IR::Assignment.new(
784
- target: IR::Index.new(receiver: data_pointer, index: offset_value, type: @ctx.types.fetch("char")),
794
+ target: offset_value,
785
795
  operator: "=",
786
- value: IR::IntegerLiteral.new(value: 0, type: @ctx.types.fetch("char")),
796
+ value: format_string_part_append_expression(part, result_value, offset_value),
787
797
  ),
788
798
  ]
789
799
  end
790
800
 
791
- [
792
- IR::Assignment.new(
793
- target: offset_value,
794
- operator: "=",
795
- value: format_string_part_append_expression(part, result_value, offset_value),
796
- ),
797
- ]
798
- end
801
+ def format_string_part_append_expression(part, result_value, offset_value)
802
+ if part[:kind] == :text
803
+ return IR::Call.new(
804
+ callee: "mt_format_append_str",
805
+ arguments: [result_value, offset_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
806
+ type: @ctx.types.fetch("ptr_uint"),
807
+ )
808
+ end
799
809
 
800
- def format_string_part_append_expression(part, result_value, offset_value)
801
- if part[:kind] == :text
802
- return IR::Call.new(
803
- callee: "mt_format_append_str",
804
- arguments: [result_value, offset_value, IR::StringLiteral.new(value: part[:value], type: @ctx.types.fetch("str"), cstring: false)],
805
- type: @ctx.types.fetch("ptr_uint"),
806
- )
807
- end
810
+ parameter = format_string_part_parameter_expression(part)
808
811
 
809
- parameter = format_string_part_parameter_expression(part)
812
+ if part[:kind] == :precision_expression
813
+ return IR::Call.new(
814
+ callee: "mt_format_append_double_precision",
815
+ arguments: [result_value, offset_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
816
+ type: @ctx.types.fetch("ptr_uint"),
817
+ )
818
+ end
819
+
820
+ if part[:kind] == :custom_expression
821
+ raise LoweringError.new("custom format parts require statement lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
822
+ end
810
823
 
811
- if part[:kind] == :precision_expression
812
- return IR::Call.new(
813
- callee: "mt_format_append_double_precision",
814
- arguments: [result_value, offset_value, parameter, IR::IntegerLiteral.new(value: part[:precision], type: @ctx.types.fetch("int"))],
824
+ IR::Call.new(
825
+ callee: format_append_linkage_name(part[:append_function_name]),
826
+ arguments: [result_value, offset_value, parameter],
815
827
  type: @ctx.types.fetch("ptr_uint"),
816
828
  )
817
829
  end
818
830
 
819
- if part[:kind] == :custom_expression
820
- raise LoweringError.new("custom format parts require statement lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
831
+ def format_string_custom_receiver_argument(part, hook:, env:)
832
+ binding = case hook
833
+ when :length
834
+ part[:format_binding].length_binding
835
+ when :append
836
+ part[:format_binding].append_binding
837
+ else
838
+ raise LoweringError.new("unsupported custom format hook #{hook}", line: 0, column: 0, path: @ctx.current_analysis_path)
839
+ end
840
+
841
+ if env
842
+ return lower_method_receiver_argument(AST::Identifier.new(name: part[:parameter_linkage_name]), binding.type, binding, env:)
843
+ end
844
+
845
+ IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
821
846
  end
822
847
 
823
- IR::Call.new(
824
- callee: mt_format_append_c_name(part[:append_function_name]),
825
- arguments: [result_value, offset_value, parameter],
826
- type: @ctx.types.fetch("ptr_uint"),
827
- )
828
- end
848
+ def format_string_result_data_pointer(result_value)
849
+ IR::Member.new(receiver: result_value, member: "data", type: pointer_to(@ctx.types.fetch("char")))
850
+ end
829
851
 
830
- def format_string_custom_receiver_argument(part, hook:, env:)
831
- binding = case hook
832
- when :length
833
- part[:format_binding].length_binding
834
- when :append
835
- part[:format_binding].append_binding
836
- else
837
- raise LoweringError.new("unsupported custom format hook #{hook}", line: 0, column: 0, path: @ctx.current_analysis_path)
838
- end
839
-
840
- if env
841
- return lower_method_receiver_argument(AST::Identifier.new(name: part[:parameter_linkage_name]), binding.type, binding, env:)
852
+ def format_string_part_parameter_expression(part)
853
+ IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
842
854
  end
843
855
 
844
- IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
845
- end
856
+ def format_string_append_plan(type, context:)
857
+ return { kind: :builtin, append_function_name: "append", append_argument_type: @ctx.types.fetch("str") } if type == @ctx.types.fetch("str")
858
+ return { kind: :builtin, append_function_name: "append_cstr", append_argument_type: @ctx.types.fetch("cstr") } if type == @ctx.types.fetch("cstr")
859
+ return { kind: :builtin, append_function_name: "append_bool", append_argument_type: @ctx.types.fetch("bool") } if type == @ctx.types.fetch("bool")
860
+ return { kind: :builtin, append_function_name: "append_float", append_argument_type: @ctx.types.fetch("float") } if type == @ctx.types.fetch("float")
861
+ return { kind: :builtin, append_function_name: "append_double", append_argument_type: @ctx.types.fetch("double") } if type == @ctx.types.fetch("double")
862
+
863
+ if type.is_a?(Types::Primitive) && type.integer?
864
+ return { kind: :builtin, append_function_name: "append_int", append_argument_type: @ctx.types.fetch("int") } if %w[byte short int].include?(type.name)
865
+ return { kind: :builtin, append_function_name: "append_uint", append_argument_type: @ctx.types.fetch("uint") } if %w[ubyte ushort uint].include?(type.name)
866
+ return { kind: :builtin, append_function_name: "append_ptr_uint", append_argument_type: @ctx.types.fetch("ptr_uint") } if type.name == "ptr_uint"
867
+ return { kind: :builtin, append_function_name: "append_long", append_argument_type: @ctx.types.fetch("long") } if %w[long ptr_int].include?(type.name)
868
+ return { kind: :builtin, append_function_name: "append_ulong", append_argument_type: @ctx.types.fetch("ulong") } if type.name == "ulong"
869
+ end
846
870
 
847
- def format_string_result_data_pointer(result_value)
848
- IR::Member.new(receiver: result_value, member: "data", type: pointer_to(@ctx.types.fetch("char")))
849
- end
871
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
872
+ return format_string_append_plan(type.backing_type, context:)
873
+ end
850
874
 
851
- def format_string_part_parameter_expression(part)
852
- IR::Name.new(name: part[:parameter_linkage_name], type: part[:parameter_type], pointer: false)
853
- end
875
+ if (custom_binding = resolve_explicit_format_binding(type, context:))
876
+ return {
877
+ kind: :custom,
878
+ append_argument_type: type,
879
+ binding: custom_binding,
880
+ append_output_type: referenced_type(custom_binding.append_binding.type.params.first.type),
881
+ }
882
+ end
854
883
 
855
- def format_string_append_plan(type, context:)
856
- return { kind: :builtin, append_function_name: "append", append_argument_type: @ctx.types.fetch("str") } if type == @ctx.types.fetch("str")
857
- return { kind: :builtin, append_function_name: "append_cstr", append_argument_type: @ctx.types.fetch("cstr") } if type == @ctx.types.fetch("cstr")
858
- return { kind: :builtin, append_function_name: "append_bool", append_argument_type: @ctx.types.fetch("bool") } if type == @ctx.types.fetch("bool")
859
- return { kind: :builtin, append_function_name: "append_float", append_argument_type: @ctx.types.fetch("float") } if type == @ctx.types.fetch("float")
860
- return { kind: :builtin, append_function_name: "append_double", append_argument_type: @ctx.types.fetch("double") } if type == @ctx.types.fetch("double")
861
-
862
- if type.is_a?(Types::Primitive) && type.integer?
863
- return { kind: :builtin, append_function_name: "append_int", append_argument_type: @ctx.types.fetch("int") } if %w[byte short int].include?(type.name)
864
- return { kind: :builtin, append_function_name: "append_uint", append_argument_type: @ctx.types.fetch("uint") } if %w[ubyte ushort uint].include?(type.name)
865
- return { kind: :builtin, append_function_name: "append_ptr_uint", append_argument_type: @ctx.types.fetch("ptr_uint") } if type.name == "ptr_uint"
866
- return { kind: :builtin, append_function_name: "append_long", append_argument_type: @ctx.types.fetch("long") } if %w[long ptr_int].include?(type.name)
867
- return { kind: :builtin, append_function_name: "append_ulong", append_argument_type: @ctx.types.fetch("ulong") } if type.name == "ulong"
884
+ raise LoweringError.new("formatted string interpolation supports str, cstr, bool, numeric primitives, integer-backed enums/flags, and types implementing format_len()/append_format(output: ref[std.string.String]), got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
868
885
  end
869
886
 
870
- if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
871
- return format_string_append_plan(type.backing_type, context:)
872
- end
887
+ def format_string_hex_append_plan(type, uppercase:)
888
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
889
+ return format_string_hex_append_plan(type.backing_type, uppercase:)
890
+ end
873
891
 
874
- if (custom_binding = resolve_explicit_format_binding(type, context:))
875
- return {
876
- kind: :custom,
877
- append_argument_type: type,
878
- binding: custom_binding,
879
- append_output_type: referenced_type(custom_binding.append_binding.type.params.first.type),
880
- }
881
- end
892
+ unless type.is_a?(Types::Primitive) && type.integer?
893
+ raise LoweringError.new("format spec ':x' and ':X' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
894
+ end
882
895
 
883
- raise LoweringError.new("formatted string interpolation supports str, cstr, bool, numeric primitives, integer-backed enums/flags, and types implementing format_len()/append_format(output: ref[std.string.String]), got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
884
- end
896
+ if type.signed_integer?
897
+ return [uppercase ? "append_long_hex_upper" : "append_long_hex", @ctx.types.fetch("long")]
898
+ end
885
899
 
886
- def format_string_hex_append_plan(type, uppercase:)
887
- if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
888
- return format_string_hex_append_plan(type.backing_type, uppercase:)
889
- end
900
+ if type.unsigned_integer?
901
+ return [uppercase ? "append_ulong_hex_upper" : "append_ulong_hex", @ctx.types.fetch("ulong")]
902
+ end
890
903
 
891
- unless type.is_a?(Types::Primitive) && type.integer?
892
904
  raise LoweringError.new("format spec ':x' and ':X' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
893
905
  end
894
906
 
895
- if type.signed_integer?
896
- return [uppercase ? "append_long_hex_upper" : "append_long_hex", @ctx.types.fetch("long")]
897
- end
907
+ def format_string_oct_append_plan(type, uppercase:)
908
+ _ = uppercase
909
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
910
+ return format_string_oct_append_plan(type.backing_type, uppercase:)
911
+ end
898
912
 
899
- if type.unsigned_integer?
900
- return [uppercase ? "append_ulong_hex_upper" : "append_ulong_hex", @ctx.types.fetch("ulong")]
901
- end
913
+ unless type.is_a?(Types::Primitive) && type.integer?
914
+ raise LoweringError.new("format spec ':o' and ':O' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
915
+ end
902
916
 
903
- raise LoweringError.new("format spec ':x' and ':X' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
904
- end
917
+ if type.signed_integer?
918
+ return ["append_long_oct", @ctx.types.fetch("long")]
919
+ end
905
920
 
906
- def format_string_oct_append_plan(type, uppercase:)
907
- _ = uppercase
908
- if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
909
- return format_string_oct_append_plan(type.backing_type, uppercase:)
910
- end
921
+ if type.unsigned_integer?
922
+ return ["append_ulong_oct", @ctx.types.fetch("ulong")]
923
+ end
911
924
 
912
- unless type.is_a?(Types::Primitive) && type.integer?
913
925
  raise LoweringError.new("format spec ':o' and ':O' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
914
926
  end
915
927
 
916
- if type.signed_integer?
917
- return ["append_long_oct", @ctx.types.fetch("long")]
918
- end
928
+ def format_string_bin_append_plan(type, uppercase:)
929
+ _ = uppercase
930
+ if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
931
+ return format_string_bin_append_plan(type.backing_type, uppercase:)
932
+ end
919
933
 
920
- if type.unsigned_integer?
921
- return ["append_ulong_oct", @ctx.types.fetch("ulong")]
922
- end
934
+ unless type.is_a?(Types::Primitive) && type.integer?
935
+ raise LoweringError.new("format spec ':b' and ':B' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
936
+ end
923
937
 
924
- raise LoweringError.new("format spec ':o' and ':O' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
925
- end
938
+ if type.signed_integer?
939
+ return ["append_long_bin", @ctx.types.fetch("long")]
940
+ end
926
941
 
927
- def format_string_bin_append_plan(type, uppercase:)
928
- _ = uppercase
929
- if type.is_a?(Types::EnumBase) && type.backing_type.is_a?(Types::Primitive) && type.backing_type.integer?
930
- return format_string_bin_append_plan(type.backing_type, uppercase:)
931
- end
942
+ if type.unsigned_integer?
943
+ return ["append_ulong_bin", @ctx.types.fetch("ulong")]
944
+ end
932
945
 
933
- unless type.is_a?(Types::Primitive) && type.integer?
934
946
  raise LoweringError.new("format spec ':b' and ':B' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
935
947
  end
936
948
 
937
- if type.signed_integer?
938
- return ["append_long_bin", @ctx.types.fetch("long")]
939
- end
940
-
941
- if type.unsigned_integer?
942
- return ["append_ulong_bin", @ctx.types.fetch("ulong")]
943
- end
949
+ def format_length_linkage_name(name)
950
+ {
951
+ "append_bool" => "mt_format_bool_len",
952
+ "append_float" => "mt_format_float_len",
953
+ "append_double" => "mt_format_double_len",
954
+ "append_ulong_hex" => "mt_format_ulong_hex_len",
955
+ "append_ulong_hex_upper" => "mt_format_ulong_hex_len",
956
+ "append_long_hex" => "mt_format_long_hex_len",
957
+ "append_long_hex_upper" => "mt_format_long_hex_len",
958
+ "append_ulong_oct" => "mt_format_ulong_oct_len",
959
+ "append_long_oct" => "mt_format_long_oct_len",
960
+ "append_ulong_bin" => "mt_format_ulong_bin_len",
961
+ "append_long_bin" => "mt_format_long_bin_len",
962
+ "append_int" => "mt_format_int_len",
963
+ "append_uint" => "mt_format_uint_len",
964
+ "append_ptr_uint" => "mt_format_ptr_uint_len",
965
+ "append_long" => "mt_format_long_len",
966
+ "append_ulong" => "mt_format_ulong_len",
967
+ }.fetch(name)
968
+ end
969
+
970
+ def format_append_linkage_name(name)
971
+ {
972
+ "append" => "mt_format_append_str",
973
+ "append_cstr" => "mt_format_append_cstr",
974
+ "append_bool" => "mt_format_append_bool",
975
+ "append_float" => "mt_format_append_float",
976
+ "append_double" => "mt_format_append_double",
977
+ "append_ulong_hex" => "mt_format_append_ulong_hex",
978
+ "append_ulong_hex_upper" => "mt_format_append_ulong_hex_upper",
979
+ "append_long_hex" => "mt_format_append_long_hex",
980
+ "append_long_hex_upper" => "mt_format_append_long_hex_upper",
981
+ "append_ulong_oct" => "mt_format_append_ulong_oct",
982
+ "append_long_oct" => "mt_format_append_long_oct",
983
+ "append_ulong_bin" => "mt_format_append_ulong_bin",
984
+ "append_long_bin" => "mt_format_append_long_bin",
985
+ "append_int" => "mt_format_append_int",
986
+ "append_uint" => "mt_format_append_uint",
987
+ "append_ptr_uint" => "mt_format_append_ptr_uint",
988
+ "append_long" => "mt_format_append_long",
989
+ "append_ulong" => "mt_format_append_ulong",
990
+ }.fetch(name)
991
+ end
992
+
993
+ def prepare_binary_expression_for_inline_lowering(expression, env:, expected_type: nil)
994
+ propagated_type = propagating_expected_type(expression.operator, expected_type)
995
+ left_type, right_type = infer_binary_operand_types(expression, env:, expected_type:)
996
+ operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
997
+ left_setup, left = prepare_expression_for_inline_lowering(expression.left, env:, expected_type: operand_type || propagated_type || left_type)
998
+ right_env = binary_right_env(expression, env)
999
+ right_setup, right = prepare_expression_for_inline_lowering(expression.right, env: right_env, expected_type: operand_type || left_type)
944
1000
 
945
- raise LoweringError.new("format spec ':b' and ':B' require integer interpolation, got #{type}", line: 0, column: 0, path: @ctx.current_analysis_path)
946
- end
1001
+ unless %w[and or].include?(expression.operator)
1002
+ return [
1003
+ left_setup + right_setup,
1004
+ AST::BinaryOp.new(operator: expression.operator, left:, right:),
1005
+ ]
1006
+ end
947
1007
 
948
- def mt_format_length_c_name(name)
949
- {
950
- "append_bool" => "mt_format_bool_len",
951
- "append_float" => "mt_format_float_len",
952
- "append_double" => "mt_format_double_len",
953
- "append_ulong_hex" => "mt_format_ulong_hex_len",
954
- "append_ulong_hex_upper" => "mt_format_ulong_hex_len",
955
- "append_long_hex" => "mt_format_long_hex_len",
956
- "append_long_hex_upper" => "mt_format_long_hex_len",
957
- "append_ulong_oct" => "mt_format_ulong_oct_len",
958
- "append_long_oct" => "mt_format_long_oct_len",
959
- "append_ulong_bin" => "mt_format_ulong_bin_len",
960
- "append_long_bin" => "mt_format_long_bin_len",
961
- "append_int" => "mt_format_int_len",
962
- "append_uint" => "mt_format_uint_len",
963
- "append_ptr_uint" => "mt_format_ptr_uint_len",
964
- "append_long" => "mt_format_long_len",
965
- "append_ulong" => "mt_format_ulong_len",
966
- }.fetch(name)
967
- end
1008
+ return [[], expression] if left_setup.empty? && right_setup.empty?
968
1009
 
969
- def mt_format_append_c_name(name)
970
- {
971
- "append" => "mt_format_append_str",
972
- "append_cstr" => "mt_format_append_cstr",
973
- "append_bool" => "mt_format_append_bool",
974
- "append_float" => "mt_format_append_float",
975
- "append_double" => "mt_format_append_double",
976
- "append_ulong_hex" => "mt_format_append_ulong_hex",
977
- "append_ulong_hex_upper" => "mt_format_append_ulong_hex_upper",
978
- "append_long_hex" => "mt_format_append_long_hex",
979
- "append_long_hex_upper" => "mt_format_append_long_hex_upper",
980
- "append_ulong_oct" => "mt_format_append_ulong_oct",
981
- "append_long_oct" => "mt_format_append_long_oct",
982
- "append_ulong_bin" => "mt_format_append_ulong_bin",
983
- "append_long_bin" => "mt_format_append_long_bin",
984
- "append_int" => "mt_format_append_int",
985
- "append_uint" => "mt_format_append_uint",
986
- "append_ptr_uint" => "mt_format_append_ptr_uint",
987
- "append_long" => "mt_format_append_long",
988
- "append_ulong" => "mt_format_append_ulong",
989
- }.fetch(name)
990
- end
1010
+ result_type = infer_expression_type(expression, env:, expected_type:)
1011
+ result_name = fresh_c_temp_name(env, expression.operator)
1012
+ register_prepared_temp!(env, result_name, result_type)
1013
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1014
+ left_value = lower_contextual_expression(left, env:, expected_type: result_type)
1015
+ right_value = lower_contextual_expression(right, env: right_env, expected_type: result_type)
1016
+ branch_condition = expression.operator == "and" ? result_ref : IR::Unary.new(operator: "not", operand: result_ref, type: @ctx.types.fetch("bool"))
991
1017
 
992
- def prepare_binary_expression_for_inline_lowering(expression, env:, expected_type: nil)
993
- propagated_type = propagating_expected_type(expression.operator, expected_type)
994
- left_type, right_type = infer_binary_operand_types(expression, env:, expected_type:)
995
- operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
996
- left_setup, left = prepare_expression_for_inline_lowering(expression.left, env:, expected_type: operand_type || propagated_type || left_type)
997
- right_env = binary_right_env(expression, env)
998
- right_setup, right = prepare_expression_for_inline_lowering(expression.right, env: right_env, expected_type: operand_type || left_type)
999
-
1000
- unless %w[and or].include?(expression.operator)
1001
- return [
1002
- left_setup + right_setup,
1003
- AST::BinaryOp.new(operator: expression.operator, left:, right:),
1018
+ [
1019
+ left_setup + [
1020
+ IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: left_value),
1021
+ IR::IfStmt.new(
1022
+ condition: branch_condition,
1023
+ then_body: right_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: right_value)],
1024
+ else_body: nil,
1025
+ ),
1026
+ ],
1027
+ AST::Identifier.new(name: result_name),
1004
1028
  ]
1005
1029
  end
1006
1030
 
1007
- return [[], expression] if left_setup.empty? && right_setup.empty?
1008
-
1009
- result_type = infer_expression_type(expression, env:, expected_type:)
1010
- result_name = fresh_c_temp_name(env, expression.operator)
1011
- register_prepared_temp!(env, result_name, result_type)
1012
- result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1013
- left_value = lower_contextual_expression(left, env:, expected_type: result_type)
1014
- right_value = lower_contextual_expression(right, env: right_env, expected_type: result_type)
1015
- branch_condition = expression.operator == "and" ? result_ref : IR::Unary.new(operator: "not", operand: result_ref, type: @ctx.types.fetch("bool"))
1016
-
1017
- [
1018
- left_setup + [
1019
- IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: left_value),
1020
- IR::IfStmt.new(
1021
- condition: branch_condition,
1022
- then_body: right_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: right_value)],
1023
- else_body: nil,
1024
- ),
1025
- ],
1026
- AST::Identifier.new(name: result_name),
1027
- ]
1028
- end
1031
+ def ensure_tuple_struct(tuple_type)
1032
+ return unless tuple_type.is_a?(Types::Tuple)
1029
1033
 
1030
- def ensure_tuple_struct(tuple_type)
1031
- return unless tuple_type.is_a?(Types::Tuple)
1034
+ @registered_tuple_types ||= {}
1035
+ return if @registered_tuple_types[tuple_type]
1032
1036
 
1033
- @registered_tuple_types ||= {}
1034
- return if @registered_tuple_types[tuple_type]
1037
+ linkage_name = tuple_type_name(tuple_type)
1038
+ return if @artifacts.synthetic_structs.any? { |s| s.linkage_name == linkage_name }
1035
1039
 
1036
- linkage_name = tuple_type_name(tuple_type)
1037
- return if @artifacts.synthetic_structs.any? { |s| s.linkage_name == linkage_name }
1040
+ fields = tuple_type.element_types.each_with_index.map do |et, i|
1041
+ IR::Field.new(name: tuple_type.field_names[i], type: et)
1042
+ end
1043
+ @artifacts.synthetic_structs << IR::StructDecl.new(
1044
+ name: tuple_type.to_s,
1045
+ linkage_name: linkage_name,
1046
+ fields: fields,
1047
+ packed: false,
1048
+ alignment: nil,
1049
+ )
1050
+ @registered_tuple_types[tuple_type] = true
1051
+ end
1038
1052
 
1039
- fields = tuple_type.element_types.each_with_index.map do |et, i|
1040
- IR::Field.new(name: tuple_type.field_names[i], type: et)
1053
+ def tuple_type_name(type)
1054
+ sanitized = type.element_types.map { |et| sanitize_type_name_for_tuple(et) }.join("_")
1055
+ base = "mt_tuple_#{sanitized}"
1056
+ default_names = type.element_types.each_with_index.map { |_, i| "_#{i}" }
1057
+ if type.field_names != default_names
1058
+ base << "_" << type.field_names.map { |n| sanitize_type_name_for_tuple(n) }.join("_")
1059
+ end
1060
+ base
1041
1061
  end
1042
- @artifacts.synthetic_structs << IR::StructDecl.new(
1043
- name: tuple_type.to_s,
1044
- linkage_name: linkage_name,
1045
- fields: fields,
1046
- packed: false,
1047
- alignment: nil,
1048
- )
1049
- @registered_tuple_types[tuple_type] = true
1050
- end
1051
1062
 
1052
- def tuple_type_name(type)
1053
- sanitized = type.element_types.map { |et| sanitize_type_name_for_tuple(et) }.join("_")
1054
- base = "mt_tuple_#{sanitized}"
1055
- default_names = type.element_types.each_with_index.map { |_, i| "_#{i}" }
1056
- if type.field_names != default_names
1057
- base << "_" << type.field_names.map { |n| sanitize_type_name_for_tuple(n) }.join("_")
1063
+ def sanitize_type_name_for_tuple(type)
1064
+ type.to_s.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").gsub(/^_|_$/, "")
1058
1065
  end
1059
- base
1060
- end
1061
1066
 
1062
- def sanitize_type_name_for_tuple(type)
1063
- type.to_s.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").gsub(/^_|_$/, "")
1064
- end
1067
+ def prepare_if_expression_for_inline_lowering(expression, env:, expected_type: nil)
1068
+ condition_setup, condition = prepare_expression_for_inline_lowering(expression.condition, env:, expected_type: @ctx.types.fetch("bool"))
1069
+ then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1070
+ else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1071
+ result_type = infer_expression_type(expression, env:, expected_type:)
1072
+ then_setup, then_expression = prepare_expression_for_inline_lowering(expression.then_expression, env: then_env, expected_type: result_type)
1073
+ else_setup, else_expression = prepare_expression_for_inline_lowering(expression.else_expression, env: else_env, expected_type: result_type)
1065
1074
 
1066
- def prepare_if_expression_for_inline_lowering(expression, env:, expected_type: nil)
1067
- condition_setup, condition = prepare_expression_for_inline_lowering(expression.condition, env:, expected_type: @ctx.types.fetch("bool"))
1068
- then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1069
- else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1070
- result_type = infer_expression_type(expression, env:, expected_type:)
1071
- then_setup, then_expression = prepare_expression_for_inline_lowering(expression.then_expression, env: then_env, expected_type: result_type)
1072
- else_setup, else_expression = prepare_expression_for_inline_lowering(expression.else_expression, env: else_env, expected_type: result_type)
1073
-
1074
- return [[], expression] if condition_setup.empty? && then_setup.empty? && else_setup.empty?
1075
-
1076
- result_name = fresh_c_temp_name(env, "if_expr")
1077
- register_prepared_temp!(env, result_name, result_type)
1078
- result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1079
-
1080
- [
1081
- condition_setup + [
1082
- IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type)),
1083
- IR::IfStmt.new(
1084
- condition: lower_expression(condition, env:, expected_type: @ctx.types.fetch("bool")),
1085
- then_body: then_setup + [
1086
- IR::Assignment.new(
1087
- target: result_ref,
1088
- operator: "=",
1089
- value: lower_contextual_expression(then_expression, env: then_env, expected_type: result_type),
1090
- ),
1091
- ],
1092
- else_body: else_setup + [
1093
- IR::Assignment.new(
1094
- target: result_ref,
1095
- operator: "=",
1096
- value: lower_contextual_expression(else_expression, env: else_env, expected_type: result_type),
1097
- ),
1098
- ],
1099
- ),
1100
- ],
1101
- AST::Identifier.new(name: result_name),
1102
- ]
1103
- end
1075
+ return [[], expression] if condition_setup.empty? && then_setup.empty? && else_setup.empty?
1104
1076
 
1105
- def prepare_match_expression_for_inline_lowering(expression, env:, expected_type: nil)
1106
- scrutinee_type = infer_expression_type(expression.expression, env:)
1107
- expression_setup, prepared_expression = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type: scrutinee_type)
1108
- result_type = infer_expression_type(expression, env:, expected_type:)
1109
- result_name = fresh_c_temp_name(env, "match_expr")
1110
- register_prepared_temp!(env, result_name, result_type)
1111
- result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1112
- setup = expression_setup + [IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type))]
1113
- lowered_expression = lower_expression(prepared_expression, env:, expected_type: scrutinee_type)
1114
-
1115
- if scrutinee_type.is_a?(Types::Variant) &&
1116
- expression.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
1117
- !duplicable_foreign_argument_expression?(lowered_expression)
1118
- scrutinee_name = fresh_c_temp_name(env, "match_value")
1119
- setup << IR::LocalDecl.new(name: scrutinee_name, linkage_name: scrutinee_name, type: scrutinee_type, value: lowered_expression)
1120
- lowered_expression = IR::Name.new(name: scrutinee_name, type: scrutinee_type, pointer: false)
1077
+ result_name = fresh_c_temp_name(env, "if_expr")
1078
+ register_prepared_temp!(env, result_name, result_type)
1079
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1080
+
1081
+ [
1082
+ condition_setup + [
1083
+ IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type)),
1084
+ IR::IfStmt.new(
1085
+ condition: lower_expression(condition, env:, expected_type: @ctx.types.fetch("bool")),
1086
+ then_body: then_setup + [
1087
+ IR::Assignment.new(
1088
+ target: result_ref,
1089
+ operator: "=",
1090
+ value: lower_contextual_expression(then_expression, env: then_env, expected_type: result_type),
1091
+ ),
1092
+ ],
1093
+ else_body: else_setup + [
1094
+ IR::Assignment.new(
1095
+ target: result_ref,
1096
+ operator: "=",
1097
+ value: lower_contextual_expression(else_expression, env: else_env, expected_type: result_type),
1098
+ ),
1099
+ ],
1100
+ ),
1101
+ ],
1102
+ AST::Identifier.new(name: result_name),
1103
+ ]
1121
1104
  end
1122
1105
 
1123
- switch_expression = lowered_expression
1124
- string_if_chain = nil
1125
- range_if_chain = nil
1126
- cases = if scrutinee_type.is_a?(Types::Variant)
1127
- kind_type = @ctx.types.fetch("int")
1128
- switch_expression = IR::Member.new(receiver: lowered_expression, member: "kind", type: kind_type)
1129
- expression.arms.map do |arm|
1130
- arm_env = duplicate_env(env)
1131
- binding_decl = if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1132
- arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1133
- if arm_name && scrutinee_type.has_payload?(arm_name)
1134
- fields = scrutinee_type.arm(arm_name)
1135
- payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1136
- data_expr = IR::Member.new(receiver: lowered_expression, member: "data", type: nil)
1137
- arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1138
- binding_c = c_local_name(arm.binding_name)
1139
- arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1140
- IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
1106
+ def prepare_match_expression_for_inline_lowering(expression, env:, expected_type: nil)
1107
+ scrutinee_type = infer_expression_type(expression.expression, env:)
1108
+ expression_setup, prepared_expression = prepare_expression_for_inline_lowering(expression.expression, env:, expected_type: scrutinee_type)
1109
+ result_type = infer_expression_type(expression, env:, expected_type:)
1110
+ result_name = fresh_c_temp_name(env, "match_expr")
1111
+ register_prepared_temp!(env, result_name, result_type)
1112
+ result_ref = IR::Name.new(name: result_name, type: result_type, pointer: false)
1113
+ setup = expression_setup + [IR::LocalDecl.new(name: result_name, linkage_name: result_name, type: result_type, value: IR::ZeroInit.new(type: result_type))]
1114
+ lowered_expression = lower_expression(prepared_expression, env:, expected_type: scrutinee_type)
1115
+
1116
+ if scrutinee_type.is_a?(Types::Variant) &&
1117
+ expression.arms.any? { |arm| arm.binding_name && !wildcard_arm_pattern?(arm.pattern) } &&
1118
+ !duplicable_foreign_argument_expression?(lowered_expression)
1119
+ scrutinee_name = fresh_c_temp_name(env, "match_value")
1120
+ setup << IR::LocalDecl.new(name: scrutinee_name, linkage_name: scrutinee_name, type: scrutinee_type, value: lowered_expression)
1121
+ lowered_expression = IR::Name.new(name: scrutinee_name, type: scrutinee_type, pointer: false)
1122
+ end
1123
+
1124
+ switch_expression = lowered_expression
1125
+ string_if_chain = nil
1126
+ range_if_chain = nil
1127
+ cases = if scrutinee_type.is_a?(Types::Variant)
1128
+ kind_type = @ctx.types.fetch("int")
1129
+ switch_expression = IR::Member.new(receiver: lowered_expression, member: "kind", type: kind_type)
1130
+ expression.arms.map do |arm|
1131
+ arm_env = duplicate_env(env)
1132
+ binding_decl = if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
1133
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1134
+ if arm_name && scrutinee_type.has_payload?(arm_name)
1135
+ fields = scrutinee_type.arm(arm_name)
1136
+ payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
1137
+ data_expr = IR::Member.new(receiver: lowered_expression, member: "data", type: nil)
1138
+ arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
1139
+ binding_c = c_local_name(arm.binding_name)
1140
+ arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
1141
+ IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
1142
+ end
1143
+ end
1144
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1145
+ body = [binding_decl, *value_setup].compact
1146
+ body << IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))
1147
+ if wildcard_arm_pattern?(arm.pattern)
1148
+ IR::SwitchDefaultCase.new(body: body)
1149
+ else
1150
+ arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1151
+ IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body: body)
1141
1152
  end
1142
1153
  end
1143
- value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1144
- body = [binding_decl, *value_setup].compact
1145
- body << IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))
1146
- if wildcard_arm_pattern?(arm.pattern)
1147
- IR::SwitchDefaultCase.new(body: body)
1154
+ elsif scrutinee_type.is_a?(Types::StringView)
1155
+ bool_type = @ctx.types.fetch("bool")
1156
+ wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1157
+ non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1158
+ else_body = if wildcard
1159
+ arm_env = duplicate_env(env)
1160
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1161
+ value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1148
1162
  else
1149
- arm_name = variant_match_arm_name_from_pattern(arm.pattern)
1150
- IR::SwitchCase.new(value: IR::Name.new(name: enum_member_c_name(scrutinee_type, "kind_#{arm_name}"), type: kind_type, pointer: false), body: body)
1163
+ []
1151
1164
  end
1152
- end
1153
- elsif scrutinee_type.is_a?(Types::StringView)
1154
- bool_type = @ctx.types.fetch("bool")
1155
- wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1156
- non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1157
- else_body = if wildcard
1158
- arm_env = duplicate_env(env)
1159
- value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1160
- value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1161
- else
1162
- []
1163
- end
1164
- non_wildcard.reverse_each do |arm|
1165
- arm_env = duplicate_env(env)
1166
- value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1167
- then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1168
- lit = lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type)
1169
- cond = IR::Binary.new(operator: "==", left: lowered_expression, right: lit, type: bool_type)
1170
- else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1171
- end
1172
- string_if_chain = else_body
1173
- nil
1174
- elsif scrutinee_type.is_a?(Types::Tuple)
1175
- bool_type = @ctx.types.fetch("bool")
1176
- wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1177
- non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1178
- else_body = if wildcard
1179
- arm_env = duplicate_env(env)
1180
- value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1181
- value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1182
- else
1183
- []
1184
- end
1185
- non_wildcard.reverse_each do |arm|
1186
- arm_env = duplicate_env(env)
1187
- value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1188
- then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1189
- cond = tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env: arm_env)
1190
- else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1191
- end
1192
- string_if_chain = else_body
1193
- nil
1194
- else
1195
- has_range_arms = expression.arms.any? { |arm| arm.pattern.is_a?(AST::RangeExpr) }
1196
-
1197
- if has_range_arms
1165
+ non_wildcard.reverse_each do |arm|
1166
+ arm_env = duplicate_env(env)
1167
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1168
+ then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1169
+ lit = lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type)
1170
+ cond = IR::Binary.new(operator: "==", left: lowered_expression, right: lit, type: bool_type)
1171
+ else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1172
+ end
1173
+ string_if_chain = else_body
1174
+ nil
1175
+ elsif scrutinee_type.is_a?(Types::Tuple)
1198
1176
  bool_type = @ctx.types.fetch("bool")
1199
- non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1200
1177
  wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1201
- else_body = []
1202
- if wildcard
1203
- wildcard_env = duplicate_env(env)
1204
- wildcard_value_setup, wildcard_prepared = prepare_expression_for_inline_lowering(wildcard.value, env: wildcard_env, expected_type: result_type)
1205
- else_body = wildcard_value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(wildcard_prepared, env: wildcard_env, expected_type: result_type))]
1178
+ non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1179
+ else_body = if wildcard
1180
+ arm_env = duplicate_env(env)
1181
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(wildcard.value, env: arm_env, expected_type: result_type)
1182
+ value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1183
+ else
1184
+ []
1206
1185
  end
1207
1186
  non_wildcard.reverse_each do |arm|
1208
1187
  arm_env = duplicate_env(env)
1209
1188
  value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1210
1189
  then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1211
- cond = if arm.pattern.is_a?(AST::RangeExpr)
1212
- lower_range_match_condition(arm.pattern, switch_expression, bool_type, env: arm_env)
1213
- else
1214
- lit = lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type)
1215
- IR::Binary.new(operator: "==", left: switch_expression, right: lit, type: bool_type)
1216
- end
1190
+ cond = tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env: arm_env)
1217
1191
  else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1218
1192
  end
1219
- range_if_chain = else_body
1193
+ string_if_chain = else_body
1220
1194
  nil
1221
1195
  else
1222
- expression.arms.map do |arm|
1223
- arm_env = duplicate_env(env)
1224
- value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1225
- body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1226
- if wildcard_arm_pattern?(arm.pattern)
1227
- IR::SwitchDefaultCase.new(body: body)
1228
- else
1229
- IR::SwitchCase.new(value: lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type), body: body)
1196
+ has_range_arms = expression.arms.any? { |arm| arm.pattern.is_a?(AST::RangeExpr) }
1197
+
1198
+ if has_range_arms
1199
+ bool_type = @ctx.types.fetch("bool")
1200
+ non_wildcard = expression.arms.reject { |arm| wildcard_arm_pattern?(arm.pattern) }
1201
+ wildcard = expression.arms.find { |arm| wildcard_arm_pattern?(arm.pattern) }
1202
+ else_body = []
1203
+ if wildcard
1204
+ wildcard_env = duplicate_env(env)
1205
+ wildcard_value_setup, wildcard_prepared = prepare_expression_for_inline_lowering(wildcard.value, env: wildcard_env, expected_type: result_type)
1206
+ else_body = wildcard_value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(wildcard_prepared, env: wildcard_env, expected_type: result_type))]
1207
+ end
1208
+ non_wildcard.reverse_each do |arm|
1209
+ arm_env = duplicate_env(env)
1210
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1211
+ then_body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1212
+ cond = if arm.pattern.is_a?(AST::RangeExpr)
1213
+ lower_range_match_condition(arm.pattern, switch_expression, bool_type, env: arm_env)
1214
+ else
1215
+ lit = lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type)
1216
+ IR::Binary.new(operator: "==", left: switch_expression, right: lit, type: bool_type)
1217
+ end
1218
+ else_body = [IR::IfStmt.new(condition: cond, then_body:, else_body:)]
1219
+ end
1220
+ range_if_chain = else_body
1221
+ nil
1222
+ else
1223
+ expression.arms.map do |arm|
1224
+ arm_env = duplicate_env(env)
1225
+ value_setup, prepared_value = prepare_expression_for_inline_lowering(arm.value, env: arm_env, expected_type: result_type)
1226
+ body = value_setup + [IR::Assignment.new(target: result_ref, operator: "=", value: lower_contextual_expression(prepared_value, env: arm_env, expected_type: result_type))]
1227
+ if wildcard_arm_pattern?(arm.pattern)
1228
+ IR::SwitchDefaultCase.new(body: body)
1229
+ else
1230
+ IR::SwitchCase.new(value: lower_expression(arm.pattern, env: arm_env, expected_type: scrutinee_type), body: body)
1231
+ end
1230
1232
  end
1231
1233
  end
1232
1234
  end
1233
- end
1234
1235
 
1235
- if string_if_chain
1236
- [setup + string_if_chain, AST::Identifier.new(name: result_name)]
1237
- elsif range_if_chain
1238
- [setup + range_if_chain, AST::Identifier.new(name: result_name)]
1239
- else
1240
- [setup + [IR::SwitchStmt.new(expression: switch_expression, cases: cases, exhaustive: true)], AST::Identifier.new(name: result_name)]
1236
+ if string_if_chain
1237
+ [setup + string_if_chain, AST::Identifier.new(name: result_name)]
1238
+ elsif range_if_chain
1239
+ [setup + range_if_chain, AST::Identifier.new(name: result_name)]
1240
+ else
1241
+ [setup + [IR::SwitchStmt.new(expression: switch_expression, cases: cases, exhaustive: true)], AST::Identifier.new(name: result_name)]
1242
+ end
1241
1243
  end
1242
- end
1243
-
1244
- def tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env:)
1245
- elements = arm.pattern.elements
1246
- element_types = scrutinee_type.element_types
1247
- field_names = scrutinee_type.field_names
1248
1244
 
1249
- conds = elements.each_with_index.filter_map do |elem, index|
1250
- next if elem.is_a?(AST::Identifier) && elem.name == "_"
1251
-
1252
- field_expr = IR::Member.new(receiver: lowered_expression, member: field_names[index], type: element_types[index])
1253
- lit = lower_expression(elem, env:, expected_type: element_types[index])
1254
- IR::Binary.new(operator: "==", left: field_expr, right: lit, type: bool_type)
1255
- end
1245
+ def tuple_arm_condition(arm, lowered_expression, scrutinee_type, bool_type, env:)
1246
+ elements = arm.pattern.elements
1247
+ element_types = scrutinee_type.element_types
1248
+ field_names = scrutinee_type.field_names
1256
1249
 
1257
- conds.reduce { |acc, cond| IR::Binary.new(operator: "and", left: acc, right: cond, type: bool_type) }
1258
- end
1250
+ conds = elements.each_with_index.filter_map do |elem, index|
1251
+ next if elem.is_a?(AST::Identifier) && elem.name == "_"
1259
1252
 
1260
- def materialize_prepared_expression(setup, value, env:, type:, prefix:)
1261
- raise LoweringError.new("cannot use void expression inline", line: 0, column: 0, path: @ctx.current_analysis_path) unless value
1253
+ field_expr = IR::Member.new(receiver: lowered_expression, member: field_names[index], type: element_types[index])
1254
+ lit = lower_expression(elem, env:, expected_type: element_types[index])
1255
+ IR::Binary.new(operator: "==", left: field_expr, right: lit, type: bool_type)
1256
+ end
1262
1257
 
1263
- if value.is_a?(IR::Name)
1264
- register_prepared_temp!(env, value.name, value.type, pointer: value.pointer)
1265
- return [setup, AST::Identifier.new(name: value.name)]
1258
+ conds.reduce { |acc, cond| IR::Binary.new(operator: "and", left: acc, right: cond, type: bool_type) }
1266
1259
  end
1267
1260
 
1268
- temp_name = fresh_c_temp_name(env, prefix)
1269
- register_prepared_temp!(env, temp_name, type)
1270
- [
1271
- setup + [IR::LocalDecl.new(name: temp_name, linkage_name: temp_name, type:, value:)],
1272
- AST::Identifier.new(name: temp_name),
1273
- ]
1274
- end
1275
-
1276
- def register_prepared_temp!(env, name, type, pointer: false, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false)
1277
- current_actual_scope(env[:scopes])[name] = local_binding(type:, storage_type:, linkage_name: name, mutable: false, pointer:, projection:, cstr_backed:, cstr_list_backed:)
1278
- end
1279
-
1280
- def foreign_call_requires_statement_lowering?(expression, binding, env:)
1281
- return true if foreign_call_consumes_binding?(binding)
1261
+ def materialize_prepared_expression(setup, value, env:, type:, prefix:)
1262
+ raise LoweringError.new("cannot use void expression inline", line: 0, column: 0, path: @ctx.current_analysis_path) unless value
1282
1263
 
1283
- mapping_expression = foreign_mapping_expression(binding.ast)
1284
- reference_counts = foreign_mapping_reference_counts(mapping_expression)
1285
-
1286
- binding.ast.params.each_with_index do |param_ast, index|
1287
- public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
1288
- total_references = reference_counts.fetch(param_ast.name, 0)
1289
- total_references += reference_counts.fetch(public_alias, 0) if public_alias
1290
- next unless total_references > 1
1291
- next if duplicable_foreign_argument_expression?(expression.arguments.fetch(index).value)
1264
+ if value.is_a?(IR::Name)
1265
+ register_prepared_temp!(env, value.name, value.type, pointer: value.pointer)
1266
+ return [setup, AST::Identifier.new(name: value.name)]
1267
+ end
1292
1268
 
1293
- return true
1269
+ temp_name = fresh_c_temp_name(env, prefix)
1270
+ register_prepared_temp!(env, temp_name, type)
1271
+ [
1272
+ setup + [IR::LocalDecl.new(name: temp_name, linkage_name: temp_name, type:, value:)],
1273
+ AST::Identifier.new(name: temp_name),
1274
+ ]
1294
1275
  end
1295
1276
 
1296
- binding.ast.params.each_with_index do |param_ast, index|
1297
- parameter = binding.type.params.fetch(index)
1298
- next unless automatic_foreign_cstr_temp_needed?(parameter, expression.arguments.fetch(index).value, env:) ||
1299
- automatic_foreign_cstr_list_temp_needed?(parameter, expression.arguments.fetch(index).value, env:)
1300
-
1301
- return true
1277
+ def register_prepared_temp!(env, name, type, pointer: false, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false)
1278
+ current_actual_scope(env[:scopes])[name] = local_binding(type:, storage_type:, linkage_name: name, mutable: false, pointer:, projection:, cstr_backed:, cstr_list_backed:)
1302
1279
  end
1303
1280
 
1304
- expression.arguments.drop(binding.type.params.length).each do |argument|
1305
- return true if automatic_variadic_foreign_cstr_temp_needed?(argument.value, env:)
1306
- end
1281
+ def foreign_call_requires_statement_lowering?(expression, binding, env:)
1282
+ return true if foreign_call_consumes_binding?(binding)
1307
1283
 
1308
- false
1309
- end
1284
+ mapping_expression = foreign_mapping_expression(binding.ast)
1285
+ reference_counts = foreign_mapping_reference_counts(mapping_expression)
1310
1286
 
1311
- def lower_expression(expression, env:, expected_type: nil)
1312
- type = infer_expression_type(expression, env:, expected_type:)
1313
-
1314
- case expression
1315
- when AST::AwaitExpr
1316
- raise LoweringError.new("await expressions must be lowered in async statement context", line: 0, column: 0, path: @ctx.current_analysis_path)
1317
- when AST::IntegerLiteral
1318
- IR::IntegerLiteral.new(value: expression.value, type:)
1319
- when AST::CharLiteral
1320
- IR::IntegerLiteral.new(value: expression.value, type:)
1321
- when AST::FloatLiteral
1322
- IR::FloatLiteral.new(value: expression.value, type:)
1323
- when AST::SizeofExpr
1324
- target_type = resolve_type_ref_with_fallback(expression.type, env:)
1325
- target_type ? IR::SizeofExpr.new(target_type:, type:) : raise(LoweringError, "size_of argument is not a concrete type")
1326
- when AST::AlignofExpr
1327
- target_type = resolve_type_ref_with_fallback(expression.type, env:)
1328
- target_type ? IR::AlignofExpr.new(target_type:, type:) : raise(LoweringError, "align_of argument is not a concrete type")
1329
- when AST::OffsetofExpr
1330
- target_type = resolve_type_ref(expression.type)
1331
- if !@bypass_sema_type_cache && (precomputed = @ctx.const_values[@ctx.ast.node_ids[expression.object_id]])
1332
- IR::IntegerLiteral.new(value: precomputed, type:)
1333
- elsif (binding = lookup_value(expression.field, env)) && binding[:const_value].is_a?(Types::FieldHandle)
1334
- IR::OffsetofExpr.new(target_type:, field: binding[:const_value].field_name, type:)
1335
- else
1336
- IR::OffsetofExpr.new(target_type:, field: expression.field, type:)
1337
- end
1338
- when AST::StringLiteral
1339
- IR::StringLiteral.new(value: expression.value, type:, cstring: expression.cstring)
1340
- when AST::FormatString
1341
- raise LoweringError.new("unprepared format string reached raw lowering; format strings should be materialized before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
1342
- when AST::BooleanLiteral
1343
- IR::BooleanLiteral.new(value: expression.value, type:)
1344
- when AST::NullLiteral
1345
- IR::NullLiteral.new(type:)
1346
- when AST::Identifier
1347
- binding = lookup_value(expression.name, env)
1348
- if binding
1349
- lower_bound_identifier(binding, expected_type:)
1350
- elsif @ctx.functions.key?(expression.name)
1351
- function_binding = @ctx.functions.fetch(expression.name)
1352
- raise LoweringError.new("generic function #{expression.name} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if function_binding.type_params.any?
1353
- raise LoweringError.new("foreign function #{expression.name} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if foreign_function_binding?(function_binding)
1354
-
1355
- IR::Name.new(name: function_binding_c_name(function_binding, module_name: @ctx.module_name), type: type, pointer: false)
1356
- else
1357
- raise LoweringError.new("unsupported identifier #{expression.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
1287
+ binding.ast.params.each_with_index do |param_ast, index|
1288
+ public_alias = param_ast.boundary_type ? foreign_mapping_public_alias_name(param_ast.name) : nil
1289
+ total_references = reference_counts.fetch(param_ast.name, 0)
1290
+ total_references += reference_counts.fetch(public_alias, 0) if public_alias
1291
+ next unless total_references > 1
1292
+ next if duplicable_foreign_argument_expression?(expression.arguments.fetch(index).value)
1293
+
1294
+ return true
1358
1295
  end
1359
- when AST::MemberAccess
1360
- lower_member_access(expression, env:, type:)
1361
- when AST::IndexAccess
1362
- receiver_type = infer_expression_type(expression.receiver, env:)
1363
- receiver = lower_expression(expression.receiver, env:)
1364
- index = lower_expression(expression.index, env:)
1365
- if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
1366
- IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
1367
- elsif receiver_type.is_a?(Types::Span)
1368
- IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
1369
- else
1370
- IR::Index.new(receiver:, index:, type:)
1296
+
1297
+ binding.ast.params.each_with_index do |param_ast, index|
1298
+ parameter = binding.type.params.fetch(index)
1299
+ next unless automatic_foreign_cstr_temp_needed?(parameter, expression.arguments.fetch(index).value, env:) ||
1300
+ automatic_foreign_cstr_list_temp_needed?(parameter, expression.arguments.fetch(index).value, env:)
1301
+
1302
+ return true
1371
1303
  end
1372
- when AST::UnaryOp
1373
- raise LoweringError.new("propagation expressions must be prepared before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path) if expression.operator == "?"
1374
1304
 
1375
- operand = lower_expression(expression.operand, env:, expected_type: type)
1376
- expanded = lower_vector_unary_op(expression.operator, operand, type)
1377
- return expanded if expanded
1305
+ expression.arguments.drop(binding.type.params.length).each do |argument|
1306
+ return true if automatic_variadic_foreign_cstr_temp_needed?(argument.value, env:)
1307
+ end
1378
1308
 
1379
- IR::Unary.new(operator: expression.operator, operand:, type:)
1380
- when AST::BinaryOp
1381
- right_env = binary_right_env(expression, env)
1382
- left_type, right_type = infer_binary_operand_types(expression, env:, expected_type: type)
1383
- operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
1384
- left = lower_expression(expression.left, env:, expected_type: operand_type || type)
1385
- right = lower_expression(expression.right, env: right_env, expected_type: operand_type || left.type)
1386
- left = cast_expression(left, operand_type) if operand_type
1387
- right = cast_expression(right, operand_type) if operand_type
1309
+ false
1310
+ end
1388
1311
 
1389
- expanded = lower_vector_binary_op(expression.operator, left, left_type, right, right_type, type)
1390
- return expanded if expanded
1312
+ def lower_expression(expression, env:, expected_type: nil)
1313
+ type = infer_expression_type(expression, env:, expected_type:)
1391
1314
 
1392
- IR::Binary.new(operator: expression.operator, left:, right:, type:)
1393
- when AST::IfExpr
1394
- then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1395
- else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1396
- IR::Conditional.new(
1397
- condition: lower_expression(expression.condition, env:, expected_type: @ctx.types.fetch("bool")),
1398
- then_expression: lower_contextual_expression(expression.then_expression, env: then_env, expected_type: type),
1399
- else_expression: lower_contextual_expression(expression.else_expression, env: else_env, expected_type: type),
1400
- type:,
1401
- )
1402
- when AST::MatchExpr
1403
- raise LoweringError.new("match expressions must be prepared before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
1404
- when AST::UnsafeExpr
1405
- lower_expression(expression.expression, env:, expected_type: type)
1406
- when AST::ProcExpr
1407
- proc_type = type.is_a?(Types::Proc) ? type : infer_expression_type(expression, env:, expected_type: type)
1408
- _setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
1409
- value
1410
- when AST::DetachExpr
1411
- lower_detach_expr(expression, env:)
1412
- when AST::Call
1413
- lower_call(expression, env:, type:)
1414
- when AST::PrefixCast
1415
- lowered_arg = lower_expression(expression.expression, env:)
1416
- IR::Cast.new(target_type: type, expression: lowered_arg, type:)
1417
- when AST::Specialization
1418
- lower_specialization(expression, env:, type:)
1419
- when AST::ExpressionList
1420
- if array_type?(type) || array_type?(expected_type)
1421
- lowered_elements = expression.elements.map do |element|
1422
- if element.is_a?(AST::Argument)
1423
- lower_expression(element.value, env:, expected_type: array_element_type(expected_type || type))
1424
- else
1425
- lower_expression(element, env:, expected_type: array_element_type(expected_type || type))
1426
- end
1315
+ case expression
1316
+ when AST::AwaitExpr
1317
+ raise LoweringError.new("await expressions must be lowered in async statement context", line: 0, column: 0, path: @ctx.current_analysis_path)
1318
+ when AST::IntegerLiteral
1319
+ IR::IntegerLiteral.new(value: expression.value, type:)
1320
+ when AST::CharLiteral
1321
+ IR::IntegerLiteral.new(value: expression.value, type:)
1322
+ when AST::FloatLiteral
1323
+ IR::FloatLiteral.new(value: expression.value, type:)
1324
+ when AST::SizeofExpr
1325
+ target_type = resolve_type_ref_with_fallback(expression.type, env:)
1326
+ target_type ? IR::SizeofExpr.new(target_type:, type:) : raise(LoweringError, "size_of argument is not a concrete type")
1327
+ when AST::AlignofExpr
1328
+ target_type = resolve_type_ref_with_fallback(expression.type, env:)
1329
+ target_type ? IR::AlignofExpr.new(target_type:, type:) : raise(LoweringError, "align_of argument is not a concrete type")
1330
+ when AST::OffsetofExpr
1331
+ target_type = resolve_type_ref(expression.type)
1332
+ if !@bypass_sema_type_cache && (precomputed = @ctx.const_values[@ctx.ast.node_ids[expression.object_id]])
1333
+ IR::IntegerLiteral.new(value: precomputed, type:)
1334
+ elsif (binding = lookup_value(expression.field, env)) && binding[:const_value].is_a?(Types::FieldHandle)
1335
+ IR::OffsetofExpr.new(target_type:, field: binding[:const_value].field_name, type:)
1336
+ else
1337
+ IR::OffsetofExpr.new(target_type:, field: expression.field, type:)
1427
1338
  end
1428
- IR::ArrayLiteral.new(type: expected_type || type, elements: lowered_elements)
1429
- else
1430
- ensure_tuple_struct(type)
1431
- fields = expression.elements.each_with_index.map do |element, index|
1432
- if element.is_a?(AST::Argument)
1433
- element_type = type.element_types[index]
1434
- field_name = element.name
1435
- IR::AggregateField.new(name: field_name, value: lower_expression(element.value, env:, expected_type: element_type))
1436
- else
1437
- element_type = type.element_types[index]
1438
- IR::AggregateField.new(name: "_#{index}", value: lower_expression(element, env:, expected_type: element_type))
1339
+ when AST::StringLiteral
1340
+ IR::StringLiteral.new(value: expression.value, type:, cstring: expression.cstring)
1341
+ when AST::FormatString
1342
+ raise LoweringError.new("unprepared format string reached raw lowering; format strings should be materialized before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
1343
+ when AST::BooleanLiteral
1344
+ IR::BooleanLiteral.new(value: expression.value, type:)
1345
+ when AST::NullLiteral
1346
+ IR::NullLiteral.new(type:)
1347
+ when AST::Identifier
1348
+ binding = lookup_value(expression.name, env)
1349
+ if binding
1350
+ lower_bound_identifier(binding, expected_type:)
1351
+ elsif @ctx.functions.key?(expression.name)
1352
+ function_binding = @ctx.functions.fetch(expression.name)
1353
+ raise LoweringError.new("generic function #{expression.name} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if function_binding.type_params.any?
1354
+ raise LoweringError.new("foreign function #{expression.name} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if foreign_function_binding?(function_binding)
1355
+
1356
+ IR::Name.new(name: function_binding_c_name(function_binding, module_name: @ctx.module_name), type: type, pointer: false)
1357
+ else
1358
+ raise LoweringError.new("unsupported identifier #{expression.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
1359
+ end
1360
+ when AST::MemberAccess
1361
+ lower_member_access(expression, env:, type:)
1362
+ when AST::IndexAccess
1363
+ receiver_type = infer_expression_type(expression.receiver, env:)
1364
+ receiver = lower_expression(expression.receiver, env:)
1365
+ index = lower_expression(expression.index, env:)
1366
+ if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
1367
+ IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
1368
+ elsif receiver_type.is_a?(Types::Span)
1369
+ IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
1370
+ else
1371
+ IR::Index.new(receiver:, index:, type:)
1372
+ end
1373
+ when AST::UnaryOp
1374
+ raise LoweringError.new("propagation expressions must be prepared before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path) if expression.operator == "?"
1375
+
1376
+ operand = lower_expression(expression.operand, env:, expected_type: type)
1377
+ expanded = lower_vector_unary_operation(expression.operator, operand, type)
1378
+ return expanded if expanded
1379
+
1380
+ IR::Unary.new(operator: expression.operator, operand:, type:)
1381
+ when AST::BinaryOp
1382
+ right_env = binary_right_env(expression, env)
1383
+ left_type, right_type = infer_binary_operand_types(expression, env:, expected_type: type)
1384
+ operand_type = promoted_binary_operand_type(expression.operator, left_type, right_type)
1385
+ left = lower_expression(expression.left, env:, expected_type: operand_type || type)
1386
+ right = lower_expression(expression.right, env: right_env, expected_type: operand_type || left.type)
1387
+ left = cast_expression(left, operand_type) if operand_type
1388
+ right = cast_expression(right, operand_type) if operand_type
1389
+
1390
+ expanded = lower_vector_binary_operation(expression.operator, left, left_type, right, right_type, type)
1391
+ return expanded if expanded
1392
+
1393
+ IR::Binary.new(operator: expression.operator, left:, right:, type:)
1394
+ when AST::IfExpr
1395
+ then_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: true, env:))
1396
+ else_env = env_with_refinements(env, flow_refinements(expression.condition, truthy: false, env:))
1397
+ IR::Conditional.new(
1398
+ condition: lower_expression(expression.condition, env:, expected_type: @ctx.types.fetch("bool")),
1399
+ then_expression: lower_contextual_expression(expression.then_expression, env: then_env, expected_type: type),
1400
+ else_expression: lower_contextual_expression(expression.else_expression, env: else_env, expected_type: type),
1401
+ type:,
1402
+ )
1403
+ when AST::MatchExpr
1404
+ raise LoweringError.new("match expressions must be prepared before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path)
1405
+ when AST::UnsafeExpr
1406
+ lower_expression(expression.expression, env:, expected_type: type)
1407
+ when AST::ProcExpr
1408
+ proc_type = type.is_a?(Types::Proc) ? type : infer_expression_type(expression, env:, expected_type: type)
1409
+ _setup, value = lower_proc_expression_for_local(expression, env:, local_name: fresh_c_temp_name(env, "proc_expr"), proc_type: proc_type)
1410
+ value
1411
+ when AST::DetachExpr
1412
+ lower_detach_expr(expression, env:)
1413
+ when AST::Call
1414
+ lower_call(expression, env:, type:)
1415
+ when AST::PrefixCast
1416
+ lowered_arg = lower_expression(expression.expression, env:)
1417
+ IR::Cast.new(target_type: type, expression: lowered_arg, type:)
1418
+ when AST::Specialization
1419
+ lower_specialization(expression, env:, type:)
1420
+ when AST::ExpressionList
1421
+ if array_type?(type) || array_type?(expected_type)
1422
+ lowered_elements = expression.elements.map do |element|
1423
+ if element.is_a?(AST::Argument)
1424
+ lower_expression(element.value, env:, expected_type: array_element_type(expected_type || type))
1425
+ else
1426
+ lower_expression(element, env:, expected_type: array_element_type(expected_type || type))
1427
+ end
1428
+ end
1429
+ IR::ArrayLiteral.new(type: expected_type || type, elements: lowered_elements)
1430
+ else
1431
+ ensure_tuple_struct(type)
1432
+ fields = expression.elements.each_with_index.map do |element, index|
1433
+ if element.is_a?(AST::Argument)
1434
+ element_type = type.element_types[index]
1435
+ field_name = element.name
1436
+ IR::AggregateField.new(name: field_name, value: lower_expression(element.value, env:, expected_type: element_type))
1437
+ else
1438
+ element_type = type.element_types[index]
1439
+ IR::AggregateField.new(name: "_#{index}", value: lower_expression(element, env:, expected_type: element_type))
1440
+ end
1439
1441
  end
1442
+ IR::AggregateLiteral.new(type:, fields:)
1440
1443
  end
1441
- IR::AggregateLiteral.new(type:, fields:)
1444
+ else
1445
+ raise LoweringError.new("unsupported expression #{expression.class.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
1442
1446
  end
1443
- else
1444
- raise LoweringError.new("unsupported expression #{expression.class.name}", line: 0, column: 0, path: @ctx.current_analysis_path)
1445
1447
  end
1446
- end
1447
1448
 
1448
- def lower_member_access(expression, env:, type:)
1449
- if (type_expr = resolve_type_expression(expression.receiver))
1450
- if type_expr.is_a?(Types::Variant)
1451
- return IR::VariantLiteral.new(type: type_expr, arm_name: expression.member, fields: [])
1452
- end
1449
+ def lower_member_access(expression, env:, type:)
1450
+ if (type_expr = resolve_type_expression(expression.receiver))
1451
+ if type_expr.is_a?(Types::Variant)
1452
+ return IR::VariantLiteral.new(type: type_expr, arm_name: expression.member, fields: [])
1453
+ end
1453
1454
 
1454
- member_name = if (type_expr.is_a?(Types::Enum) || type_expr.is_a?(Types::Flags)) && !type_expr.external
1455
- enum_member_c_name(type_expr, expression.member)
1456
- else
1457
- expression.member
1455
+ member_name = if (type_expr.is_a?(Types::Enum) || type_expr.is_a?(Types::Flags)) && !type_expr.external
1456
+ enum_member_c_name(type_expr, expression.member)
1457
+ else
1458
+ expression.member
1459
+ end
1460
+ return IR::Name.new(name: member_name, type:, pointer: false)
1458
1461
  end
1459
- return IR::Name.new(name: member_name, type:, pointer: false)
1460
- end
1461
1462
 
1462
- if expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.receiver.name)
1463
- imported_module = @ctx.imports.fetch(expression.receiver.name)
1464
- if imported_module.functions.key?(expression.member)
1465
- function_binding = imported_module.functions.fetch(expression.member)
1466
- raise LoweringError.new("generic function #{expression.receiver.name}.#{expression.member} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if function_binding.type_params.any?
1467
- raise LoweringError.new("foreign function #{expression.receiver.name}.#{expression.member} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if foreign_function_binding?(function_binding)
1463
+ if expression.receiver.is_a?(AST::Identifier) && @ctx.imports.key?(expression.receiver.name)
1464
+ imported_module = @ctx.imports.fetch(expression.receiver.name)
1465
+ if imported_module.functions.key?(expression.member)
1466
+ function_binding = imported_module.functions.fetch(expression.member)
1467
+ raise LoweringError.new("generic function #{expression.receiver.name}.#{expression.member} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if function_binding.type_params.any?
1468
+ raise LoweringError.new("foreign function #{expression.receiver.name}.#{expression.member} cannot be used as a value", line: 0, column: 0, path: @ctx.current_analysis_path) if foreign_function_binding?(function_binding)
1468
1469
 
1469
- return IR::Name.new(name: function_binding_c_name(function_binding, module_name: imported_module.name), type:, pointer: false)
1470
- end
1470
+ return IR::Name.new(name: function_binding_c_name(function_binding, module_name: imported_module.name), type:, pointer: false)
1471
+ end
1471
1472
 
1472
- return IR::Name.new(name: imported_value_c_name(imported_module, expression.member), type:, pointer: false)
1473
- end
1473
+ return IR::Name.new(name: imported_value_c_name(imported_module, expression.member), type:, pointer: false)
1474
+ end
1474
1475
 
1475
- receiver_type = infer_expression_type(expression.receiver, env:)
1476
+ receiver_type = infer_expression_type(expression.receiver, env:)
1476
1477
 
1477
- if expression.receiver.is_a?(AST::IndexAccess)
1478
- base_type = infer_expression_type(expression.receiver.receiver, env:)
1479
- if base_type.is_a?(Types::SoA)
1480
- return lower_soa_indexed_field_access(expression.receiver.receiver, expression.receiver.index, expression.member, base_type, env:, type:)
1478
+ if expression.receiver.is_a?(AST::IndexAccess)
1479
+ base_type = infer_expression_type(expression.receiver.receiver, env:)
1480
+ if base_type.is_a?(Types::SoA)
1481
+ return lower_soa_indexed_field_access(expression.receiver.receiver, expression.receiver.index, expression.member, base_type, env:, type:)
1482
+ end
1481
1483
  end
1482
- end
1483
1484
 
1484
- if receiver_type == @ctx.types["field_handle"]
1485
- handle = compile_time_const_value(expression.receiver, env:)
1486
- return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::FieldHandle)
1487
- end
1488
- if receiver_type == @ctx.types["member_handle"]
1489
- handle = compile_time_const_value(expression.receiver, env:)
1490
- return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::MemberHandle)
1491
- end
1485
+ if receiver_type == @ctx.types["field_handle"]
1486
+ handle = compile_time_const_value(expression.receiver, env:)
1487
+ return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::FieldHandle)
1488
+ end
1489
+ if receiver_type == @ctx.types["member_handle"]
1490
+ handle = compile_time_const_value(expression.receiver, env:)
1491
+ return lower_compile_time_handle_member(handle, expression.member, type) if handle.is_a?(Types::MemberHandle)
1492
+ end
1492
1493
 
1493
- if array_type?(receiver_type) && expression.member == "as_span"
1494
- return lower_array_to_span_expression(lower_expression(expression.receiver, env:), type)
1495
- end
1494
+ if array_type?(receiver_type) && expression.member == "as_span"
1495
+ return lower_array_to_span_expression(lower_expression(expression.receiver, env:), type)
1496
+ end
1496
1497
 
1497
- receiver = lower_expression(expression.receiver, env:)
1498
- member_expr = IR::Member.new(receiver:, member: member_c_name(receiver_type, expression.member), type:)
1499
- if self_referencing_variant_field_access?(receiver_type, expression.member, type)
1500
- return IR::Unary.new(operator: "*", operand: member_expr, type:)
1498
+ receiver = lower_expression(expression.receiver, env:)
1499
+ member_expr = IR::Member.new(receiver:, member: member_c_name(receiver_type, expression.member), type:)
1500
+ if self_referencing_variant_field_access?(receiver_type, expression.member, type)
1501
+ return IR::Unary.new(operator: "*", operand: member_expr, type:)
1502
+ end
1503
+ member_expr
1501
1504
  end
1502
- member_expr
1503
- end
1504
1505
 
1505
- def self_referencing_variant_field_access?(receiver_type, member, field_type)
1506
- return false unless receiver_type.is_a?(Types::VariantArmPayload)
1506
+ def self_referencing_variant_field_access?(receiver_type, member, field_type)
1507
+ return false unless receiver_type.is_a?(Types::VariantArmPayload)
1507
1508
 
1508
- outer = receiver_type.variant_type
1509
- return false unless outer.is_a?(Types::Variant) || outer.is_a?(Types::VariantInstance)
1509
+ outer = receiver_type.variant_type
1510
+ return false unless outer.is_a?(Types::Variant) || outer.is_a?(Types::VariantInstance)
1510
1511
 
1511
- # The C backend pointer-breaks every cyclic field, including cycles that
1512
- # run through generic instantiations such as `Option[Atom]` inside `Atom`.
1513
- # Deref the access whenever the field type can reach the outer variant.
1514
- type_reaches_target?(field_type, outer)
1515
- end
1512
+ # The C backend pointer-breaks every cyclic field, including cycles that
1513
+ # run through generic instantiations such as `Option[Atom]` inside `Atom`.
1514
+ # Deref the access whenever the field type can reach the outer variant.
1515
+ type_reaches_target?(field_type, outer)
1516
+ end
1516
1517
 
1517
- def type_reaches_target?(type, target)
1518
- type_reaches_target_with_visited?(type, target, {})
1519
- end
1518
+ def type_reaches_target?(type, target)
1519
+ type_reaches_target_with_visited?(type, target, {})
1520
+ end
1520
1521
 
1521
- def type_reaches_target_with_visited?(type, target, visited)
1522
- return true if type == target
1523
- return false if visited[type.object_id]
1522
+ def type_reaches_target_with_visited?(type, target, visited)
1523
+ return true if type == target
1524
+ return false if visited[type.object_id]
1524
1525
 
1525
- visited[type.object_id] = true
1526
- reachability_children(type).any? do |child|
1527
- type_reaches_target_with_visited?(child, target, visited)
1526
+ visited[type.object_id] = true
1527
+ reachability_children(type).any? do |child|
1528
+ type_reaches_target_with_visited?(child, target, visited)
1529
+ end
1528
1530
  end
1529
- end
1530
1531
 
1531
- # Structural expansion used by cyclic-field reachability. Mirrors the
1532
- # CBackend's `aggregate_type_dependencies` so the deref injected for a
1533
- # pointer-broken cyclic field agrees with the emitted C struct layout.
1534
- def reachability_children(type)
1535
- case type
1536
- when Types::Nullable
1537
- [type.base]
1538
- when Types::GenericInstance
1539
- if pointer_type?(type)
1540
- []
1541
- elsif array_type?(type)
1542
- [array_element_type(type)]
1532
+ # Structural expansion used by cyclic-field reachability. Mirrors the
1533
+ # CBackend's `aggregate_type_dependencies` so the deref injected for a
1534
+ # pointer-broken cyclic field agrees with the emitted C struct layout.
1535
+ def reachability_children(type)
1536
+ case type
1537
+ when Types::Nullable
1538
+ [type.base]
1539
+ when Types::GenericInstance
1540
+ if pointer_type?(type)
1541
+ []
1542
+ elsif array_type?(type)
1543
+ [array_element_type(type)]
1544
+ else
1545
+ []
1546
+ end
1547
+ when Types::Span
1548
+ [type.element_type]
1549
+ when Types::Struct, Types::StructInstance, Types::Union
1550
+ type.fields.values
1551
+ when Types::Variant, Types::VariantInstance
1552
+ type.arm_names.flat_map { |name| type.arm(name).values }
1553
+ when Types::VariantArmPayload
1554
+ type.fields.values
1543
1555
  else
1544
1556
  []
1545
1557
  end
1546
- when Types::Span
1547
- [type.element_type]
1548
- when Types::Struct, Types::StructInstance, Types::Union
1549
- type.fields.values
1550
- when Types::Variant, Types::VariantInstance
1551
- type.arm_names.flat_map { |name| type.arm(name).values }
1552
- when Types::VariantArmPayload
1553
- type.fields.values
1554
- else
1555
- []
1556
1558
  end
1557
- end
1558
1559
 
1559
- def lower_compile_time_handle_member(handle, member, type)
1560
- case handle
1561
- when Types::FieldHandle
1562
- case member
1563
- when "name" then IR::StringLiteral.new(value: handle.field_name, type: @ctx.types["str"], cstring: false)
1564
- when "type" then nil # handled via compile_time_const_value before lowering
1565
- end
1566
- when Types::MemberHandle
1567
- case member
1568
- when "name" then IR::StringLiteral.new(value: handle.member_name, type: @ctx.types["str"], cstring: false)
1569
- when "value"
1570
- value_type = @ctx.types["int"]
1571
- IR::IntegerLiteral.new(value: handle.member_value || 0, type: value_type)
1560
+ def lower_compile_time_handle_member(handle, member, type)
1561
+ case handle
1562
+ when Types::FieldHandle
1563
+ case member
1564
+ when "name" then IR::StringLiteral.new(value: handle.field_name, type: @ctx.types["str"], cstring: false)
1565
+ when "type" then nil # handled via compile_time_const_value before lowering
1566
+ end
1567
+ when Types::MemberHandle
1568
+ case member
1569
+ when "name" then IR::StringLiteral.new(value: handle.member_name, type: @ctx.types["str"], cstring: false)
1570
+ when "value"
1571
+ value_type = @ctx.types["int"]
1572
+ IR::IntegerLiteral.new(value: handle.member_value || 0, type: value_type)
1573
+ end
1572
1574
  end
1573
1575
  end
1574
- end
1575
1576
 
1576
- def lower_vector_binary_op(operator, left, left_type, right, right_type, result_type)
1577
- return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1577
+ def lower_vector_binary_operation(operator, left, left_type, right, right_type, result_type)
1578
+ return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1578
1579
 
1579
- if result_type.is_a?(Types::Vector)
1580
- return lower_vector_binary_op_on_vectors(operator, left, left_type, right, right_type, result_type)
1581
- end
1580
+ if result_type.is_a?(Types::Vector)
1581
+ return lower_vector_binary_operation_on_vectors(operator, left, left_type, right, right_type, result_type)
1582
+ end
1582
1583
 
1583
- return lower_aggregate_binary_op(operator, left, right, result_type) if operator == "+" || operator == "-"
1584
+ return lower_aggregate_binary_operation(operator, left, right, result_type) if operator == "+" || operator == "-"
1584
1585
 
1585
- return lower_aggregate_binary_op(operator, left, right, result_type) if result_type.is_a?(Types::Quaternion)
1586
+ return lower_aggregate_binary_operation(operator, left, right, result_type) if result_type.is_a?(Types::Quaternion)
1586
1587
 
1587
- scalar_is_left = !left_type.is_a?(Types::Matrix)
1588
- aggregate_expr = scalar_is_left ? right : left
1589
- scalar_expr = scalar_is_left ? left : right
1590
- scalar_type = scalar_is_left ? left_type : right_type
1588
+ scalar_is_left = !left_type.is_a?(Types::Matrix)
1589
+ aggregate_expr = scalar_is_left ? right : left
1590
+ scalar_expr = scalar_is_left ? left : right
1591
+ scalar_type = scalar_is_left ? left_type : right_type
1591
1592
 
1592
- fields = result_type.fields.map do |fname, ftype|
1593
- field_expr = IR::Member.new(receiver: aggregate_expr, member: fname, type: ftype)
1594
- left_expr = scalar_is_left ? scalar_expr : field_expr
1595
- right_expr = scalar_is_left ? field_expr : scalar_expr
1596
- value = lower_vector_binary_op(operator, left_expr, ftype, right_expr, scalar_type, ftype) ||
1597
- IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1598
- IR::AggregateField.new(name: fname, value:)
1593
+ fields = result_type.fields.map do |fname, ftype|
1594
+ field_expr = IR::Member.new(receiver: aggregate_expr, member: fname, type: ftype)
1595
+ left_expr = scalar_is_left ? scalar_expr : field_expr
1596
+ right_expr = scalar_is_left ? field_expr : scalar_expr
1597
+ value = lower_vector_binary_operation(operator, left_expr, ftype, right_expr, scalar_type, ftype) ||
1598
+ IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1599
+ IR::AggregateField.new(name: fname, value:)
1600
+ end
1601
+ IR::AggregateLiteral.new(fields:, type: result_type)
1599
1602
  end
1600
- IR::AggregateLiteral.new(fields:, type: result_type)
1601
- end
1602
1603
 
1603
- def lower_vector_unary_op(operator, operand, result_type)
1604
- return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1605
- return nil unless operator == "+" || operator == "-"
1604
+ def lower_vector_unary_operation(operator, operand, result_type)
1605
+ return nil unless result_type.is_a?(Types::Vector) || result_type.is_a?(Types::Matrix) || result_type.is_a?(Types::Quaternion)
1606
+ return nil unless operator == "+" || operator == "-"
1606
1607
 
1607
- fields = result_type.fields.map do |fname, ftype|
1608
- field_expr = IR::Member.new(receiver: operand, member: fname, type: ftype)
1609
- value = lower_vector_unary_op(operator, field_expr, ftype) || IR::Unary.new(operator:, operand: field_expr, type: ftype)
1610
- IR::AggregateField.new(name: fname, value:)
1608
+ fields = result_type.fields.map do |fname, ftype|
1609
+ field_expr = IR::Member.new(receiver: operand, member: fname, type: ftype)
1610
+ value = lower_vector_unary_operation(operator, field_expr, ftype) || IR::Unary.new(operator:, operand: field_expr, type: ftype)
1611
+ IR::AggregateField.new(name: fname, value:)
1612
+ end
1613
+ IR::AggregateLiteral.new(fields:, type: result_type)
1611
1614
  end
1612
- IR::AggregateLiteral.new(fields:, type: result_type)
1613
- end
1614
1615
 
1615
- def lower_vector_binary_op_on_vectors(operator, left, left_type, right, right_type, result_type)
1616
- if left_type.is_a?(Types::Vector) && right_type.is_a?(Types::Vector)
1617
- return lower_aggregate_binary_op(operator, left, right, result_type)
1618
- end
1616
+ def lower_vector_binary_operation_on_vectors(operator, left, left_type, right, right_type, result_type)
1617
+ if left_type.is_a?(Types::Vector) && right_type.is_a?(Types::Vector)
1618
+ return lower_aggregate_binary_operation(operator, left, right, result_type)
1619
+ end
1619
1620
 
1620
- return nil unless operator == "*" || operator == "/"
1621
+ return nil unless operator == "*" || operator == "/"
1621
1622
 
1622
- scalar_is_left = !left_type.is_a?(Types::Vector)
1623
- vector_expr = scalar_is_left ? right : left
1624
- scalar_expr = scalar_is_left ? left : right
1623
+ scalar_is_left = !left_type.is_a?(Types::Vector)
1624
+ vector_expr = scalar_is_left ? right : left
1625
+ scalar_expr = scalar_is_left ? left : right
1625
1626
 
1626
- fields = result_type.fields.map do |fname, ftype|
1627
- field_expr = IR::Member.new(receiver: vector_expr, member: fname, type: ftype)
1628
- left_expr = scalar_is_left ? scalar_expr : field_expr
1629
- right_expr = scalar_is_left ? field_expr : scalar_expr
1630
- value = IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1631
- IR::AggregateField.new(name: fname, value:)
1627
+ fields = result_type.fields.map do |fname, ftype|
1628
+ field_expr = IR::Member.new(receiver: vector_expr, member: fname, type: ftype)
1629
+ left_expr = scalar_is_left ? scalar_expr : field_expr
1630
+ right_expr = scalar_is_left ? field_expr : scalar_expr
1631
+ value = IR::Binary.new(operator:, left: left_expr, right: right_expr, type: ftype)
1632
+ IR::AggregateField.new(name: fname, value:)
1633
+ end
1634
+ IR::AggregateLiteral.new(fields:, type: result_type)
1635
+ end
1636
+
1637
+ def lower_aggregate_binary_operation(operator, left, right, result_type)
1638
+ fields = result_type.fields.map do |fname, ftype|
1639
+ left_field = IR::Member.new(receiver: left, member: fname, type: ftype)
1640
+ right_field = IR::Member.new(receiver: right, member: fname, type: ftype)
1641
+ value = lower_vector_binary_operation(operator, left_field, ftype, right_field, ftype, ftype) || IR::Binary.new(
1642
+ operator:,
1643
+ left: left_field,
1644
+ right: right_field,
1645
+ type: ftype,
1646
+ )
1647
+ IR::AggregateField.new(name: fname, value:)
1648
+ end
1649
+ IR::AggregateLiteral.new(fields:, type: result_type)
1632
1650
  end
1633
- IR::AggregateLiteral.new(fields:, type: result_type)
1634
- end
1635
1651
 
1636
- def lower_aggregate_binary_op(operator, left, right, result_type)
1637
- fields = result_type.fields.map do |fname, ftype|
1638
- left_field = IR::Member.new(receiver: left, member: fname, type: ftype)
1639
- right_field = IR::Member.new(receiver: right, member: fname, type: ftype)
1640
- value = lower_vector_binary_op(operator, left_field, ftype, right_field, ftype, ftype) || IR::Binary.new(
1641
- operator:,
1642
- left: left_field,
1643
- right: right_field,
1644
- type: ftype,
1652
+ def lower_soa_indexed_field_access(soa_base, index_expr, field_name, soa_type, env:, type:)
1653
+ field_type = soa_type.fields[field_name]
1654
+ raise LoweringError.new("SoA type #{soa_type} has no field #{field_name}", line: 0, column: 0, path: @ctx.current_analysis_path) unless field_type
1655
+
1656
+ receiver = lower_expression(soa_base, env:)
1657
+ index = lower_expression(index_expr, env:)
1658
+ IR::Index.new(
1659
+ receiver: IR::Member.new(receiver:, member: field_name, type: field_type),
1660
+ index:,
1661
+ type:,
1645
1662
  )
1646
- IR::AggregateField.new(name: fname, value:)
1647
1663
  end
1648
- IR::AggregateLiteral.new(fields:, type: result_type)
1649
- end
1650
-
1651
- def lower_soa_indexed_field_access(soa_base, index_expr, field_name, soa_type, env:, type:)
1652
- field_type = soa_type.fields[field_name]
1653
- raise LoweringError.new("SoA type #{soa_type} has no field #{field_name}", line: 0, column: 0, path: @ctx.current_analysis_path) unless field_type
1654
-
1655
- receiver = lower_expression(soa_base, env:)
1656
- index = lower_expression(index_expr, env:)
1657
- IR::Index.new(
1658
- receiver: IR::Member.new(receiver:, member: field_name, type: field_type),
1659
- index:,
1660
- type:,
1661
- )
1662
- end
1663
1664
 
1664
- def member_c_name(receiver_type, member)
1665
- owner_type = receiver_type
1666
- loop do
1667
- case owner_type
1668
- when Types::Nullable
1669
- owner_type = owner_type.base
1670
- when Types::GenericInstance
1671
- if %w[ptr const_ptr own ref].include?(owner_type.name) && owner_type.arguments.length == 1
1672
- owner_type = owner_type.arguments.first
1665
+ def member_c_name(receiver_type, member)
1666
+ owner_type = receiver_type
1667
+ loop do
1668
+ case owner_type
1669
+ when Types::Nullable
1670
+ owner_type = owner_type.base
1671
+ when Types::GenericInstance
1672
+ if %w[ptr const_ptr own ref].include?(owner_type.name) && owner_type.arguments.length == 1
1673
+ owner_type = owner_type.arguments.first
1674
+ else
1675
+ break
1676
+ end
1673
1677
  else
1674
1678
  break
1675
1679
  end
1676
- else
1677
- break
1678
1680
  end
1679
- end
1680
1681
 
1681
- if (event_type = event_member_from_owner_type(owner_type, member))
1682
- return event_type.hidden_field_name
1683
- end
1682
+ if (event_type = event_member_from_owner_type(owner_type, member))
1683
+ return event_type.hidden_field_name
1684
+ end
1684
1685
 
1685
- owner_type.field_c_name(member)
1686
- end
1686
+ owner_type.field_c_name(member)
1687
+ end
1687
1688
 
1688
- def resolve_type_ref_with_fallback(type_ref, env:)
1689
- resolve_type_ref(type_ref)
1690
- rescue LoweringError
1691
- return unless type_ref.name.parts.length >= 1
1689
+ def resolve_type_ref_with_fallback(type_ref, env:)
1690
+ resolve_type_ref(type_ref)
1691
+ rescue LoweringError
1692
+ return unless type_ref.name.parts.length >= 1
1692
1693
 
1693
- expression = build_expression_from_qualified_name(type_ref.name)
1694
- return unless expression
1694
+ expression = build_expression_from_qualified_name(type_ref.name)
1695
+ return unless expression
1695
1696
 
1696
- ct_value = compile_time_const_value(expression, env:)
1697
- if ct_value.is_a?(Types::Struct) || ct_value.is_a?(Types::Primitive) ||
1698
- ct_value.is_a?(Types::Union) || ct_value.is_a?(Types::Nullable) ||
1699
- ct_value.is_a?(Types::StructInstance)
1700
- ct_value
1697
+ ct_value = compile_time_const_value(expression, env:)
1698
+ if ct_value.is_a?(Types::Struct) || ct_value.is_a?(Types::Primitive) ||
1699
+ ct_value.is_a?(Types::Union) || ct_value.is_a?(Types::Nullable) ||
1700
+ ct_value.is_a?(Types::StructInstance)
1701
+ ct_value
1702
+ end
1701
1703
  end
1702
- end
1703
1704
 
1704
- def build_expression_from_qualified_name(qualified_name)
1705
- parts = qualified_name.parts
1706
- return unless parts.length >= 1
1705
+ def build_expression_from_qualified_name(qualified_name)
1706
+ parts = qualified_name.parts
1707
+ return unless parts.length >= 1
1707
1708
 
1708
- expr = AST::Identifier.new(name: parts.first)
1709
- parts[1..].each do |part|
1710
- expr = AST::MemberAccess.new(receiver: expr, member: part)
1711
- end
1712
- expr
1713
- end
1714
-
1715
- def prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: false)
1716
- storage_type, success_type, return_type, error_type = infer_result_propagation_types(expression, env:, allow_void_success:)
1717
- is_option = option_let_else_type?(storage_type)
1718
-
1719
- env[:prepared_expression_cleanups] ||= []
1720
- cleanup_start = env[:prepared_expression_cleanups].length
1721
- operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type: storage_type)
1722
- operand_cleanups = env[:prepared_expression_cleanups].drop(cleanup_start)
1723
-
1724
- result_name = fresh_c_temp_name(env, "propagate")
1725
- result_ref = IR::Name.new(name: result_name, type: storage_type, pointer: false)
1726
- return_context = env.fetch(:return_context)
1727
- failure_return = if storage_type == return_type
1728
- result_ref
1729
- elsif is_option
1730
- IR::VariantLiteral.new(
1731
- type: return_type,
1732
- arm_name: "none",
1733
- fields: [],
1734
- )
1735
- else
1736
- IR::VariantLiteral.new(
1737
- type: return_type,
1738
- arm_name: "failure",
1739
- fields: [
1740
- IR::AggregateField.new(
1741
- name: "error",
1742
- value: variant_binding_projection_expression(result_ref, storage_type, "failure", "error", error_type),
1743
- ),
1744
- ],
1745
- )
1746
- end
1747
- failure_cleanup = operand_cleanups.flat_map(&:itself)
1748
- failure_terminator = if return_context[:async_info]
1749
- failure_cleanup +
1750
- lower_async_cleanup_entries(
1751
- return_context[:local_defers],
1752
- return_context[:active_defers],
1753
- frame_expr: return_context.fetch(:frame_expr),
1754
- raw_frame_expr: return_context.fetch(:raw_frame_expr),
1755
- async_info: return_context.fetch(:async_info),
1756
- ) +
1757
- async_complete_statements(
1758
- frame_expr: return_context.fetch(:frame_expr),
1759
- raw_frame_expr: return_context.fetch(:raw_frame_expr),
1760
- async_info: return_context.fetch(:async_info),
1761
- value: failure_return,
1709
+ expr = AST::Identifier.new(name: parts.first)
1710
+ parts[1..].each do |part|
1711
+ expr = AST::MemberAccess.new(receiver: expr, member: part)
1712
+ end
1713
+ expr
1714
+ end
1715
+
1716
+ def prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: false)
1717
+ storage_type, success_type, return_type, error_type = infer_result_propagation_types(expression, env:, allow_void_success:)
1718
+ is_option = option_let_else_type?(storage_type)
1719
+
1720
+ env[:prepared_expression_cleanups] ||= []
1721
+ cleanup_start = env[:prepared_expression_cleanups].length
1722
+ operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type: storage_type)
1723
+ operand_cleanups = env[:prepared_expression_cleanups].drop(cleanup_start)
1724
+
1725
+ result_name = fresh_c_temp_name(env, "propagate")
1726
+ result_ref = IR::Name.new(name: result_name, type: storage_type, pointer: false)
1727
+ return_context = env.fetch(:return_context)
1728
+ failure_return = if storage_type == return_type
1729
+ result_ref
1730
+ elsif is_option
1731
+ IR::VariantLiteral.new(
1732
+ type: return_type,
1733
+ arm_name: "none",
1734
+ fields: [],
1762
1735
  )
1763
- else
1764
- failure_cleanup +
1765
- cleanup_statements(return_context[:local_defers], return_context[:active_defers]) +
1766
- [IR::ReturnStmt.new(value: failure_return, path: @ctx.current_analysis_path)]
1767
- end
1736
+ else
1737
+ IR::VariantLiteral.new(
1738
+ type: return_type,
1739
+ arm_name: "failure",
1740
+ fields: [
1741
+ IR::AggregateField.new(
1742
+ name: "error",
1743
+ value: variant_binding_projection_expression(result_ref, storage_type, "failure", "error", error_type),
1744
+ ),
1745
+ ],
1746
+ )
1747
+ end
1748
+ failure_cleanup = operand_cleanups.flat_map(&:itself)
1749
+ failure_terminator = if return_context[:async_info]
1750
+ failure_cleanup +
1751
+ lower_async_cleanup_entries(
1752
+ return_context[:local_defers],
1753
+ return_context[:active_defers],
1754
+ frame_expr: return_context.fetch(:frame_expr),
1755
+ raw_frame_expr: return_context.fetch(:raw_frame_expr),
1756
+ async_info: return_context.fetch(:async_info),
1757
+ ) +
1758
+ async_complete_statements(
1759
+ frame_expr: return_context.fetch(:frame_expr),
1760
+ raw_frame_expr: return_context.fetch(:raw_frame_expr),
1761
+ async_info: return_context.fetch(:async_info),
1762
+ value: failure_return,
1763
+ )
1764
+ else
1765
+ failure_cleanup +
1766
+ cleanup_statements(return_context[:local_defers], return_context[:active_defers]) +
1767
+ [IR::ReturnStmt.new(value: failure_return, path: @ctx.current_analysis_path)]
1768
+ end
1769
+
1770
+ if success_type == @ctx.types.fetch("void")
1771
+ return [
1772
+ operand_setup + [
1773
+ IR::LocalDecl.new(
1774
+ name: result_name,
1775
+ linkage_name: result_name,
1776
+ type: storage_type,
1777
+ value: lower_contextual_expression(operand, env:, expected_type: storage_type),
1778
+ ),
1779
+ IR::IfStmt.new(
1780
+ condition: let_else_failure_condition(result_ref, storage_type),
1781
+ then_body: failure_terminator,
1782
+ else_body: nil,
1783
+ ),
1784
+ ],
1785
+ nil,
1786
+ ]
1787
+ end
1788
+
1789
+ projection = is_option ? :option_some_value : :result_success_value
1790
+ register_prepared_temp!(env, result_name, success_type, storage_type:, projection:)
1768
1791
 
1769
- if success_type == @ctx.types.fetch("void")
1770
- return [
1792
+ [
1771
1793
  operand_setup + [
1772
1794
  IR::LocalDecl.new(
1773
1795
  name: result_name,
@@ -1781,29 +1803,9 @@ module MilkTea
1781
1803
  else_body: nil,
1782
1804
  ),
1783
1805
  ],
1784
- nil,
1806
+ AST::Identifier.new(name: result_name),
1785
1807
  ]
1786
1808
  end
1787
-
1788
- projection = is_option ? :option_some_value : :result_success_value
1789
- register_prepared_temp!(env, result_name, success_type, storage_type:, projection:)
1790
-
1791
- [
1792
- operand_setup + [
1793
- IR::LocalDecl.new(
1794
- name: result_name,
1795
- linkage_name: result_name,
1796
- type: storage_type,
1797
- value: lower_contextual_expression(operand, env:, expected_type: storage_type),
1798
- ),
1799
- IR::IfStmt.new(
1800
- condition: let_else_failure_condition(result_ref, storage_type),
1801
- then_body: failure_terminator,
1802
- else_body: nil,
1803
- ),
1804
- ],
1805
- AST::Identifier.new(name: result_name),
1806
- ]
1807
1809
  end
1808
1810
  end
1809
1811
  end