mt-lang 0.3.4 → 0.3.5

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.
@@ -39,50 +39,6 @@ module MilkTea
39
39
  callee.is_a?(AST::MemberAccess) ? callee.member : nil
40
40
  end
41
41
 
42
- def async_variant_match_arm_binding(arm, scrutinee_expr, scrutinee_type, env:, frame_expr: nil, local_fields: nil)
43
- arm_env = duplicate_env(env)
44
- binding_decl = nil
45
-
46
- if arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
47
- arm_name = variant_match_arm_name_from_pattern(arm.pattern)
48
- if arm_name && scrutinee_type.has_payload?(arm_name)
49
- fields = scrutinee_type.arm(arm_name)
50
- payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
51
-
52
- field_key = async_match_binding_field_key(arm)
53
- field_info = local_fields&.fetch(field_key, nil)
54
- if field_info && frame_expr
55
- target = async_frame_field_expression(frame_expr, field_info[:field_name], field_info[:storage_type])
56
- binding_c = async_frame_field_c_name(field_info[:field_name])
57
- arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
58
- data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
59
- arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
60
- binding_decl = IR::Assignment.new(target:, operator: "=", value: arm_expr)
61
- else
62
- data_expr = IR::Member.new(receiver: scrutinee_expr, member: "data", type: nil)
63
- arm_expr = IR::Member.new(receiver: data_expr, member: arm_name, type: payload_type)
64
- binding_c = c_local_name(arm.binding_name)
65
- arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: binding_c, mutable: false, pointer: false)
66
- binding_decl = IR::LocalDecl.new(name: arm.binding_name, linkage_name: binding_c, type: payload_type, value: arm_expr)
67
- end
68
- end
69
- end
70
-
71
- [arm_env, binding_decl]
72
- end
73
-
74
- def bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
75
- return unless scrutinee_type.is_a?(Types::Variant)
76
- return unless arm.binding_name && !wildcard_arm_pattern?(arm.pattern)
77
-
78
- arm_name = variant_match_arm_name_from_pattern(arm.pattern)
79
- return unless arm_name && scrutinee_type.has_payload?(arm_name)
80
-
81
- fields = scrutinee_type.arm(arm_name)
82
- payload_type = Types::VariantArmPayload.new(scrutinee_type, arm_name, fields)
83
- arm_env[:scopes].last[arm.binding_name] = local_binding(type: payload_type, linkage_name: c_local_name(arm.binding_name), mutable: true, pointer: false)
84
- end
85
-
86
42
  def array_type?(type)
87
43
  type.is_a?(Types::GenericInstance) && type.name == "array" && type.arguments.length == 2 &&
88
44
  type.arguments[1].is_a?(Types::LiteralTypeArg)
@@ -213,56 +169,6 @@ module MilkTea
213
169
  super
214
170
  end
215
171
 
216
- def iterator_loop_info(type, env:)
217
- iter_name = "__mt_for_iterable__"
218
- iterator_name = "__mt_for_iterator__"
219
- probe_env = duplicate_env(env)
220
- current_actual_scope(probe_env[:scopes])[iter_name] = local_binding(type:, linkage_name: iter_name, mutable: false, pointer: false)
221
-
222
- iter_call = AST::Call.new(
223
- callee: AST::MemberAccess.new(receiver: AST::Identifier.new(name: iter_name), member: "iter"),
224
- arguments: [],
225
- )
226
- iterator_type = infer_expression_type(iter_call, env: probe_env)
227
-
228
- current_actual_scope(probe_env[:scopes])[iterator_name] = local_binding(type: iterator_type, linkage_name: iterator_name, mutable: true, pointer: false)
229
- next_call = AST::Call.new(
230
- callee: AST::MemberAccess.new(receiver: AST::Identifier.new(name: iterator_name), member: "next"),
231
- arguments: [],
232
- )
233
- item_storage_type = infer_expression_type(next_call, env: probe_env)
234
- if item_storage_type.is_a?(Types::Nullable) && nullable_iterator_item_type?(item_storage_type.base)
235
- return {
236
- kind: :nullable_item,
237
- iterator_type:,
238
- item_storage_type:,
239
- item_type: item_storage_type.base,
240
- }
241
- end
242
-
243
- if item_storage_type == @ctx.types.fetch("bool")
244
- current_call = AST::Call.new(
245
- callee: AST::MemberAccess.new(receiver: AST::Identifier.new(name: iterator_name), member: "current"),
246
- arguments: [],
247
- )
248
- current_type = infer_expression_type(current_call, env: probe_env)
249
- return {
250
- kind: :current_item,
251
- iterator_type:,
252
- item_storage_type: current_type,
253
- item_type: current_type,
254
- }
255
- end
256
-
257
- nil
258
- rescue LoweringError
259
- nil
260
- end
261
-
262
- def nullable_iterator_item_type?(type)
263
- type == @ctx.types.fetch("cstr") || pointer_type?(type)
264
- end
265
-
266
172
  def collection_loop_item_value(iterable_ref, iterable_type, index_ref, element_type)
