mt-lang 0.2.12 → 0.2.14

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: 28dc7f8c8824f11189bdd3cf704d10eefa2e144d466b218b5b34b1693531fe94
4
- data.tar.gz: 73dcea75aeb6fb9c460e6b574e0bb64dc118ca9cd0a028586bd526d7b00acb49
3
+ metadata.gz: 684d2aada4af1e2b5fc1c0685e26004ed9442931692cb25007777b09df77dbe5
4
+ data.tar.gz: 5f16c0ce02404a51f4ac8dd8c12b8047fb4ae5ab442b1269ba7ce8c58809a065
5
5
  SHA512:
6
- metadata.gz: c3286442bbca94bd51b9c7aa4166f2b854d9f6bd48da3f6860648f303c30c90e39f2ac531315a5f13a148f3591eab1a5a02dabfd935f6ea136dfe0018012db9d
7
- data.tar.gz: 53cc3240a7b226d152f831775e508a1db5c030f533d4fb6a88084f6564fcffa86954614e86b37f2c0b7650ae5ef27486e9aea05a6eaffe14b2aa5b2be9be0c8a
6
+ metadata.gz: 9e129d1a7a428094755af01cb5bb98c29fb78ee9003b6938cb4335eb0e58763804a9e1c902f5d1563111a20c7b70ca69f4497f11c4cf476ed5b9ef8696ccc9ed
7
+ data.tar.gz: ffc46a4a1658c84a2fa6d2d7d6331b98164487c488112055adc9a2f20367074f26a02002d0a36ec493f9f9738ac2123f80a2e1e04fbaa5b364a54509898a2644
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.12"
6
+ VERSION = "0.2.14"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -472,20 +472,13 @@ module MilkTea
472
472
 
473
473
  members = []
474
474
  skip_newlines
475
- auto_value = 0
476
475
  until check(:dedent) || eof?
477
476
  member_token = consume_name("expected member name")
478
477
  member_name = member_token.lexeme
479
478
  if match(:equal)
480
479
  value = parse_expression
481
- if value.is_a?(AST::IntegerLiteral)
482
- auto_value = value.value + 1
483
- elsif value.is_a?(AST::UnaryOp) && value.operator == "-" && value.operand.is_a?(AST::IntegerLiteral)
484
- auto_value = -value.operand.value + 1
485
- end
486
480
  else
487
- value = AST::IntegerLiteral.new(lexeme: auto_value.to_s, value: auto_value)
488
- auto_value += 1
481
+ value = nil
489
482
  end
490
483
  consume_end_of_statement
491
484
  members << AST::EnumMember.new(name: member_name, value:, line: member_token.line, column: member_token.column)
@@ -166,6 +166,7 @@ module MilkTea
166
166
  module_binding = @ctx.imported_modules[module_path]
167
167
  unless module_binding
168
168
  install_fallback_builtin_type(module_path)
169
+ @ctx.imports[module_path] = empty_module_binding(module_path) unless @ctx.imports.key?(module_path)
169
170
  next
170
171
  end
171
172
 
@@ -178,6 +179,19 @@ module MilkTea
178
179
  end
179
180
  end
180
181
 
182
+ def empty_module_binding(module_path)
183
+ ModuleBinding.new(
184
+ name: module_path,
185
+ types: {}, type_declarations: {}, interfaces: {},
186
+ attributes: {}, attribute_applications: {},
187
+ values: {}, functions: {}, methods: {},
188
+ implemented_interfaces: {}, imports: {},
189
+ private_types: {}, private_interfaces: {}, private_attributes: {},
190
+ private_values: {}, private_functions: {}, private_methods: {},
191
+ private_implemented_interfaces: {},
192
+ )
193
+ end
194
+
181
195
  def install_fallback_builtin_type(module_path)
182
196
  case module_path
183
197
  when "std.option"
@@ -581,8 +595,13 @@ module MilkTea
581
595
 
582
596
  decl.members.each do |member|
583
597
  begin
584
- actual_type = infer_expression(member.value, scopes: [], expected_type: backing_type)
585
- const_value = evaluate_enum_member_const_value(member.value, enum_type:, member_values:)
598
+ if member.value
599
+ actual_type = infer_expression(member.value, scopes: [], expected_type: backing_type)
600
+ const_value = evaluate_enum_member_const_value(member.value, enum_type:, member_values:)
601
+ else
602
+ const_value = (member_values.values.last&.succ || 0)
603
+ actual_type = backing_type
604
+ end
586
605
 
587
606
  compatible = types_compatible?(actual_type, backing_type, expression: member.value, scopes: [])
588
607
  compatible ||= actual_type.is_a?(Types::EnumBase) && types_compatible?(actual_type.backing_type, backing_type, expression: member.value, scopes: [])
@@ -742,7 +742,7 @@ module MilkTea
742
742
  end
743
743
 
744
744
  def method_dispatch_receiver_type_for_completion(receiver_type)
745
- return receiver_type.definition if receiver_type.is_a?(Types::StructInstance)
745
+ return receiver_type.definition if receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::VariantInstance)
746
746
 
747
747
  if receiver_type.is_a?(Types::Nullable)
748
748
  dispatch_base_type = method_dispatch_receiver_type_for_completion(receiver_type.base)
@@ -765,7 +765,7 @@ module MilkTea
765
765
 
766
766
  def project_field_receiver_type_for_completion(type, facts = nil)
767
767
  type = project_receiver_type_for_completion(type)
768
- return type.definition if type.is_a?(Types::StructInstance)
768
+ return type.definition if type.is_a?(Types::StructInstance) || type.is_a?(Types::VariantInstance)
769
769
  return field_type_from_struct_definition(type, facts) if facts
770
770
 
771
771
  type
@@ -792,7 +792,7 @@ module MilkTea
792
792
 
