mt-lang 0.2.19 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '076938689f867b9d3d7f60eab7f4654c03a45ea69b429f1af17c86a3afef51a5'
4
- data.tar.gz: 6f01bf9d070e1307666949acf47d99170d6b28c0048c868a6310663f2d55c919
3
+ metadata.gz: e599b2192dada4fb02cbcefc522482028adce79482a53965f2715895ced1c744
4
+ data.tar.gz: 2a4b8540ee7050b98707aee9cdd15b6f18504c8f401f9590c03720a49d88de78
5
5
  SHA512:
6
- metadata.gz: 8141047ac65832b64b8eab1d565b5c3eb3bf8841b5541624605179a1e4bc0479f4dab16a92b3b7055e2eb844e37781aac7acab2aa812b348e53d9a151a16fe97
7
- data.tar.gz: e5ead28d45b33a6c7fdb5c46f84cdd9a887e6bca593b841687a1d1124ba904f02ee09bdb1ef8dab87f2b8dcfcd6480190e94cea57e8b3b4853148d06d199f6e6
6
+ metadata.gz: a308513fd5d5aac4ab32c9707b1bb066851b103b4c3ff9521e880f0bdaf7501968329da6fa6a98487077f532189b0931bb4e715e2a495f359e383320ecc1b212
7
+ data.tar.gz: d6f99733d6bc1be8eff2d4002b7b2e7d8aa8b1b6a48fe7ca26631a0ffc3780bafadcee2003c73afa473c457d59c736845d3e34f96e0a13ea1b3a48920b294b2c
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.2.19"
6
+ VERSION = "0.2.21"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -1074,7 +1074,15 @@ module MilkTea
1074
1074
  lower_expression(prepared_value, env:, expected_type: target.type)
1075
1075
  end
1076
1076
  update_cstr_metadata_for_assignment!(statement, prepared_value, env)
1077
- if statement.operator == "=" && contains_proc_storage_type?(target.type)
1077
+ operator = statement.operator
1078
+ if ["+=", "-=", "*=", "/="].include?(operator) &&
1079
+ (target.type.is_a?(Types::Vector) || target.type.is_a?(Types::Matrix) || target.type.is_a?(Types::Quaternion))
1080
+ binary_op = operator[0...-1]
1081
+ expanded = lower_vector_binary_op(binary_op, target, target.type, value, value.type, target.type)
1082
+ value = expanded || IR::Binary.new(operator: binary_op, left: target, right: value, type: target.type)
1083
+ operator = "="
1084
+ end
1085
+ if operator == "=" && contains_proc_storage_type?(target.type)
1078
1086
  rhs_name = fresh_c_temp_name(env, "proc_assign")
1079
1087
  lowered << IR::LocalDecl.new(name: rhs_name, linkage_name: rhs_name, type: target.type, value:)
1080
1088
  rhs = IR::Name.new(name: rhs_name, type: target.type, pointer: false)
@@ -1082,7 +1090,7 @@ module MilkTea
1082
1090
  lowered.concat(lower_proc_contained_guarded_release_statements(target, target.type))
1083
1091
  lowered << IR::Assignment.new(target:, operator: "=", value: rhs)
1084
1092
  else
1085
- lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
1093
+ lowered << IR::Assignment.new(target:, operator:, value:)
1086
1094
  end
1087
1095
  lowered
1088
1096
  end
@@ -211,7 +211,15 @@ module MilkTea
211
211
  end
212
212
  update_cstr_metadata_for_assignment!(statement, prepared_value, local_env)
213
213
  local_defers.concat(suppress_format_releases_for_assignment(prepared_cleanups, target.type))
214
- if statement.operator == "=" && contains_proc_storage_type?(target.type)
214
+ operator = statement.operator
215
+ if ["+=", "-=", "*=", "/="].include?(operator) &&
216
+ (target.type.is_a?(Types::Vector) || target.type.is_a?(Types::Matrix) || target.type.is_a?(Types::Quaternion))
217
+ binary_op = operator[0...-1]
218
+ expanded = lower_vector_binary_op(binary_op, target, target.type, value, value.type, target.type)
219
+ value = expanded || IR::Binary.new(operator: binary_op, left: target, right: value, type: target.type)
220
+ operator = "="
221
+ end
222
+ if operator == "=" && contains_proc_storage_type?(target.type)
215
223
  # Materialize the RHS to a C temp to avoid evaluating aggregate literals multiple times