267
173
  if array_type?(iterable_type)
268
174
  IR::Index.new(receiver: iterable_ref, index: index_ref, type: element_type)
@@ -841,18 +747,6 @@ module MilkTea
841
747
  end
842
748
  end
843
749
 
844
- def lower_async_loop_exit(target, local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
845
- cleanup = lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
846
- if cleanup.empty?
847
- [loop_exit_statement(target, local_defers:, outer_defers:)]
848
- else
849
- label = target[:label]
850
- raise LoweringError, "structured loop exits with cleanup are unsupported" unless label
851
-
852
- cleanup + [IR::GotoStmt.new(label:)]
853
- end
854
- end
855
-
856
750
  def contains_label_target?(statements, label)
857
751
  statements.any? do |statement|
858
752
  case statement
@@ -928,28 +822,6 @@ module MilkTea
928
822
  !let_else_discard_binding_syntax?(statement)
929
823
  end
930
824
 
931
- def async_local_decl_field_key(statement)
932
- return "__discard_#{statement.line}" if statement.name == "_"
933
-
934
- statement.name
935
- end
936
-
937
- def async_local_decl_field_name(statement)
938
- return "local_discard_#{statement.line}" if statement.name == "_"
939
-
940
- "local_#{statement.name}"
941
- end
942
-
943
- def async_match_binding_field_key(arm)
944
- @async_binding_counter ||= 0
945
- @async_binding_counter += 1
946
- "match_binding_#{@async_binding_counter}"
947
- end
948
-
949
- def async_match_binding_field_name(arm)
950
- "local_match_binding_#{@async_binding_counter}"
951
- end
952
-
953
825
  def let_else_storage_c_name(statement, env)
954
826
  return fresh_c_temp_name(env, "let_else_discard") if let_else_discard_binding_syntax?(statement)
955
827
 
@@ -1133,100 +1005,6 @@ module MilkTea
1133
1005
  [storage_type, success_type, return_type, nil]
1134
1006
  end
1135
1007
 
1136
- def prepare_result_propagation_for_inline_lowering(expression, env:, allow_void_success: false)
1137
- storage_type, success_type, return_type, error_type = infer_result_propagation_types(expression, env:, allow_void_success:)
1138
- is_option = option_let_else_type?(storage_type)
1139
-
1140
- env[:prepared_expression_cleanups] ||= []
1141
- cleanup_start = env[:prepared_expression_cleanups].length
1142
- operand_setup, operand = prepare_expression_for_inline_lowering(expression.operand, env:, expected_type: storage_type)
1143
- operand_cleanups = env[:prepared_expression_cleanups].drop(cleanup_start)
1144
-
1145
- result_name = fresh_c_temp_name(env, "propagate")
1146
- result_ref = IR::Name.new(name: result_name, type: storage_type, pointer: false)
1147
- return_context = env.fetch(:return_context)
1148
- failure_return = if storage_type == return_type
1149
- result_ref
1150
- elsif is_option
1151
- IR::VariantLiteral.new(
1152
- type: return_type,
1153
- arm_name: "none",
1154
- fields: [],
1155
- )
1156
- else
1157
- IR::VariantLiteral.new(
1158
- type: return_type,
1159
- arm_name: "failure",
1160
- fields: [
1161
- IR::AggregateField.new(
1162
- name: "error",
1163
- value: variant_binding_projection_expression(result_ref, storage_type, "failure", "error", error_type),
1164
- ),
1165
- ],
1166
- )
1167
- end
1168
- failure_cleanup = operand_cleanups.flat_map(&:itself)
1169
- failure_terminator = if return_context[:async_info]
1170
- failure_cleanup +
1171
- lower_async_cleanup_entries(
1172
- return_context[:local_defers],
1173
- return_context[:active_defers],
1174
- frame_expr: return_context.fetch(:frame_expr),
1175
- raw_frame_expr: return_context.fetch(:raw_frame_expr),
1176
- async_info: return_context.fetch(:async_info),
1177
- ) +
1178
- async_complete_statements(
1179
- frame_expr: return_context.fetch(:frame_expr),
1180
- raw_frame_expr: return_context.fetch(:raw_frame_expr),
1181
- async_info: return_context.fetch(:async_info),
1182
- value: failure_return,
1183
- )
1184
- else
1185
- failure_cleanup +
1186
- cleanup_statements(return_context[:local_defers], return_context[:active_defers]) +
1187
- [IR::ReturnStmt.new(value: failure_return, source_path: @ctx.current_analysis_path)]
1188
- end
1189
-
1190
- if success_type == @ctx.types.fetch("void")
1191
- return [
1192
- operand_setup + [
1193
- IR::LocalDecl.new(
1194
- name: result_name,
1195
- linkage_name: result_name,
1196
- type: storage_type,
1197
- value: lower_contextual_expression(operand, env:, expected_type: storage_type),
1198
- ),
1199
- IR::IfStmt.new(
1200
- condition: let_else_failure_condition(result_ref, storage_type),
1201
- then_body: failure_terminator,
1202
- else_body: nil,
1203
- ),
1204
- ],
1205
- nil,
1206
- ]
1207
- end
1208
-
1209
- projection = is_option ? :option_some_value : :result_success_value
1210
- register_prepared_temp!(env, result_name, success_type, storage_type:, projection:)
1211
-
1212
- [
1213
- operand_setup + [
1214
- IR::LocalDecl.new(
1215
- name: result_name,
1216
- linkage_name: result_name,
1217
- type: storage_type,
1218
- value: lower_contextual_expression(operand, env:, expected_type: storage_type),
1219
- ),
1220
- IR::IfStmt.new(
1221
- condition: let_else_failure_condition(result_ref, storage_type),
1222
- then_body: failure_terminator,
1223
- else_body: nil,
1224
- ),
1225
- ],
1226
- AST::Identifier.new(name: result_name),
1227
- ]
1228
- end
1229
-
1230
1008
  def c_type_name(type)
1231
1009
  if type.is_a?(Types::Nullable)
1232
1010
  return "nullable_#{c_type_name(type.base)}"
@@ -334,55 +334,40 @@ module MilkTea
334
334
  private_class_method :raise_platform_conflict!
335
335
 
336
336
  def check_path(path)
337
- resolved_path = self.class.resolve_source_path(path, platform: @platform, error_class: ModuleLoadError)
338
- shared_cache_mtime = nil
339
- shared_cache_mtime_checked = false
340
-
341
- return @analysis_cache[resolved_path] if @analysis_cache.key?(resolved_path)
342
-
343
- if use_shared_cache?
344
- entry = @shared_cache[resolved_path]
345
- if entry
346
- shared_cache_mtime_checked = true
347
- shared_cache_mtime = File.mtime(resolved_path).to_f rescue nil
348
- if shared_cache_mtime && entry[:mtime] == shared_cache_mtime
349
- @analysis_cache[resolved_path] = entry[:analysis]
350
- return entry[:analysis]
351
- end
352
- end
353
- end
337
+ resolved_path, ast, cached = check_module_cache(path)
338
+ return cached if cached
354
339
 
355
- if @checking_paths.include?(resolved_path)
356
- raise ModuleLoadError.new("cyclic import detected", path: resolved_path)
357
- end
358
-
359
- @checking_paths << resolved_path
360
- ast = load_file(resolved_path)
361
340
  imported_modules = imported_modules_for_ast(ast, importer_path: resolved_path)
362
-
363
341
  global_index = build_global_import_index(ast)
364
342
  analysis = SemanticAnalyzer.check(ast, imported_modules:, path: resolved_path, global_import_index: global_index)
365
343
  @analysis_cache[resolved_path] = analysis
344
+ update_shared_cache(resolved_path, analysis)
345
+ analysis
346
+ ensure
347
+ @checking_paths.pop if @checking_paths.last == resolved_path
348
+ end
366
349
 
367
- if use_shared_cache?
368
- mtime = if shared_cache_mtime_checked
369
- shared_cache_mtime
370
- else
371
- File.mtime(resolved_path).to_f rescue nil
372
- end
373
- @shared_cache[resolved_path] = { mtime: mtime, analysis: analysis } if mtime
374
- end
350
+ def check_path_collecting_errors(path)
351
+ resolved_path, ast, cached = check_module_cache(path, extra_cache: @collecting_analysis_cache)
352
+ return cached if cached
353
+
354
+ imported_modules = imported_modules_for_ast_collecting_errors(ast, importer_path: resolved_path).modules
355
+ result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules:, path: resolved_path)
356
+ analysis = result[:analysis]
357
+ raise(result[:errors].first || ModuleLoadError.new("module analysis unavailable", path: resolved_path)) unless analysis
375
358
 