793
793
  def field_owner_type(receiver_type, facts = nil)
794
794
  aggregate_type = project_field_receiver_type_for_completion(receiver_type, facts)
795
- return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance)
795
+ return aggregate_type.definition if aggregate_type.is_a?(Types::StructInstance) || aggregate_type.is_a?(Types::VariantInstance)
796
796
 
797
797
  aggregate_type
798
798
  end
@@ -704,7 +704,7 @@ module MilkTea
704
704
  end
705
705
 
706
706
  def interface_receiver_definition_location(current_uri, receiver_type)
707
- receiver_type = receiver_type.definition if receiver_type.is_a?(Types::StructInstance)
707
+ receiver_type = receiver_type.definition if receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::VariantInstance)
708
708
 
709
709
  if receiver_type.module_name.nil? || receiver_type.module_name.empty?
710
710
  token = local_type_definition_token(current_uri, receiver_type.name)
@@ -716,7 +716,7 @@ module MilkTea
716
716
  end
717
717
 
718
718
  def receiver_module_name(receiver_type)
719
- receiver_type = receiver_type.definition if receiver_type.is_a?(Types::StructInstance)
719
+ receiver_type = receiver_type.definition if receiver_type.is_a?(Types::StructInstance) || receiver_type.is_a?(Types::VariantInstance)
720
720
 
721
721
  receiver_type.module_name
722
722
  end
@@ -281,6 +281,13 @@ module MilkTea
281
281
  'exchange' => 'static function exchange(value: T) -> T',
282
282
  'compare_exchange' => 'static function compare_exchange(expected: T, desired: T) -> bool',
283
283
  },