216
224
  # and to ensure retain/release operate on a stable struct value throughout the sequence.
217
225
  rhs_name = fresh_c_temp_name(local_env, "proc_assign")
@@ -224,7 +232,7 @@ module MilkTea
224
232
  lowered.concat(lower_proc_contained_guarded_release_statements(target, target.type))
225
233
  lowered << IR::Assignment.new(target:, operator: "=", value: rhs)
226
234
  else
227
- lowered << IR::Assignment.new(target:, operator: statement.operator, value:)
235
+ lowered << IR::Assignment.new(target:, operator:, value:)
228
236
  end
229
237
  when AST::IfStmt
230
238
  if statement.inline
@@ -176,7 +176,11 @@ module MilkTea
176
176
  enum_type = @ctx.types.fetch(decl.name)
177
177
  backing_type = enum_type.backing_type
178
178
  members = decl.members.map do |member|
179
- value = lower_expression(member.value, env: empty_env, expected_type: backing_type)
179
+ value = if member.value
180
+ lower_expression(member.value, env: empty_env, expected_type: backing_type)
181
+ else
182
+ IR::IntegerLiteral.new(value: enum_type.member_value(member.name), type: backing_type)
183
+ end
180
184
  IR::EnumMember.new(name: member.name, linkage_name: enum_member_c_name(enum_type, member.name), value:)
181
185
  end
182
186
 
@@ -260,6 +260,8 @@ module MilkTea
260
260
  end
261
261
  when IR::ArrayLiteral
262
262
  "#{expression.type}(#{expression.elements.map { |element| render_expression(element) }.join(', ')})"
263
+ when IR::Assignment
264
+ "#{render_expression(expression.target)} #{expression.operator} #{render_expression(expression.value)}"
263
265
  else
264
266
  raise ArgumentError, "unsupported IR expression #{expression.class.name}"
265
267
  end
@@ -450,6 +450,21 @@ module MilkTea
450
450
  raise_sema_error("safe array indexing requires an addressable array value; bind it to a local first")
451
451
  end
452
452
 
453
+ if array_type?(receiver_type) && expression.index.is_a?(AST::IntegerLiteral)
454
+ length = array_length(receiver_type)
455
+ index_val = expression.index.value
456
+ if index_val >= length || index_val < 0
457
+ raise_sema_error("array index #{index_val} is out of bounds for array[T, #{length}]", expression)
458
+ end
459
+ end
460
+
461
+ if array_type?(receiver_type) && expression.index.is_a?(AST::UnaryOp) && expression.index.operator == "-" &&
462
+ expression.index.operand.is_a?(AST::IntegerLiteral)
463
+ length = array_length(receiver_type)
464
+ index_val = -expression.index.operand.value
465
+ raise_sema_error("array index #{index_val} is out of bounds for array[T, #{length}]", expression)
466
+ end
467
+
453
468
  infer_index_result_type(receiver_type, index_type)
454
469
  end
455
470
 
@@ -185,6 +185,7 @@ module MilkTea
185
185
 
186
186
  validate_consuming_foreign_expression!(statement.value, scopes:, root_allowed: false) if statement.value
187
187
  value_type = statement.value ? infer_expression(statement.value, scopes:, expected_type: return_type) : @ctx.types.fetch("void")
188
+ check_stack_pointer_escape_in_return(statement.value, scopes:) if statement.value
188
189
  ensure_assignable!(
189
190
  value_type,
190
191
  return_type,
@@ -494,16 +495,21 @@ module MilkTea
494
495
  line: statement.line, column: statement.column,
495
496
  )
496
497
  when "+=", "-=", "*=", "/="
497
- raise_sema_error("operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}") unless target_type.numeric? && value_type.numeric?
498
+ binary_op = statement.operator[0...-1]
499
+ if (result_type = vector_arithmetic_result(binary_op, target_type, value_type))
500
+ raise_sema_error("operator #{statement.operator} on #{target_type} and #{value_type} produces #{result_type}, expected #{target_type}") unless result_type == target_type
501
+ else
502
+ raise_sema_error("operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}") unless target_type.numeric? && value_type.numeric?
498
503
 