359
+ @collecting_path_errors[resolved_path] = result[:errors]
360
+ @collecting_analysis_cache[resolved_path] = analysis
376
361
  analysis
377
362
  ensure
378
363
  @checking_paths.pop if @checking_paths.last == resolved_path
379
364
  end
380
365
 
381
- def check_path_collecting_errors(path)
366
+ def check_module_cache(path, extra_cache: nil)
382
367
  resolved_path = self.class.resolve_source_path(path, platform: @platform, error_class: ModuleLoadError)
383
368
 
384
- return @analysis_cache[resolved_path] if @analysis_cache.key?(resolved_path)
385
- return @collecting_analysis_cache[resolved_path] if @collecting_analysis_cache.key?(resolved_path)
369
+ return [resolved_path, nil, @analysis_cache[resolved_path]] if @analysis_cache.key?(resolved_path)
370
+ return [resolved_path, nil, extra_cache[resolved_path]] if extra_cache&.key?(resolved_path)
386
371
 
387
372
  if use_shared_cache?
388
373
  entry = @shared_cache[resolved_path]
@@ -390,27 +375,23 @@ module MilkTea
390
375
  mtime = File.mtime(resolved_path).to_f rescue nil
391
376
  if mtime && entry[:mtime] == mtime
392
377
  @analysis_cache[resolved_path] = entry[:analysis]