284
+ 'event' => {
285
+ 'subscribe' => 'function subscribe(listener) -> Result[Subscription, EventError]',
286
+ 'subscribe_once' => 'function subscribe_once(listener) -> Result[Subscription, EventError]',
287
+ 'unsubscribe' => 'function unsubscribe(subscription) -> bool',
288
+ 'emit' => 'function emit() / function emit(payload)',
289
+ 'wait' => 'function wait() -> Task[Result[T, EventError]]',
290
+ },
284
291
  'str_buffer' => {
285
292
  'assign' => 'static function assign(value: str) -> void',
286
293
  'append' => 'static function append(value: str) -> void',
@@ -307,20 +314,23 @@ module MilkTea
307
314
  token = context&.fetch(:token, nil)
308
315
  token_kind = token&.type || :none
309
316
  unless token&.type == :identifier
310
- info = builtin_keyword_hover_info(token)
311
- if info
312
- result_state = 'hit'
313
- return {
314
- contents: {
315
- kind: 'markdown',
316
- value: render_hover_markdown(info)
317
- },
318
- range: token_to_range(token)
319
- }
320
- end
317
+ unless member_access_token?(context)
318
+ info = builtin_keyword_hover_info(token) ||
319
+ BUILTIN_CALL_HOVER_INFO[token.lexeme]&.slice(:signature, :docs)
320
+ if info
321
+ result_state = 'hit'
322
+ return {
323
+ contents: {
324
+ kind: 'markdown',
325
+ value: render_hover_markdown(info)
326
+ },
327
+ range: token_to_range(token)
328
+ }
329
+ end
321
330
 
322
- result_state = 'not-identifier'
323
- return nil
331
+ result_state = 'not-identifier'
332
+ return nil
333
+ end
324
334
  end
325
335
 
326
336
  info = resolve_hover_info(uri, lsp_line, lsp_char, token: token, tokens: context[:tokens], token_index: context[:token_index], stages: stages)
@@ -355,10 +365,11 @@ module MilkTea
355
365
  token_index = context[:token_index]
356
366
  end
357
367
 
358
- return nil unless token&.type == :identifier
359
-
360
368
  tokens ||= @workspace.get_tokens(uri) || []
361
369
  token_index = tokens.index(token) if token_index.nil?
370
+ member_access_kw = token_index && keyword_member_access_token?(tokens, token_index)
371
+ return nil unless token&.type == :identifier || member_access_kw
372
+
362
373
  if token_index
363
374
  module_info = module_declaration_info_at(tokens, token_index)
364
375
  if module_info
@@ -412,6 +423,7 @@ module MilkTea
412
423
  signature = nil
413
424
  docs = nil
414
425
  source_location = nil
426
+ resolved_via_type_member = false
415
427
 
416
428
  if (binding = method_binding_at_token(facts, token))
417
429
  signature = method_signature(binding)
@@ -449,11 +461,26 @@ module MilkTea
449
461
  type = facts.types[name]
450
462
  signature = type_hover_signature(name, type)
451
463
  docs ||= BUILTIN_TYPE_DOCS[name]
464
+ elsif !name.include?(".") && (nested = find_nested_type_by_short_name(facts, name))
465
+ signature = type_hover_signature(name, nested)
466
+ docs ||= BUILTIN_TYPE_DOCS[name]
452
467
  elsif (binding = facts.values[name])
453
468
  signature = value_hover_signature(binding)
469
+ elsif !BUILTIN_CALL_HOVER_INFO.key?(name) && (member_info = find_member_in_types(facts, name))
470
+ type, member, value = member_info
471
+ signature = value ? "#{type.name}.#{member} = #{value}" : "#{type.name}.#{member}"
472
+ resolved_via_type_member = true
473
+ elsif !BUILTIN_CALL_HOVER_INFO.key?(name) && (arm_sig = find_variant_arm_in_types(facts, name))
474
+ signature = arm_sig
475
+ resolved_via_type_member = true
454
476
  elsif (import_binding = facts.imports[name])
455
477
  signature = "module #{import_binding.name}"
456
478
  source_location = module_definition_location(uri, import_binding.name)
479
+ elsif (attr_binding = facts.attributes[name])
480
+ targets = attr_binding.targets.map(&:to_s).join(", ")
481
+ params_str = attr_binding.params.map { |p| "#{p.name}: #{p.type}" }.join(", ")
482
+ signature = "attribute [#{targets}] #{name}(#{params_str})"
483
+ source_location ||= module_definition_location(uri, facts.module_name)
457
484
  else
458
485
  dot_receiver = @workspace.find_dot_receiver(uri, lsp_line, lsp_char)
459
486
  dot_receiver_path = @workspace.find_dot_receiver_path(uri, lsp_line, lsp_char)
@@ -499,7 +526,7 @@ module MilkTea
499
526
  if (method_binding = methods[name])
500
527
  signature = method_signature(method_binding)
501
528
  else
502
- type_base = receiver_type.to_s[/^([a-z_]+)/, 1]
529
+ type_base = receiver_type.to_s[/^([A-Za-z_]+)/, 1]
503
530
  if type_base && (method_sigs = BUILTIN_TYPE_METHOD_SIGNATURES[type_base])
504
531
  signature = method_sigs[name]
505
532
  end
@@ -516,18 +543,17 @@ module MilkTea
516
543
  end
517
544
 
518
545
  unless signature
519
- unless token_index && tokens && tokens[previous_non_trivia_token_index(tokens, token_index)]&.type == :dot
520
- local_def = @workspace.find_definition_token_global(
521
- name,
522
- preferred_uri: uri,
523
- before_line: lsp_line + 1,
524
- before_char: lsp_char + 1,
525
- )
526
- if local_def
527
- signature = resolve_lexical_local_hover_signature(local_def[:uri], name, local_def[:token])
528
- end
546
+ if token_index && tokens && parameter_declaration_token?(tokens, token_index)
547
+ signature = resolve_lexical_local_hover_signature(uri, name, tokens[token_index])
529
548
  end
530
549
  end
550
+
551
+ unless signature
552
+ if token_index && tokens && attribute_target_token?(tokens, token_index)
553
+ signature = "attribute target #{name}"
554
+ end
555
+ end
556
+
531
557
  end
532
558
 
533
559
  return nil unless signature
@@ -535,6 +561,8 @@ module MilkTea
535
561
 
536
562
  definition_entry = if source_location
537
563
  measure_perf_stage(stages, 'definition_entry') { hover_definition_entry_from_location(source_location) }
564
+ elsif resolved_via_type_member
565
+ nil
538
566
  else
539
567
  measure_perf_stage(stages, 'global_definition') do
540
568
  @workspace.find_definition_token_global(
@@ -961,9 +989,10 @@ module MilkTea
961
989
  source_location = field_definition_location(current_uri, field_receiver_type, segment[:name])
962
990
 
963
991
  if segment[:token_index] == token_index
992
+ definition_entry = hover_definition_entry_from_location(source_location)
964
993
  return {
965
994
  signature: field_hover_signature(segment[:name], field_type),
966
- docs: nil,
995
+ docs: hover_doc_comment_for_definition(definition_entry),
967
996
  source: hover_source_label_from_location(source_location),
968
997
  source_uri: hover_source_uri_from_location(source_location),
969
998
  source_line: hover_source_line_from_location(source_location),
@@ -978,7 +1007,7 @@ module MilkTea
978
1007
  if segment[:token_index] == token_index
979
1008
  return {
980
1009
  signature: type_hover_signature(segment[:name], nested),
981
- docs: nil,
1010
+ docs: BUILTIN_TYPE_DOCS[segment[:name]],
982
1011
  }
983
1012
  end
984
1013
  current_type = nested
@@ -993,10 +1022,11 @@ module MilkTea
993
1022
 
994
1023
  source_location = module_member_binding_location(current_uri, method_info[:module_name], segment[:name], method_info[:binding])
995
1024
  source_location ||= module_member_definition_location(current_uri, method_info[:module_name], segment[:name])
1025
+ definition_entry = hover_definition_entry_from_location(source_location)
996
1026
 
997
1027
  return {
998
1028
  signature: method_signature(method_info[:binding]),
999
- docs: nil,
1029
+ docs: hover_doc_comment_for_definition(definition_entry),
1000
1030
  source: hover_source_label_from_location(source_location),
1001
1031
  source_uri: hover_source_uri_from_location(source_location),
1002
1032
  source_line: hover_source_line_from_location(source_location),
@@ -1386,6 +1416,43 @@ module MilkTea
1386
1416
  "field #{name}: #{type}"
1387
1417
  end
1388
1418
 
1419
+ def find_nested_type_by_short_name(facts, short_name)
1420
+ matches = facts.types.keys.select { |k| k.to_s.end_with?(".#{short_name}") }
1421
+ return nil unless matches.length == 1
1422
+
1423
+ facts.types[matches.first]
1424
+ end
1425
+
1426
+ def find_member_in_types(facts, name)
1427
+ facts.types.reverse_each do |_key, type|
1428
+ next unless type.respond_to?(:members)
1429
+
1430
+ member = type.members.find { |m| m == name }
1431
+ if member
1432
+ value = type.respond_to?(:member_value) ? type.member_value(name) : nil
1433
+ return [type, member, value]
1434
+ end
1435
+ end
1436
+ nil
1437
+ end
1438
+
1439
+ def find_variant_arm_in_types(facts, name)
1440
+ facts.types.reverse_each do |_key, type|
1441
+ arm = nil
1442
+ if type.respond_to?(:arms)
1443
+ arm = type.arms[name]
1444
+ elsif type.respond_to?(:arm_names) && type.arm_names.include?(name)
1445
+ arm = type.arm(name)
1446
+ end
1447
+ next unless arm
1448
+
1449
+ fields = arm.map { |fname, ftype| "#{fname}: #{ftype}" }.join(", ")
1450
+ field_str = fields.empty? ? "" : "(#{fields})"
1451
+ return "#{type.name}.#{name}#{field_str}"
1452
+ end
1453
+ nil
1454
+ end
1455
+
1389
1456
  def builtin_hover_info(name, tokens, token_index)
1390
1457
  specialization_info = builtin_value_specialization_info(name, tokens, token_index)
1391
1458
  return specialization_info if specialization_info
@@ -1399,11 +1466,60 @@ module MilkTea
1399
1466
  builtin_call_hover_info(name, tokens, token_index)
1400
1467
  end
1401
1468
 
1469
+ def parameter_declaration_token?(tokens, index)
1470
+ return false unless index && tokens[index]&.type == :identifier
1471
+
1472
+ prev = previous_non_trivia_token_index(tokens, index)
1473
+ return false unless prev
1474
+ return false unless [:lparen, :comma].include?(tokens[prev].type)
1475
+
1476
+ nxt = next_non_trivia_token_index(tokens, index + 1)
1477
+ nxt && tokens[nxt].type == :colon
1478
+ end
1479
+
1480
+ def attribute_target_token?(tokens, index)
1481
+ return false unless index && tokens[index]&.type == :identifier
1482
+
1483
+ prev = previous_non_trivia_token_index(tokens, index)
1484
+ return false unless prev && [:lbracket, :comma].include?(tokens[prev].type)
1485
+
1486
+ after = next_non_trivia_token_index(tokens, index + 1)
1487
+ return false unless after && [:rbracket, :comma].include?(tokens[after].type)
1488
+
1489
+ true
1490
+ end
1491
+
1492
+ def call_argument_token?(tokens, index)
1493
+ return false unless index && tokens[index]&.type == :identifier
1494
+
1495
+ prev = previous_non_trivia_token_index(tokens, index)
1496
+ return false unless prev && [:lparen, :comma].include?(tokens[prev].type)
1497
+
1498
+ nxt = next_non_trivia_token_index(tokens, index + 1)
1499
+ !nxt || ![:colon, :rbracket, :comma].include?(tokens[nxt].type)
1500
+ end
1501
+
1402
1502
  def builtin_in_member_access_context?(tokens, token_index)
1403
1503
  prev_index = previous_non_trivia_token_index(tokens, token_index)
1404
1504
  prev_index && tokens[prev_index].type == :dot
1405
1505
  end
1406
1506
 
1507
+ def member_access_token?(context)
1508
+ tokens = context[:tokens]
1509
+ token_index = context[:token_index]
1510
+ return false unless tokens && token_index
1511
+ prev = previous_non_trivia_token_index(tokens, token_index)
1512
+ prev && tokens[prev].type == :dot
1513
+ end
1514
+
1515
+ def keyword_member_access_token?(tokens, token_index)
1516
+ return false unless tokens && token_index
1517
+ tok = tokens[token_index]
1518
+ return false unless tok && tok.type != :identifier
1519
+ prev = previous_non_trivia_token_index(tokens, token_index)
1520
+ prev && tokens[prev].type == :dot
1521
+ end
1522
+
1407
1523
  def builtin_value_specialization_info(name, tokens, token_index)
1408
1524
  return nil unless %w[zero default reinterpret].include?(name)
1409
1525
 
@@ -1439,7 +1555,7 @@ module MilkTea
1439
1555
  end
1440
1556
 
1441
1557
  def builtin_type_constructor_hover_info(name, tokens, token_index)
1442
- return nil unless %w[array span Option Result SoA str_buffer].include?(name)
1558
+ return nil unless %w[array span Option Result SoA str_buffer ref ptr const_ptr own Task atomic].include?(name)
1443
1559
 
1444
1560
  lbracket_index = next_non_trivia_token_index(tokens, token_index + 1)
1445
1561
  return nil unless lbracket_index && tokens[lbracket_index].type == :lbracket
@@ -1494,6 +1610,18 @@ module MilkTea
1494
1610
  '`Result[T, E]` is the built-in success/failure type with `success(value = ...)` and `failure(error = ...)` arms.'
1495
1611
  when 'str_buffer'
1496
1612
  '`str_buffer[N]` is a fixed-capacity mutable UTF-8 text buffer. Methods: `assign`, `append`, `assign_format`, `append_format`, `clear`, `len`, `as_str`, `as_cstr`.'
1613
+ when 'ref'
1614
+ '`ref[T]` is a non-null borrow reference. Auto-dereferences for member access and method calls. Cannot be stored in module-level variables, constants, or nested containers.'
1615
+ when 'ptr'
1616
+ '`ptr[T]` is a raw mutable pointer. Indexing, dereference, and arithmetic require `unsafe`. Nullable via `ptr[T]?`.'
1617
+ when 'const_ptr'
1618
+ '`const_ptr[T]` is a read-only pointer. Does not require `unsafe` for dereference.'
1619
+ when 'own'
1620
+ '`own[T]` is an owning heap pointer. Auto-dereferences like `ref`. Storable, returnable, and nullable. Allocated via `heap.must_alloc[T](count)`.'
1621
+ when 'Task'
1622
+ '`Task[T]` is an async task future. Returned by `async function`. Use `await` to unwrap, or `aio.wait`/`aio.run` to drive.'
1623
+ when 'atomic'
1624
+ '`atomic[T]` is an atomic value for lock-free concurrent access. `T` must be a primitive integer or `bool`. Methods: `load`, `store`, `add`, `sub`, `exchange`.'
1497
1625
  end
1498
1626
 
1499
1627
  {
@@ -1734,6 +1862,14 @@ module MilkTea
1734
1862
  when "let" then kind = :let
1735
1863
  when "var" then kind = :var
1736
1864
  when "const" then kind = :const
1865
+ when "struct" then kind = :struct_type
1866
+ when "function", "async", "external", "foreign" then kind = :function
1867
+ when "enum" then kind = :enum_type
1868
+ when "flags" then kind = :flags_type
1869
+ when "union" then kind = :union_type
1870
+ when "variant" then kind = :variant_type
1871
+ when "(" then kind = :param
1872
+ when "," then kind = :param
1737
1873
  end
1738
1874
  end
1739
1875
 
@@ -1760,9 +1896,15 @@ module MilkTea
1760
1896
  end
1761
1897
 
1762
1898
  kind ||= :let
1899
+ unless %i[let var const param].include?(kind)
1900
+ kind = kind.to_s.sub(/_type$/, "")
1901
+ return "#{kind} #{name}"
1902
+ end
1903
+
1904
+ display_kind = kind == :param ? "parameter" : kind.to_s
1763
1905
  mutability = kind == :var ? "mutable" : "immutable"
1764
1906
  if type_str && !type_str.empty?
1765
- "#{kind} #{name}: #{type_str} (#{mutability})"
1907
+ "#{display_kind} #{name}: #{type_str} (#{mutability})"
1766
1908
  else
1767
1909
  "#{kind} #{name} (#{mutability})"
1768
1910
  end
@@ -44,47 +44,62 @@ module MilkTea
44
44
  while i < tokens.length - 1
45
45
  callee = tokens[i]
46
46
  next_tok = tokens[i + 1]
47
-
48
47
  prev_tok = i > 0 ? tokens[i - 1] : nil
49
48
 
50
- if callee.type == :identifier && prev_tok&.type != :function && prev_tok&.type != :dot
51
- binding = nil
52
- lparen_index = nil
49
+ binding = nil
50
+ lparen_index = nil
53
51
 
52
+ if callee.type == :identifier && prev_tok&.type != :function
53
+ # Direct function call: func(...)
54
54
  if next_tok&.type == :lparen
55
55
  binding = facts.functions[callee.lexeme]
56
+ binding ||= facts.imports.each_value.find { |mod| mod.functions.key?(callee.lexeme) }&.functions&.dig(callee.lexeme)
56
57
  lparen_index = i + 1
57
58
  elsif next_tok&.type == :dot
58
59
  member = tokens[i + 2]
59
60
  member_lparen = tokens[i + 3]
60
61
  if member&.type == :identifier && member_lparen&.type == :lparen
61
- module_binding = facts.imports[callee.lexeme]
62
- binding = module_binding&.functions&.[](member.lexeme)
62
+ # Module-qualified call: mod.func(...)
63
+ mod_binding = facts.imports[callee.lexeme]
64
+ if mod_binding
65
+ binding = mod_binding.functions[member.lexeme]
66
+ else
67
+ # Instance method call: obj.method(...) or Type.static(...)
68
+ receiver_type = resolve_dot_receiver_value_type(facts, callee.lexeme, callee.line, callee.column)
69
+ receiver_type ||= facts.types[callee.lexeme]
70
+ unless receiver_type
71
+ receiver_type = facts.imports.each_value.find { |mod| mod.types.key?(callee.lexeme) }&.types&.dig(callee.lexeme)
72
+ end
73
+ if receiver_type
74
+ methods = methods_for_receiver_type(facts, receiver_type)
75
+ binding = methods[member.lexeme] || methods["static:#{member.lexeme}"]
76
+ end
77
+ end
63
78
  lparen_index = i + 3
64
79
  end
65
80
  end
81
+ end
66
82
 
67
- if binding && lparen_index
68
- arg_starts, closing_index = collect_call_argument_starts(tokens, lparen_index)
69
- params_list = binding.type.params
70
-
71
- arg_starts.each_with_index do |arg_tok, index|
72
- break if index >= params_list.length
73
- next unless position_in_range?(arg_tok.line - 1, arg_tok.column - 1, start_line, start_char, end_line, end_char)
74
- next if self_describing_argument_expression?(tokens, arg_tok)
75
- param_name = params_list[index].name
76
- next if arg_tok.type == :identifier && arg_tok.lexeme == param_name
77
-
78
- hints << {
79
- position: { line: arg_tok.line - 1, character: arg_tok.column - 1 },
80
- label: "#{params_list[index].name}: ",
81
- kind: 2,
82
- paddingRight: true
83
- }
84
- end
85
-
86
- i = closing_index if closing_index
83
+ if binding && lparen_index
84
+ arg_starts, closing_index = collect_call_argument_starts(tokens, lparen_index)
85
+ params_list = binding.respond_to?(:type) ? binding.type.params : []
86
+
87
+ arg_starts.each_with_index do |arg_tok, index|
88
+ break if index >= params_list.length
89
+ next unless position_in_range?(arg_tok.line - 1, arg_tok.column - 1, start_line, start_char, end_line, end_char)
90
+ next if self_describing_argument_expression?(tokens, arg_tok)
91
+ param_name = params_list[index].name
92
+ next if arg_tok.type == :identifier && arg_tok.lexeme == param_name
93
+
94
+ hints << {
95
+ position: { line: arg_tok.line - 1, character: arg_tok.column - 1 },
96
+ label: "#{params_list[index].name}: ",
97
+ kind: 2,
98
+ paddingRight: true
99
+ }
87
100
  end
101
+
102
+ i = closing_index if closing_index
88
103
  end
89
104
 
90
105
  i += 1
@@ -121,6 +136,12 @@ module MilkTea
121
136
  next unless position_in_range?(func.line - 1, func.column - 1, start_line, start_char, end_line, end_char)
122
137
 
123
138
  binding = facts.functions[func.name]
139
+ unless binding
140
+ facts.imports.each_value do |mod|
141
+ binding = mod.functions[func.name]
142
+ break if binding
143
+ end
144
+ end
124
145
  next unless binding
125
146
 
126
147
  return_type = binding.type.return_type
@@ -311,6 +311,30 @@ module MilkTea
311
311
  unless scoped.nil?
312
312
  return scoped.map { |entry| { range: entry[:range], kind: 1 } }
313
313
  end
314
+
315
+ binding_id = begin
316
+ rename_target_binding_id(uri, token, lsp_line, lsp_char, facts)
317
+ rescue StandardError
318
+ nil
319
+ end
320
+ if binding_id
321
+ ranges = begin
322
+ scoped_binding_occurrence_ranges(uri, token.lexeme, facts, binding_id, include_declaration: true)
323
+ rescue StandardError
324
+ []
325
+ end
326
+ unless ranges.empty?
327
+ return ranges.map do |range|
328
+ {
329
+ range: {
330
+ start: { line: range[:line] - 1, character: range[:column] - 1 },
331
+ end: { line: range[:line] - 1, character: range[:column] - 1 + range[:length] },
332
+ },
333
+ kind: 1,
334
+ }
335
+ end
336
+ end
337
+ end
314
338
  end
315
339
 
316
340
  toks = @workspace.get_tokens(uri) || []
@@ -70,11 +70,6 @@ module MilkTea
70
70
  workspace_symbol_changes = workspace_symbol_identity_rename_changes(uri, token, lsp_line, lsp_char, new_name)
71
71
  return { changes: workspace_symbol_changes } if workspace_symbol_changes
72
72
 
73
- if (facts = @workspace.get_facts(uri))
74
- module_symbol_changes = module_level_symbol_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
75
- return { changes: module_symbol_changes } if module_symbol_changes
76
- end
77
-
78
73
  lexical_changes = lexical_rename_changes_in_document(uri, token.lexeme, new_name)
79
74
  return { changes: lexical_changes } if lexical_changes
80
75
 
@@ -202,27 +197,6 @@ module MilkTea
202
197
  { uri => edits }
203
198
  end
204
199
 
205
- def module_level_symbol_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
206
- name = token.lexeme
207
- module_level = facts.functions.key?(name) || facts.values.key?(name) || facts.types.key?(name) || facts.interfaces&.key?(name)
208
- return nil unless module_level
209
-
210
- related_uris = @workspace.related_open_document_uris(uri)
211
- changes = {}
212
- [uri, *related_uris].uniq.each do |doc_uri|
213
- edits = []
214
- doc_tokens = @workspace.get_tokens(doc_uri) || []
215
- doc_tokens.each do |tok|
216
- next unless tok.type == :identifier && tok.lexeme == name
217
- edits << { range: token_to_range(tok), newText: new_name }
218
- end
219
- changes[doc_uri] = edits unless edits.empty?
220
- end
221
-
222
- return nil if changes.empty?
223
- changes
224
- end
225
-
226
200
  def import_alias_rename_changes(uri, token, lsp_line, lsp_char, facts, new_name)
227
201
  alias_name = token.lexeme
228
202
  if facts
@@ -286,22 +260,28 @@ module MilkTea
286
260
  type_name_member_access?(tokens, cursor_index, facts)
287
261
  return nil unless cursor_is_enum_member
288
262
 
289
- edits = tokens.each_with_index.filter_map do |tok, i|
290
- next unless tok.type == :identifier && tok.lexeme == token.lexeme
263
+ changes = {}
264
+ [uri, *@workspace.related_open_document_uris(uri)].uniq.each do |doc_uri|
265
+ doc_tokens = @workspace.get_tokens(doc_uri) || []
266
+ doc_facts = doc_uri == uri ? facts : @workspace.get_facts(doc_uri)
267
+ edits = doc_tokens.each_with_index.filter_map do |tok, i|
268
+ next unless tok.type == :identifier && tok.lexeme == token.lexeme
291
269
 
292
- enum_member_decl = variant_enum_member_declaration?(tokens, i)
293
- enum_member_access = type_name_member_access?(tokens, i, facts)
294
- next unless enum_member_decl || enum_member_access
270
+ enum_member_decl = variant_enum_member_declaration?(doc_tokens, i)
271
+ enum_member_access = type_name_member_access?(doc_tokens, i, doc_facts)
272
+ next unless enum_member_decl || enum_member_access
295
273
 
296
- {
297
- range: token_to_range(tok),
298
- newText: new_name,
299
- }
300
- end
274
+ {
275
+ range: token_to_range(tok),
276
+ newText: new_name,
277
+ }
278
+ end
301
279
 
302
- return nil if edits.empty?
280
+ changes[doc_uri] = edits unless edits.empty?
281
+ end
303
282
 
304
- { uri => edits }
283
+ return nil if changes.empty?
284
+ changes
305
285
  end
306
286
 
307
287
  def scoped_local_reference_locations(uri, token, lsp_line, lsp_char, facts, include_declaration:)
@@ -482,16 +462,28 @@ module MilkTea
482
462
  struct_type = find_struct_containing_field(facts, field_name)
483
463
  return nil unless struct_type
484
464
 
465
+ changes = collect_struct_field_changes(uri, field_name, new_name)
466
+ related_uris = @workspace.related_open_document_uris(uri)
467
+ related_uris.each do |related_uri|
468
+ next if related_uri == uri
469
+ related_changes = collect_struct_field_changes(related_uri, field_name, new_name)
470
+ changes[related_uri] = related_changes if related_changes
471
+ end
472
+
473
+ return nil if changes.empty?
474
+ changes
475
+ end
476
+
477
+ def collect_struct_field_changes(uri, field_name, new_name)
478
+ tokens = @workspace.get_tokens(uri) || []
485
479
  edits = []
486
480
  tokens.each_with_index do |tok, i|
487
481
  next unless tok.type == :identifier && tok.lexeme == field_name
488
- next unless struct_field_edit_context?(tokens, i, struct_type)
482
+ next unless struct_field_edit_context?(tokens, i, nil)
489
483
 
490
484
  edits << { range: token_to_range(tok), newText: new_name }
491
485
  end
492
-
493
- return nil if edits.empty?
494
- { uri => edits }
486
+ edits.empty? ? nil : edits
495
487
  end
496
488
 
497
489
  def struct_field_cursor_context?(tokens, index)
@@ -555,17 +547,25 @@ module MilkTea
555
547
  receiver_type = method_info[:receiver_type]
556
548
  return nil unless receiver_type
557
549
 
558
- edits = []
559
- doc_tokens = @workspace.get_tokens(uri) || []
560
- doc_tokens.each_with_index do |tok, i|
561
- next unless tok.type == :identifier && tok.lexeme == method_name
562
- next unless i > 0 && doc_tokens[i - 1].type == :dot
550
+ changes = {}
551
+ [uri, *@workspace.related_open_document_uris(uri)].uniq.each do |doc_uri|
552
+ edits = []
553
+ doc_tokens = @workspace.get_tokens(doc_uri) || []
554
+ doc_tokens.each_with_index do |tok, i|
555
+ next unless tok.type == :identifier && tok.lexeme == method_name
563
556
 
564
- edits << { range: token_to_range(tok), newText: new_name }
557
+ is_call = i > 0 && doc_tokens[i - 1].type == :dot
558
+ is_def = doc_uri == uri && tok.line == token.line && tok.column == token.column
559
+ next unless is_call || is_def
560
+
561
+ edits << { range: token_to_range(tok), newText: new_name }
562
+ end
563
+
564
+ changes[doc_uri] = edits unless edits.empty?
565
565
  end
566
566
 
567
- return nil if edits.empty?
568
- { uri => edits }
567
+ return nil if changes.empty?
568
+ changes
569
569
  end
570
570
 
571
571
  def find_method_at_position(uri, token, facts)
@@ -28,18 +28,25 @@ module MilkTea
28
28
  end
29
29
  end
30
30
 
31
- if (receiver_type = resolve_dot_receiver_value_type(facts, dot_recv, lsp_line + 1, lsp_char + 1))
31
+ if (receiver_type = resolve_dot_receiver_value_type(facts, dot_recv, lsp_line + 1, lsp_char + 1) ||
32
+ resolve_dot_receiver_value_type(facts, dot_recv.sub(/\[.*\]\z/, ""), lsp_line + 1, lsp_char + 1))
32
33
  methods = methods_for_receiver_type(facts, receiver_type)
33
34
  if (binding = methods[name] || methods["static:#{name}"])
34
35
  return [binding, name]
35
36
  end
36
37
  end
37
38
 
38
- if (type_receiver = resolve_type_receiver_info(facts, dot_recv, nil))
39
+ if (type_receiver = resolve_type_receiver_info(facts, dot_recv, nil) ||
40
+ resolve_type_receiver_info(facts, dot_recv.sub(/\[.*\]\z/, ""), nil))
39
41
  methods = methods_for_receiver_type(facts, type_receiver[:type])
40
42
  if (binding = methods[name] || methods["static:#{name}"])
41
43
  return [binding, name]
42
44
  end
45
+ if type_receiver[:type].respond_to?(:arms) && (arm = type_receiver[:type].arms[name])
46
+ if arm.respond_to?(:payload) && arm.payload
47
+ return [arm.payload, name]
48
+ end
49
+ end
43
50
  end
44
51
  end
45
52
 
@@ -60,7 +67,7 @@ module MilkTea
60
67
  end
61
68
 
62
69
  # 6. Type constructor (struct constructor)
63
- if (type = facts.types[name])
70
+ if (type = facts.types[name] || facts.imports.each_value.find { |mod| mod.types.key?(name) }&.types&.dig(name))
64
71
  if type.respond_to?(:fields) && !type.fields.empty?
65
72
  return [type, name]
66
73
  end
@@ -99,12 +106,13 @@ module MilkTea
99
106
  end
100
107
 
101
108
  return nil unless extending
102
- facts.types[extending.type_name]
109
+ facts.types[extending.type_name] ||
110
+ facts.imports.each_value.find { |mod| mod.types.key?(extending.type_name) }&.types&.dig(extending.type_name)
103
111
  end
104
112
 
105
113
  def build_signature_from_binding(binding, name, ctx)
106
- if binding.is_a?(Types::Struct) || binding.is_a?(Types::StructInstance)
107
- fields = binding.fields
114
+ if binding.is_a?(Types::Struct) || binding.is_a?(Types::StructInstance) || binding.is_a?(Types::VariantArmPayload)
115
+ fields = binding.is_a?(Types::StructInstance) ? binding.definition.fields : binding.fields
108
116
  params_list = fields.map { |fname, ftype| Types::Registry.parameter(fname, ftype) }
109
117
  return_type_label = binding.is_a?(Types::StructInstance) ? binding.to_s : binding.name
110
118
  elsif binding.respond_to?(:[])
@@ -120,6 +128,7 @@ module MilkTea
120
128
  activeParameter: ctx[:active_parameter],
121
129
  }
122
130
  else
131
+ return nil unless binding.respond_to?(:type) && binding.type.respond_to?(:params)
123
132
  params_list = binding.type.params
124
133
  return_type_label = binding.type.return_type
125
134
  end
@@ -6,7 +6,15 @@ module MilkTea
6
6
  module WorkspaceCaches
7
7
  def get_content(uri)
8
8
  @document_state_mutex.synchronize do
9
- @open_documents[uri] || @indexed_documents[uri] || ''
9
+ @open_documents[uri] || @indexed_documents[uri] ||
10
+ begin
11
+ path = uri_to_path(uri)
12
+ if path && File.file?(path)
13
+ @indexed_documents[uri] = File.read(path)
14
+ end
15
+ rescue StandardError
16
+ nil
17
+ end || ''
10
18
  end
11
19
  end
12
20
 
data/std/option.mt CHANGED
@@ -8,6 +8,7 @@ public variant Option[T]:
8
8
  none
9
9
 
10
10
  extending Option[T]:
11
+ ## Returns true when the option holds a value.
11
12
  public function is_some() -> bool:
12
13
  match this:
13
14
  Option.some:
@@ -15,6 +16,7 @@ extending Option[T]:
15
16
  Option.none:
16
17
  return false
17
18
 
19
+ ## Returns true when the option holds no value.
18
20
  public function is_none() -> bool:
19
21
  match this:
20
22
  Option.some:
@@ -22,6 +24,7 @@ extending Option[T]:
22
24
  Option.none:
23
25
  return true
24
26
 
27
+ ## Returns the contained value or aborts via fatal.
25
28
  public function unwrap() -> T:
26
29
  match this:
27
30
  Option.some as payload:
@@ -29,6 +32,7 @@ extending Option[T]:
29
32
  Option.none:
30
33
  fatal(c"called Option.unwrap on a none value")
31
34
 
35
+ ## Returns the contained value or aborts with the given message.
32
36
  public function expect(msg: str) -> T:
33
37
  match this:
34
38
  Option.some as payload:
@@ -36,6 +40,7 @@ extending Option[T]:
36
40
  Option.none:
37
41
  fatal(msg)
38
42
 
43
+ ## Returns the contained value or a default.
39
44
  public function unwrap_or(default: T) -> T:
40
45
  match this:
41
46
  Option.some as payload:
@@ -43,6 +48,7 @@ extending Option[T]:
43
48
  Option.none:
44
49
  return default
45
50
 
51
+ ## Returns the contained value or calls a fallback closure.
46
52
  public function unwrap_or_else(f: proc() -> T) -> T:
47
53
  match this:
48
54
  Option.some as payload:
data/std/result.mt CHANGED
@@ -11,6 +11,7 @@ public variant Result[T, E]:
11
11
  failure(error: E)
12
12
 
13
13
  extending Result[T, E]:
14
+ ## Returns true when the result holds a success value.
14
15
  public function is_success() -> bool:
15
16
  match this:
16
17
  Result.success:
@@ -18,6 +19,7 @@ extending Result[T, E]:
18
19
  Result.failure:
19
20
  return false
20
21
 
22
+ ## Returns true when the result holds a failure value.
21
23
  public function is_failure() -> bool:
22
24
  match this:
23
25
  Result.success:
@@ -25,6 +27,7 @@ extending Result[T, E]:
25
27
  Result.failure:
26
28
  return true
27
29
 
30
+ ## Returns the success value or aborts via fatal.
28
31
  public function unwrap() -> T:
29
32
  match this:
30
33
  Result.success as payload:
@@ -32,6 +35,7 @@ extending Result[T, E]:
32
35
  Result.failure:
33
36
  fatal(c"called Result.unwrap on a failure value")
34
37
 
38
+ ## Returns the success value or aborts with the given message.
35
39
  public function expect(msg: str) -> T:
36
40
  match this:
37
41
  Result.success as payload:
@@ -39,6 +43,7 @@ extending Result[T, E]:
39
43
  Result.failure:
40
44
  fatal(msg)
41
45
 
46
+ ## Returns the failure error value or aborts via fatal.
42
47
  public function unwrap_error() -> E:
43
48
  match this:
44
49
  Result.success:
@@ -46,6 +51,7 @@ extending Result[T, E]:
46
51
  Result.failure as payload:
47
52
  return payload.error
48
53
 
54
+ ## Returns the failure error value or aborts with the given message.
49
55
  public function expect_error(msg: str) -> E:
50
56
  match this:
51
57
  Result.success:
@@ -53,6 +59,7 @@ extending Result[T, E]:
53
59
  Result.failure as payload:
54
60
  return payload.error
55
61
 
62
+ ## Returns the success value or a default.
56
63
  public function unwrap_or(default: T) -> T:
57
64
  match this:
58
65
  Result.success as payload:
@@ -60,6 +67,7 @@ extending Result[T, E]:
60
67
  Result.failure:
61
68
  return default
62
69
 
70
+ ## Returns the success value or calls a fallback closure with the error.
63
71
  public function unwrap_or_else(f: proc(error: E) -> T) -> T:
64
72
  match this:
65
73
  Result.success as payload:
@@ -67,6 +75,7 @@ extending Result[T, E]:
67
75
  Result.failure as payload:
68
76
  return f(error=payload.error)
69
77
 
78
+ ## Maps the error through a closure while preserving the success value.
70
79
  public function map_error[F](f: proc(error: E) -> F) -> Result[T, F]:
71
80
  match this:
72
81
  Result.success as payload:
@@ -74,6 +83,7 @@ extending Result[T, E]:
74
83
  Result.failure as payload:
75
84
  return Result[T, F].failure(error = f(payload.error))
76
85
 
86
+ ## Converts the result into an Option, discarding the error.
77
87
  public function ok() -> Option[T]:
78
88
  match this:
79
89
  Result.success as payload:
@@ -81,6 +91,7 @@ extending Result[T, E]:
81
91
  Result.failure:
82
92
  return Option[T].none
83
93
 
94
+ ## Converts the failure into an Option, discarding the success value.
84
95
  public function error() -> Option[E]:
85
96
  match this:
86
97
  Result.success:
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.12
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -583,7 +583,7 @@ metadata:
583
583
  homepage_uri: https://teefan.github.io/mt-lang/
584
584
  source_code_uri: https://github.com/teefan/mt-lang
585
585
  post_install_message: |
586
- Milk Tea 0.2.12 installed!
586
+ Milk Tea 0.2.14 installed!
587
587
 
588
588
  System requirements:
589
589
  - A C compiler (gcc or clang) must be available on PATH