499
- ensure_assignable!(
500
- value_type,
501
- target_type,
502
- "operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}",
503
- expression: statement.value,
504
- contextual_int_to_float: contextual_int_to_float_target?(target_type),
505
- line: statement.line, column: statement.column,
506
- )
504
+ ensure_assignable!(
505
+ value_type,
506
+ target_type,
507
+ "operator #{statement.operator} requires matching numeric types, got #{target_type} and #{value_type}",
508
+ expression: statement.value,
509
+ contextual_int_to_float: contextual_int_to_float_target?(target_type),
510
+ line: statement.line, column: statement.column,
511
+ )
512
+ end
507
513
  when "%="
508
514
  unless common_integer_type(target_type, value_type) == target_type
509
515
  raise_sema_error("operator #{statement.operator} requires compatible integer types, got #{target_type} and #{value_type}")
@@ -1322,6 +1328,64 @@ module MilkTea
1322
1328
  end
1323
1329
  end
1324
1330
 
1331
+ # Walks the returned expression tree to detect ptr_of/ref_of/const_ptr_of
1332
+ # applied to a stack-local variable whose address must not escape the
1333
+ # function. Only flags when the pointer value itself (not a computed
1334
+ # result through a function call) reaches the return.
1335
+ def check_stack_pointer_escape_in_return(expression, scopes:)
1336
+ return unless expression
1337
+
1338
+ if expression.is_a?(AST::Call)
1339
+ check_stack_escape_call(expression, scopes:)
1340
+ elsif expression.is_a?(AST::BinaryOp)
1341
+ check_stack_pointer_escape_in_return(expression.left, scopes:)
1342
+ check_stack_pointer_escape_in_return(expression.right, scopes:)
1343
+ elsif expression.is_a?(AST::UnaryOp)
1344
+ check_stack_pointer_escape_in_return(expression.operand, scopes:)
1345
+ end
1346
+ end
1347
+
1348
+ def check_stack_escape_call(expression, scopes:)
1349
+ callee = expression.callee
1350
+ return unless callee.is_a?(AST::Identifier)
1351
+ return unless %w[ptr_of ref_of const_ptr_of].include?(callee.name)
1352
+ return if expression.arguments.empty?
1353
+
1354
+ arg = expression.arguments.first
1355
+ source = arg.respond_to?(:value) ? arg.value : arg
1356
+ check_stack_local_source(source, scopes:, builtin: callee.name)
1357
+ end
1358
+
1359
+ def check_stack_local_source(expression, scopes:, builtin:)
1360
+ case expression
1361
+ when AST::Identifier
1362
+ binding = lookup_local_binding(expression.name, scopes)
1363
+ return unless binding
1364
+ return unless %i[let var param].include?(binding.kind)
1365
+ return if stack_safe_pointer_source?(binding)
1366
+ raise_sema_error(
1367
+ "#{builtin}(#{expression.name}) cannot be returned: pointer to function-local storage",
1368
+ expression,
1369
+ )
1370
+ when AST::MemberAccess
1371
+ check_stack_local_source(expression.receiver, scopes:, builtin:)
1372
+ when AST::IndexAccess
1373
+ check_stack_local_source(expression.receiver, scopes:, builtin:)
1374
+ end
1375
+ end
1376
+
1377
+ def lookup_local_binding(name, scopes)
1378
+ scopes.reverse_each do |scope|
1379
+ return scope[name] if scope.key?(name)
1380
+ end
1381
+ nil
1382
+ end
1383
+
1384
+ def stack_safe_pointer_source?(binding)
1385
+ return false unless binding.type
1386
+ ref_type?(binding.type) || own_type?(binding.type) || (binding.mutable && binding.kind == :param)
1387
+ end
1388
+
1325
1389
  end
1326
1390
  end
1327
1391
  end
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.2.19
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -586,7 +586,7 @@ metadata:
586
586
  homepage_uri: https://teefan.github.io/mt-lang/
587
587
  source_code_uri: https://github.com/teefan/mt-lang
588
588
  post_install_message: |
589
- Milk Tea 0.2.19 installed!
589
+ Milk Tea 0.2.21 installed!
590
590
 
591
591
  System requirements:
592
592
  - A C compiler (gcc or clang) must be available on PATH