393
- return entry[:analysis]
378
+ return [resolved_path, nil, entry[:analysis]]
394
379
  end
395
380
  end
396
381
  end
397
382
 
398
- if @checking_paths.include?(resolved_path)
399
- raise ModuleLoadError.new("cyclic import detected", path: resolved_path)
400
- end
383
+ raise ModuleLoadError.new("cyclic import detected", path: resolved_path) if @checking_paths.include?(resolved_path)
401
384
 
402
385
  @checking_paths << resolved_path
403
386
  ast = load_file(resolved_path)
404
- imported_modules = imported_modules_for_ast_collecting_errors(ast, importer_path: resolved_path).modules
405
- result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules:, path: resolved_path)
406
- analysis = result[:analysis]
407
- raise(result[:errors].first || ModuleLoadError.new("module analysis unavailable", path: resolved_path)) unless analysis
387
+ [resolved_path, ast, nil]
388
+ end
408
389
 
409
- @collecting_path_errors[resolved_path] = result[:errors]
410
- @collecting_analysis_cache[resolved_path] = analysis
411
- analysis
412
- ensure
413
- @checking_paths.pop if @checking_paths.last == resolved_path
390
+ def update_shared_cache(resolved_path, analysis)
391
+ return unless use_shared_cache?
392
+
393
+ mtime = File.mtime(resolved_path).to_f rescue nil
394
+ @shared_cache[resolved_path] = { mtime:, analysis: } if mtime
414
395
  end
