mt-lang 0.3.28 → 0.3.31

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.
@@ -146,6 +146,10 @@ module MilkTea
146
146
  lines.concat(emit_str_equality_helper)
147
147
  lines << ""
148
148
  end
149
+ if uses_str_concat_helper?
150
+ lines.concat(emit_str_concat_helper)
151
+ lines << ""
152
+ end
149
153
  if uses_str_buffer_helpers?
150
154
  lines.concat(emit_utf8_validation_helpers)
151
155
  lines << ""
@@ -232,17 +236,45 @@ module MilkTea
232
236
  lines << ""
233
237
  end
234
238
 
239
+ if uses_variant_equality_helper? || uses_struct_equality_helper?
240
+ lines.concat(emit_aggregate_equality_forward_declarations)
241
+ lines << ""
242
+ end
235
243
  if uses_variant_equality_helper?
236
244
  unless uses_str_equality_helper?
237
- variant_needs_str_eq = emitted_aggregate_variants.any? { |v| v.arms.any? { |a| a.fields.any? { |f| f.type.is_a?(Types::StringView) } } }
238
- if variant_needs_str_eq
239
- lines.concat(emit_string_type) unless uses_string_view?
240
- lines.concat(emit_str_equality_helper)
241
- end
245
+ emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
242
246
  end
243
247
  lines.concat(emit_variant_equality_helpers)
244
248
  lines << ""
245
249
  end
250
+ if uses_struct_equality_helper?
251
+ unless uses_str_equality_helper?
252
+ emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
253
+ end
254
+ lines.concat(emit_struct_equality_helpers)
255
+ lines << ""
256
+ end
257
+ end
258
+
259
+ def emit_aggregate_equality_forward_declarations
260
+ lines = []
261
+ struct_equality_types.each do |type|
262
+ c = named_type_c_name(type)
263
+ lines << "static bool mt_struct_eq_#{c}(struct #{c} left, struct #{c} right);"
264
+ end
265
+ variant_equality_types.each do |type|
266
+ c = named_type_c_name(type)
267
+ lines << "static bool mt_variant_eq_#{c}(struct #{c} left, struct #{c} right);"
268
+ end
269
+ lines
270
+ end
271
+
272
+ def emit_str_equality_support(lines)
273
+ return if @emitted_aggregate_str_equality_support
274
+
275
+ @emitted_aggregate_str_equality_support = true
276
+ lines.concat(emit_string_type) unless uses_string_view?
277
+ lines.concat(emit_str_equality_helper)
246
278
  end
247
279
 
248
280
  def emit_function_forward_declarations(lines)
@@ -31,10 +31,35 @@ module MilkTea
31
31
  return left == right if left.is_a?(String) && right.is_a?(String)
32
32
  return left == right if boolean_value?(left) && boolean_value?(right)
33
33
  return left == right if left.is_a?(Types::Base) && right.is_a?(Types::Base)
34
+ return struct_equality_result(left, right) if left.is_a?(Hash) && right.is_a?(Hash)
35
+ return variant_equality_result(left, right) if left.is_a?(VariantValue) && right.is_a?(VariantValue)
34
36
 
35
37
  nil
36
38
  end
37
39
 
40
+ # Const-time struct values are represented as {field_name => value} hashes;
41
+ # compare them field by field, mirroring the runtime struct == semantics.
42
+ def self.struct_equality_result(left, right)
43
+ return nil unless left.is_a?(Hash) && right.is_a?(Hash)
44
+ return nil unless left.keys.sort == right.keys.sort
45
+
46
+ left.each do |name, value|
47
+ field_result = equality_result(value, right[name])
48
+ return nil if field_result.nil?
49
+ return false if field_result == false
50
+ end
51
+ true
52
+ end
53
+
54
+ # Const-time variant values; fields mirror the runtime arm payload layout.
55
+ VariantValue = Data.define(:arm, :fields)
56
+
57
+ def self.variant_equality_result(left, right)
58
+ return false unless left.arm == right.arm
59
+
60
+ struct_equality_result(left.fields, right.fields)
61
+ end
62
+
38
63
  def self.boolean_value?(value)