415
396
 
416
397
  def parse_file(path)
@@ -24,7 +24,7 @@ module MilkTea
24
24
  attributes = parse_attribute_applications
25
25
  visibility, visibility_token = parse_visibility
26
26
 
27
- if legacy_layout_modifier_start?(peek)
27
+ if builtin_attribute_identifier?(peek)
28
28
  raise error(peek, "layout modifiers must use attributes like @[packed] or @[align(...)]")
29
29
  elsif match(:attribute)
30
30
  reject_attributes!(attributes, "attribute")
@@ -185,7 +185,7 @@ module MilkTea
185
185
  if match(:public)
186
186
  reject_attributes!(attributes)
187
187
  raise error(previous, "public is not allowed in external files")
188
- elsif legacy_layout_modifier_start?(peek)
188
+ elsif builtin_attribute_identifier?(peek)
189
189
  raise error(peek, "layout modifiers must use attributes like @[packed] or @[align(...)]")
190
190
  elsif match(:attribute)
191
191
  reject_attributes!(attributes)
@@ -124,7 +124,7 @@ module MilkTea
124
124
  end
125
125
 
126
126
  def top_level_recovery_start?(token)
127
- token.column.to_i <= 1 && (TOP_LEVEL_RECOVERY_START_TYPES.include?(token.type) || legacy_layout_modifier_start?(token))
127
+ token.column.to_i <= 1 && (TOP_LEVEL_RECOVERY_START_TYPES.include?(token.type) || builtin_attribute_identifier?(token))
128
128
  end
129
129
  end
130
130
  end
@@ -290,7 +290,7 @@ module MilkTea
290
290
  @tokens[@current + 1]&.type == :identifier
291
291
  end
292
292
 
293
- def legacy_layout_modifier_start?(token)
293
+ def builtin_attribute_identifier?(token)
294
294
  token&.type == :identifier && BUILTIN_ATTRIBUTE_NAME_LEXEMES.include?(token.lexeme)
295
295
  end
296
296
 
@@ -783,143 +783,26 @@ module MilkTea
783
783
  end
784
784
 
785
785
  def infer_integer_match_expression(expression, scrutinee_type, scopes:, expected_type:)
786
- has_wildcard = expression.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
787
- raise_sema_error("match on integer type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard
788
-
789
- covered_values = {}
790
- wildcard_seen = false
791
786
  arm_entries = []
792
-
793
- expression.arms.each do |arm|
794
- if arm.pattern.is_a?(AST::ErrorExpr)
795
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
796
- next
797
- end
798
-
799
- if wildcard_pattern?(arm.pattern)
800
- raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
801
-
802
- wildcard_seen = true
803
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
804
- next
805
- end
806
-
807
- if arm.pattern.is_a?(AST::RangeExpr)
808
- start_val = arm.pattern.start_expr
809
- end_val = arm.pattern.end_expr
810
- unless (start_val.is_a?(AST::IntegerLiteral) || start_val.is_a?(AST::CharLiteral)) &&
811
- (end_val.is_a?(AST::IntegerLiteral) || end_val.is_a?(AST::CharLiteral))
812
- raise_sema_error("range match arm bounds must be integer or char literals, got #{start_val.class.name}..#{end_val.class.name}")
813
- end
814
-
815
- raise_sema_error("range match arm start #{start_val.value} must be <= end #{end_val.value}") if start_val.value > end_val.value
816
-
817
- range_key = [start_val.value, end_val.value]
818
- raise_sema_error("duplicate match arm range #{range_key[0]}..#{range_key[1]}") if covered_values.key?(range_key)
819
-
820
- covered_values[range_key] = true
821
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
822
- next
823
- end
824
-
825
- unless arm.pattern.is_a?(AST::IntegerLiteral) || arm.pattern.is_a?(AST::CharLiteral)
826
- raise_sema_error("match arm for integer scrutinee must be an integer literal, char literal, range, or _, got #{arm.pattern.class.name}")
827
- end
828
-
829
- value = arm.pattern.value
830
- raise_sema_error("duplicate match arm value #{value}") if covered_values.key?(value)
831
-
832
- covered_values[value] = true
787
+ each_integer_match_arm(expression, scrutinee_type, scopes:) do |arm|
833
788
  arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
834
789
  end
835
-
836
790
  match_expression_common_type(arm_entries, expected_type)
837
791
  end
838
792
 
839
793
  def infer_string_match_expression(expression, scrutinee_type, scopes:, expected_type:)
840
- has_wildcard = expression.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
841
- raise_sema_error("match on str requires a wildcard arm (_:)") unless has_wildcard
842
-
843
- covered_values = {}
844
- wildcard_seen = false
845
794
  arm_entries = []
846
-
847
- expression.arms.each do |arm|
848
- if arm.pattern.is_a?(AST::ErrorExpr)
849
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
850
- next
851
- end
852
-
853
- if wildcard_pattern?(arm.pattern)
854
- raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
855
- wildcard_seen = true
856
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
857
- next
858
- end
859
-
860
- unless arm.pattern.is_a?(AST::StringLiteral)
861
- raise_sema_error("match arm for str scrutinee must be a string literal or _, got #{arm.pattern.class.name}")
862
- end
863
-
864
- value = arm.pattern.value
865
- raise_sema_error("duplicate match arm value \"#{value}\"") if covered_values.key?(value)
866
-
867
- covered_values[value] = true
795
+ each_string_match_arm(expression, scrutinee_type, scopes:) do |arm|
868
796
  arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
869
797
  end
870
-
871
798
  match_expression_common_type(arm_entries, expected_type)
872
799
  end
873
800
 
874
801
  def infer_tuple_match_expression(expression, scrutinee_type, scopes:, expected_type:)
875
- has_wildcard = expression.arms.any? { |arm| wildcard_pattern?(arm.pattern) }
876
- raise_sema_error("match on tuple type #{scrutinee_type} requires a wildcard arm (_:)") unless has_wildcard
877
-
878
- arity = scrutinee_type.element_types.length
879
- covered_values = {}
880
- wildcard_seen = false
881
802
  arm_entries = []
882
-
883
- expression.arms.each do |arm|
884
- if arm.pattern.is_a?(AST::ErrorExpr)
885
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
886
- next
887
- end
888
-
889
- if wildcard_pattern?(arm.pattern)
890
- raise_sema_error("duplicate wildcard arm in match") if wildcard_seen
891
- wildcard_seen = true
892
- arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
893
- next
894
- end
895
-
896
- unless arm.pattern.is_a?(AST::ExpressionList)
897
- raise_sema_error("match arm for tuple scrutinee must be a tuple literal or _, got #{arm.pattern.class.name}")
898
- end
899
-
900
- elements = arm.pattern.elements
901
- raise_sema_error("tuple match arm has #{elements.length} elements but scrutinee has #{arity}") unless elements.length == arity
902
-
903
- values = elements.map do |elem|
904
- if elem.is_a?(AST::Identifier) && elem.name == "_"
905
- :wildcard
906
- elsif elem.is_a?(AST::IntegerLiteral) || elem.is_a?(AST::CharLiteral) ||
907
- elem.is_a?(AST::StringLiteral) || elem.is_a?(AST::BooleanLiteral)
908
- elem.value
909
- else
910
- raise_sema_error("tuple match arm element must be a literal or _, got #{elem.class.name}")
911
- end
912
- end
913
-
914
- all_literal = values.all? { |v| v != :wildcard }
915
- if all_literal
916
- raise_sema_error("duplicate tuple match arm #{arm.pattern.elements.map { |e| e.respond_to?(:value) ? e.value.inspect : '_' }.join(', ')}") if covered_values.key?(values)
917
- covered_values[values] = true
918
- end
919
-
803
+ each_tuple_match_arm(expression, scrutinee_type, scopes:) do |arm|
920
804
  arm_entries << [infer_match_expression_arm_value(arm, scopes:, expected_type:), arm.value]
921
805
  end
922
-
923
806
  match_expression_common_type(arm_entries, expected_type)
924
807
  end
925
808