39
64
  value == true || value == false
40
65
  end
@@ -78,6 +78,14 @@ module MilkTea
78
78
  IR::AggregateField.new(name:, value: lower_const_value_literal(field_type, field_value))
79
79
  end
80
80
  IR::AggregateLiteral.new(type:, fields:)
81
+ when CompileTime::VariantValue
82
+ arm_field_types = type.respond_to?(:arm) ? (type.arm(const_value.arm) || {}) : {}
83
+ fields = const_value.fields.map do |field_name, field_value|
84
+ field_type = arm_field_types[field_name]
85
+ raise LoweringError.new("constant variant arm #{const_value.arm} field #{field_name} not found in #{type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless field_type
86
+ IR::AggregateField.new(name: field_name, value: lower_const_value_literal(field_type, field_value))
87
+ end
88
+ IR::VariantLiteral.new(type:, arm_name: const_value.arm, fields:)
81
89
  else
82
90
  raise LoweringError.new("unsupported const value type #{const_value.class}", line: 0, column: 0, path: @ctx.current_analysis_path)
83
91
  end
@@ -1387,6 +1387,10 @@ module MilkTea
1387
1387
  left = cast_expression(left, operand_type) if operand_type
1388
1388
  right = cast_expression(right, operand_type) if operand_type
1389
1389
 
1390
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
1391
+ return IR::Call.new(callee: "mt_str_concat", arguments: [left, right], type:)
1392
+ end
1393
+
1390
1394
  expanded = lower_vector_binary_operation(expression.operator, left, left_type, right, right_type, type)
1391
1395
  return expanded if expanded
1392
1396
 
@@ -270,10 +270,19 @@ module MilkTea
270
270
 
271
271
  if expected_type.is_a?(Types::Primitive) && expected_type.integer? &&
272
272
  value_fits_integer_type?(expression.value, expected_type)
273
- expected_type
274
- else
275
- @ctx.types.fetch("int")
273
+ return expected_type
276
274
  end
275
+
276
+ # A bare literal (no suffix, no fitting expected type) picks the
277
+ # narrowest fixed-width type that holds its value: int -> long -> ulong.
278
+ # Falling through to int unconditionally emitted overflowing C
279
+ # (e.g. `int32_t x = 2147483648;`).
280
+ ["int", "long", "ulong"].each do |name|
281
+ candidate = @ctx.types.fetch(name)
282
+ return candidate if value_fits_integer_type?(expression.value, candidate)
283
+ end
284
+
285
+ raise_sema_error("integer literal #{expression.value} does not fit in any integer type", expression)
277
286
  end
278
287
 
279
288
  INTEGER_SUFFIX_TYPES = {
@@ -300,11 +309,23 @@ module MilkTea
300
309
  @ctx.types.fetch("double")
301
310
  elsif expected_type.is_a?(Types::Primitive) && expected_type.float?
302
311
  expected_type
303
- else
312
+ elsif float_literal_fits_in_float?(expression.value)
304
313
  @ctx.types.fetch("float")
314
+ else
315
+ # A bare float literal beyond float32 range would silently overflow
316
+ # to infinity in C (`float x = 1e40`); promote to double instead.
317
+ @ctx.types.fetch("double")
305
318
  end
306
319
  end
307
320
 
321
+ FLOAT32_MAX_MAGNITUDE = 3.4028234663852886e+38
322
+
323
+ def float_literal_fits_in_float?(value)
324
+ return false unless value.is_a?(Numeric) && value.finite?
325
+
326
+ value.abs <= FLOAT32_MAX_MAGNITUDE
327
+ end
328
+
308
329
  def infer_identifier(expression, scopes:, expected_type: nil)
309
330
  binding = lookup_value(expression.name, scopes)
310
331
  if binding
@@ -612,8 +633,8 @@ module MilkTea
612
633
 
613
634
  left_type
614
635
  when "+", "-", "*", "/"
615
- if expression.operator == "+" && (string_like_type?(left_type) || string_like_type?(right_type))
616
- raise_sema_error("operator + does not support str/cstr concatenation; use continued string literals for static text or string.String/str_buffer for dynamic text")
636
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
637
+ return left_type
617
638
  end
618
639
 
619
640
  pointer_result = pointer_arithmetic_result(expression.operator, left_type, right_type)
@@ -662,8 +683,16 @@ module MilkTea
662
683
  when "==", "!="
663
684
  unless c_natively_equality_comparable_type?(left_type) && c_natively_equality_comparable_type?(right_type)
664
685
  bad_type = c_natively_equality_comparable_type?(right_type) ? left_type : right_type
665
- if struct_instance_type?(bad_type)
666
- raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}; use equal[#{bad_type}](...) instead")
686
+ if bad_type.is_a?(Types::Union)
687
+ raise_sema_error("operator #{expression.operator} is not supported for union type #{bad_type}; use equal[#{bad_type}](...) instead")
688
+ elsif bad_type.is_a?(Types::Variant)
689
+ field = first_non_equality_comparable_variant_field(bad_type)
690
+ field_hint = field ? " (arm '#{field[0]}' field '#{field[1]}' of type #{field[2]} is not equality-comparable)" : ""
691
+ raise_sema_error("operator #{expression.operator} is not supported for variant type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
692
+ elsif struct_instance_type?(bad_type) && !bad_type.is_a?(Types::Variant)
693
+ field = first_non_equality_comparable_field(bad_type)
694
+ field_hint = field ? " (field '#{field.first}' of type #{field.last} is not equality-comparable)" : ""
695
+ raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
667
696
  else
668
697
  raise_sema_error("operator #{expression.operator} is not supported for type #{bad_type}")
669
698
  end
@@ -902,23 +902,65 @@ module MilkTea
902
902
  type.is_a?(Types::Struct) || type.is_a?(Types::Variant)
903
903
  end
904
904
 
905
- def c_natively_equality_comparable_type?(type)
905
+ def c_natively_equality_comparable_type?(type, visiting = nil)
906
906
  return true if type.is_a?(Types::Primitive)
907
907
  return true if type.is_a?(Types::EnumBase)
908
908
  return true if type.is_a?(Types::Opaque)
909
- return true if type.is_a?(Types::Nullable)
909
+ return true if type.is_a?(Types::Nullable) && equality_comparable_field_type?(type.base, visiting)
910
910
  return true if type.is_a?(Types::Null)
911
911
  return true if type.is_a?(Types::Function)
912
912
  return true if type.is_a?(Types::Error)
913
913
  return true if type.is_a?(Types::StringView)
914
914
  return true if pointer_type?(type)
915
915
  return true if ref_type?(type)
916
- return true if type.is_a?(Types::Variant)
916
+ return true if type.is_a?(Types::Variant) && equality_comparable_variant_type?(type, visiting)
917
917
  return true if type.is_a?(Types::VariantArmPayload)
918
+ return true if equality_comparable_struct_type?(type, visiting)
919
+
920
+ false
921
+ end
922
+
923
+ # A struct value is equality-comparable with ==/!= when every field is
924
+ # itself equality-comparable. Untagged unions are excluded because their
925
+ # active-field comparison is ambiguous.
926
+ def equality_comparable_struct_type?(type, visiting = nil)
927
+ type.is_a?(Types::Struct) && !type.is_a?(Types::Union) &&
928
+ type.fields.all? { |_name, field_type| equality_comparable_field_type?(field_type, visiting) }
929
+ end
930
+
931
+ def equality_comparable_field_type?(type, visiting = nil)
932
+ return true if c_natively_equality_comparable_type?(type, visiting)
933
+ return true if array_type?(type) && equality_comparable_field_type?(array_element_type(type), visiting)
918
934
 
919
935
  false
920
936
  end
921
937
 
938
+ # A variant is equality-comparable when every arm payload field is itself
939
+ # equality-comparable. A variant that reaches itself through value fields
940
+ # (a recursive/cyclic variant) is comparable too: the C backend embeds
941
+ # cyclic fields as pointers, and those fields compare by pointer identity.
942
+ def equality_comparable_variant_type?(type, visiting = nil)
943
+ return true if visiting&.key?(type)
944
+
945
+ visiting = (visiting || {}).merge(type => true)
946
+ type.arm_names.all? do |arm_name|
947
+ (type.arm(arm_name) || {}).all? { |_field_name, field_type| equality_comparable_field_type?(field_type, visiting) }
948
+ end
949
+ end
950
+
951
+ def first_non_equality_comparable_field(struct_type)
952
+ struct_type.fields.find { |_name, field_type| !equality_comparable_field_type?(field_type) }
953
+ end
954
+
955
+ def first_non_equality_comparable_variant_field(variant_type)
956
+ variant_type.arm_names.each do |arm_name|
957
+ (variant_type.arm(arm_name) || {}).each do |field_name, field_type|
958
+ return [arm_name, field_name, field_type] unless equality_comparable_field_type?(field_type)
959
+ end
960
+ end
961
+ nil
962
+ end
963
+
922
964
  def collection_loop_type(type)
923
965
  super
924
966
  end
@@ -301,7 +301,12 @@ module MilkTea
301
301
  end
302
302
 
303
303
  if (receiver_type = resolve_type_expression(member_access_expression.receiver))
304
- next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
304
+ if receiver_type.is_a?(Types::EnumBase)
305
+ next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
306
+ end
307
+ if receiver_type.is_a?(Types::Variant)
308
+ next CompileTime::VariantValue.new(arm: member_access_expression.member, fields: {})
309
+ end
305
310
  end
306
311
 
307
312
  next unless member_access_expression.receiver.is_a?(AST::Identifier)
@@ -321,6 +326,31 @@ module MilkTea
321
326
 
322
327
  def evaluate_compile_time_call(expression, scopes: nil)
323
328
  case expression.callee
329
+ when AST::MemberAccess
330
+ if (receiver_type = resolve_type_expression(expression.callee.receiver)) && receiver_type.is_a?(Types::Variant)
331
+ arm_name = expression.callee.member
332
+ fields = {}
333
+ expression.arguments.each do |argument|
334
+ val = CompileTime.evaluate(argument.value, resolve_identifier: lambda { |id|
335
+ if scopes
336
+ binding = lookup_value(id.name, scopes)
337
+ return binding.const_value unless binding&.const_value.nil?
338
+ end
339
+ resolve_current_module_const_value(id.name)
340
+ }, resolve_member_access: lambda { |ma|
341
+ if (member_receiver_type = resolve_type_expression(ma.receiver))
342
+ next resolve_enum_member_const_value(member_receiver_type, ma.member) if member_receiver_type.is_a?(Types::EnumBase)
343
+ next CompileTime::VariantValue.new(arm: ma.member, fields: {}) if member_receiver_type.is_a?(Types::Variant)
344
+ end
345
+ nil
346
+ }, resolve_call: lambda { |inner_call|
347
+ evaluate_compile_time_call(inner_call, scopes:)
348
+ })
349
+ return nil unless val
350
+ fields[argument.name] = val
351
+ end
352
+ return CompileTime::VariantValue.new(arm: arm_name, fields: fields)
353
+ end
324
354
  when AST::Identifier
325
355
  if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
326
356
  fields = {}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.28
4
+ version: 0.3.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -149,7 +149,6 @@ files:
149
149
  - docs/index.html
150
150
  - docs/language-design.md
151
151
  - docs/language-manual.md
152
- - docs/self-hosted-compiler-plan.md
153
152
  - lib/milk_tea.rb
154
153
  - lib/milk_tea/base.rb
155
154
  - lib/milk_tea/bindings.rb
@@ -625,7 +624,7 @@ metadata:
625
624
  homepage_uri: https://teefan.github.io/mt-lang/
626
625
  source_code_uri: https://github.com/teefan/mt-lang
627
626
  post_install_message: |
628
- Milk Tea 0.3.28 installed!
627
+ Milk Tea 0.3.31 installed!
629
628
 
630
629
  System requirements:
631
630
  - A C compiler (gcc or clang) must be available on PATH