mt-lang 0.3.38 → 0.3.39

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.
@@ -271,6 +271,24 @@ module MilkTea
271
271
  'Subscription' => 'Opaque handle returned by `event.subscribe`. Pass to `event.unsubscribe` to remove a listener.',
272
272
  }.freeze
273
273
 
274
+ VEC_METHOD_SIGNATURES = {
275
+ 'with' => 'function with(...) -> vec',
276
+ 'dot' => 'function dot(other: vec) -> float',
277
+ 'length' => 'function length() -> float',
278
+ 'length_sq' => 'function length_sq() -> float',
279
+ 'cross' => 'function cross(other: vec) -> vec',
280
+ 'normalized' => 'function normalized() -> vec',
281
+ 'squared_len' => 'function squared_len() -> float',
282
+ }.freeze
283
+
284
+ PTR_METHOD_SIGNATURES = {
285
+ 'load' => 'function load() -> T',
286
+ 'store' => 'function store(value: T) -> void',
287
+ 'load_at' => 'function load_at(offset: ptr_uint) -> T',
288
+ 'store_at' => 'function store_at(offset: ptr_uint, value: T) -> void',
289
+ 'cast' => 'function cast() -> ptr[U]',
290
+ }.freeze
291
+
274
292
  BUILTIN_TYPE_METHOD_SIGNATURES = {
275
293
  'atomic' => {
276
294
  'load' => 'function load() -> T',
@@ -298,6 +316,44 @@ module MilkTea
298
316
  'as_str' => 'static function as_str() -> str',
299
317
  'as_cstr' => 'static function as_cstr() -> cstr',
300
318
  },
319
+ 'vec2' => VEC_METHOD_SIGNATURES,
320
+ 'vec3' => VEC_METHOD_SIGNATURES,
321
+ 'vec4' => VEC_METHOD_SIGNATURES,
322
+ 'ivec2' => VEC_METHOD_SIGNATURES,
323
+ 'ivec3' => VEC_METHOD_SIGNATURES,
324
+ 'ivec4' => VEC_METHOD_SIGNATURES,
325
+ 'mat3' => {
326
+ 'identity' => 'static function identity() -> mat3',
327
+ 'with' => 'function with(...) -> mat3',
328
+ },
329
+ 'mat4' => {
330
+ 'identity' => 'static function identity() -> mat4',
331
+ 'with' => 'function with(...) -> mat4',
332
+ },
333
+ 'quat' => {
334
+ 'identity' => 'static function identity() -> quat',
335
+ 'with' => 'function with(x: float, y: float, z: float, w: float) -> quat',
336
+ },
337
+ 'simd' => {
338
+ 'with' => 'function with(index: int, value: T) -> simd[T, N]',
339
+ 'load' => 'function load() -> T',
340
+ 'store' => 'function store(value: T) -> void',
341
+ },
342
+ 'array' => {
343
+ 'as_span' => 'function as_span() -> span[T]',
344
+ 'len' => 'function len() -> ptr_uint',
345
+ 'with' => 'function with(index: int, value: T) -> array[T, N]',
346
+ },
347
+ 'span' => {
348
+ 'len' => 'function len() -> ptr_uint',
349
+ },
350
+ 'str' => {
351
+ 'len' => 'function len() -> ptr_uint',
352
+ 'slice' => 'function slice(start: ptr_uint, len: ptr_uint) -> str',
353
+ },
354
+ 'ptr' => PTR_METHOD_SIGNATURES,
355
+ 'const_ptr' => PTR_METHOD_SIGNATURES,
356
+ 'own' => PTR_METHOD_SIGNATURES,
301
357
  }.freeze
302
358
 
303
359
  def handle_hover(params)
@@ -466,11 +522,11 @@ module MilkTea
466
522
  docs ||= BUILTIN_TYPE_DOCS[name]
467
523
  elsif (binding = facts.values[name])
468
524
  signature = value_hover_signature(binding)
469
- elsif !BUILTIN_CALL_HOVER_INFO.key?(name) && (member_info = find_member_in_types(facts, name))
525
+ elsif bare_member_name_candidate?(tokens, token_index) && (member_info = find_member_in_types(facts, name))
470
526
  type, member, value = member_info
471
527
  signature = value ? "#{type.name}.#{member} = #{value}" : "#{type.name}.#{member}"
472
528
  resolved_via_type_member = true
473
- elsif !BUILTIN_CALL_HOVER_INFO.key?(name) && (arm_sig = find_variant_arm_in_types(facts, name))
529
+ elsif bare_member_name_candidate?(tokens, token_index) && (arm_sig = find_variant_arm_in_types(facts, name))
474
530
  signature = arm_sig
475
531
  resolved_via_type_member = true
476
532
  elsif (import_binding = facts.imports[name])
@@ -526,7 +582,7 @@ module MilkTea
526
582
  if (method_binding = methods[name])
527
583
  signature = method_signature(method_binding)
528
584
  else
529
- type_base = receiver_type.to_s[/^([A-Za-z_]+)/, 1]
585
+ type_base = receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1]
530
586
  if type_base && (method_sigs = BUILTIN_TYPE_METHOD_SIGNATURES[type_base])
531
587
  signature = method_sigs[name]
532
588
  end
@@ -549,8 +605,14 @@ module MilkTea
549
605
  end
550
606
 
551
607
  unless signature
552
- if token_index && tokens && attribute_target_token?(tokens, token_index)
553
- signature = "attribute target #{name}"
608
+ if token_index && tokens
609
+ if (type_param = type_parameter_hover_signature(tokens, token_index))
610
+ signature = type_param
611
+ elsif (attr_sig = builtin_attribute_hover_signature(tokens, token_index))
612
+ signature = attr_sig
613
+ elsif attribute_target_token?(tokens, token_index)
614
+ signature = "attribute target #{name}"
615
+ end
554
616
  end
555
617
  end
556
618
 
@@ -858,15 +920,20 @@ module MilkTea
858
920
  def completion_function_documentation(uri, name, cache:)
859
921
  key = [uri, name]
860
922
  return cache[key] if cache.key?(key)
923
+ # Server-scoped memo so per-keystroke completion does not re-resolve the
924
+ # definition and re-extract docs for every candidate on every request.
925
+ if @completion_docs_cache.key?(key)
926
+ return cache[key] = @completion_docs_cache[key]
927
+ end
861
928
 
862
929
  definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri)
863
930
  unless definition_entry
864
931
  cache[key] = ''
865
- return ''
932
+ return @completion_docs_cache[key] = ''
866
933
  end
867
934
 
868
935
  doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token])
869
- cache[key] = signature_help_markdown_for_doc_comment(doc_comment)
936
+ @completion_docs_cache[key] = cache[key] = signature_help_markdown_for_doc_comment(doc_comment)
870
937
  end
871
938
 
872
939
  def hover_source_label_for_definition(definition_entry)
@@ -1018,14 +1085,25 @@ module MilkTea
1018
1085
 
1019
1086
  method_receiver_type = project_method_receiver_type_for_completion(current_type)
1020
1087
  method_info = member_method_info_for_receiver_type(facts, method_receiver_type, segment[:name])
1088
+ if method_info.nil?
1089
+ # Native/builtin receivers (vectors, matrices, pointers, arrays,
1090
+ # simd, atomic, ...) have no facts.methods entries; fall back to the
1091
+ # builtin method signature table so member calls resolve accurately
1092
+ # instead of falling through to a bare-name match.
1093
+ base = method_receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1]
1094
+ method_info = base && BUILTIN_TYPE_METHOD_SIGNATURES[base]&.fetch(segment[:name], nil)
1095
+ method_info = { signature: method_info, module_name: nil, binding: nil } if method_info.is_a?(String)
1096
+ end
1021
1097
  return nil unless method_info
1022
1098
 
1023
- source_location = module_member_binding_location(current_uri, method_info[:module_name], segment[:name], method_info[:binding])
1024
- source_location ||= module_member_definition_location(current_uri, method_info[:module_name], segment[:name])
1099
+ source_location = if method_info[:binding]
1100
+ module_member_binding_location(current_uri, method_info[:module_name], segment[:name], method_info[:binding])
1101
+ end
1102
+ source_location ||= (module_member_definition_location(current_uri, method_info[:module_name], segment[:name]) if method_info[:module_name])
1025
1103
  definition_entry = hover_definition_entry_from_location(source_location)
1026
1104
 
1027
1105
  return {
1028
- signature: method_signature(method_info[:binding]),
1106
+ signature: method_info[:signature] || (method_info[:binding] && method_signature(method_info[:binding])),
1029
1107
  docs: hover_doc_comment_for_definition(definition_entry),
1030
1108
  source: hover_source_label_from_location(source_location),
1031
1109
  source_uri: hover_source_uri_from_location(source_location),
@@ -1197,9 +1275,88 @@ module MilkTea
1197
1275
  end
1198
1276
  end
1199
1277
 
1278
+ # Variant arm constructors: `Option[int].some(value = 42)`.
1279
+ if (arm_info = variant_arm_constructor_info(facts, tokens, token_index))
1280
+ field_type = arm_info[:fields][field_name]
1281
+ if field_type
1282
+ return {
1283
+ signature: "#{field_name}: #{field_type}",
1284
+ docs: nil,
1285
+ source: nil,
1286
+ source_uri: nil,
1287
+ source_line: nil,
1288
+ }
1289
+ end
1290
+ end
1291
+
1292
+ # Builtin constructors with named parameters: `span[T](data, len)`.
1293
+ if (builtin_arg = BUILTIN_NAMED_ARGUMENT_SIGNATURES.dig(receiver_name, field_name))
1294
+ return {
1295
+ signature: builtin_arg,
1296
+ docs: nil,
1297
+ source: nil,
1298
+ source_uri: nil,
1299
+ source_line: nil,
1300
+ }
1301
+ end
1302
+
1200
1303
  nil
1201
1304
  end
1202
1305
 
1306
+ BUILTIN_NAMED_ARGUMENT_SIGNATURES = {
1307
+ 'span' => {
1308
+ 'data' => 'data: ptr[T]',
1309
+ 'len' => 'len: ptr_uint',
1310
+ },
1311
+ }.freeze
1312
+
1313
+ def variant_arm_constructor_info(facts, tokens, token_index)
1314
+ opener_index = parameter_list_opener_index(tokens, token_index)
1315
+ return nil unless opener_index
1316
+
1317
+ head_index = previous_non_trivia_token_index(tokens, opener_index)
1318
+ return nil unless head_index && tokens[head_index].type == :identifier
1319
+
1320
+ arm_name = tokens[head_index].lexeme
1321
+
1322
+ # Walk back over `.`, optional generic args `[...]`, to the type name.
1323
+ idx = head_index - 1
1324
+ idx = previous_non_trivia_token_index(tokens, idx) if idx >= 0 && tokens[idx].type == :dot
1325
+ if idx >= 0 && tokens[idx].type == :rbracket
1326
+ lbracket = matching_opener_index(tokens, idx)
1327
+ return nil unless lbracket
1328
+
1329
+ idx = previous_non_trivia_token_index(tokens, lbracket)
1330
+ end
1331
+ return nil unless idx && tokens[idx].type == :identifier
1332
+
1333
+ type_name = tokens[idx].lexeme
1334
+ type = resolve_arm_receiver_type(facts, tokens, idx)
1335
+
1336
+ arm = if type.respond_to?(:arm)
1337
+ type.arm(arm_name)
1338
+ elsif type.respond_to?(:arms)
1339
+ type.arms[arm_name]
1340
+ end
1341
+ return nil unless arm
1342
+
1343
+ { arm_name: arm_name, fields: arm }
1344
+ end
1345
+
1346
+ def resolve_arm_receiver_type(facts, tokens, type_name_index)
1347
+ type_name = tokens[type_name_index].lexeme
1348
+ return facts.types[type_name] if facts.types.key?(type_name)
1349
+
1350
+ dot_index = previous_non_trivia_token_index(tokens, type_name_index)
1351
+ return nil unless dot_index && tokens[dot_index].type == :dot
1352
+
1353
+ module_index = previous_non_trivia_token_index(tokens, dot_index)
1354
+ return nil unless module_index && tokens[module_index].type == :identifier
1355
+
1356
+ module_binding = facts.imports[tokens[module_index].lexeme]
1357
+ module_binding&.types&.fetch(type_name, nil)
1358
+ end
1359
+
1203
1360
  def named_argument_param_result(field_name, param, callable_ast)
1204
1361
  param_ast = callable_ast.params.find { |p| p.name == field_name }
1205
1362
  source_location = param_ast ? ast_name_range(field_name, param_ast.line, param_ast.column) : nil
@@ -1243,7 +1400,15 @@ module MilkTea
1243
1400
 
1244
1401
  head_index = previous_non_trivia_token_index(tokens, opener_index)
1245
1402
  return nil unless head_index
1246
- return nil unless tokens[head_index].type == :identifier
1403
+
1404
+ # Skip generic type arguments before the callee: `span[int](...)`.
1405
+ if tokens[head_index].type == :rbracket
1406
+ lbracket_index = matching_opener_index(tokens, head_index)
1407
+ return nil unless lbracket_index
1408
+
1409
+ head_index = previous_non_trivia_token_index(tokens, lbracket_index)
1410
+ end
1411
+ return nil unless head_index && tokens[head_index].type == :identifier
1247
1412
 
1248
1413
  tokens[head_index].lexeme
1249
1414
  end
@@ -1485,8 +1650,11 @@ module MilkTea
1485
1650
  def attribute_target_token?(tokens, index)
1486
1651
  return false unless index && tokens[index]&.type == :identifier
1487
1652
 
1488
- prev = previous_non_trivia_token_index(tokens, index)
1489
- return false unless prev && [:lbracket, :comma].include?(tokens[prev].type)
1653
+ opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket)
1654
+ return false unless opener
1655
+
1656
+ before_opener = previous_non_trivia_token_index(tokens, opener)
1657
+ return false unless before_opener && tokens[before_opener].type == :attribute
1490
1658
 
1491
1659
  after = next_non_trivia_token_index(tokens, index + 1)
1492
1660
  return false unless after && [:rbracket, :comma].include?(tokens[after].type)
@@ -1494,6 +1662,98 @@ module MilkTea
1494
1662
  true
1495
1663
  end
1496
1664
 
1665
+ BUILTIN_ATTRIBUTE_SIGNATURES = {
1666
+ 'packed' => 'built-in attribute packed',
1667
+ 'deprecated' => 'built-in attribute deprecated(message: str)',
1668
+ 'align' => 'built-in attribute align(bytes: int)',
1669
+ }.freeze
1670
+
1671
+ def builtin_attribute_hover_signature(tokens, token_index)
1672
+ return nil unless token_index && tokens[token_index]&.type == :identifier
1673
+
1674
+ name = tokens[token_index].lexeme
1675
+ signature = BUILTIN_ATTRIBUTE_SIGNATURES[name]
1676
+ return nil unless signature
1677
+
1678
+ prev = previous_non_trivia_token_index(tokens, token_index)
1679
+ return nil unless prev && tokens[prev].type == :lbracket
1680
+
1681
+ at_index = previous_non_trivia_token_index(tokens, prev)
1682
+ return nil unless at_index && tokens[at_index].type == :at
1683
+
1684
+ signature
1685
+ end
1686
+
1687
+ # A bare identifier that could be a flags/enum/variant member name.
1688
+ # Non-builtin names always qualify; builtin-named identifiers (e.g.
1689
+ # `read`) qualify only when not a call/specialization, so a builtin call
1690
+ # keeps resolving to the builtin instead of a member collision.
1691
+ def bare_member_name_candidate?(tokens, token_index)
1692
+ return false unless token_index && tokens[token_index]&.type == :identifier
1693
+
1694
+ name = tokens[token_index].lexeme
1695
+ return true unless BUILTIN_CALL_HOVER_INFO.key?(name)
1696
+
1697
+ nxt = next_non_trivia_token_index(tokens, token_index + 1)
1698
+ nxt.nil? || ![:lparen, :lbracket].include?(tokens[nxt].type)
1699
+ end
1700
+
1701
+ # Hover content for an identifier sitting in a `[...]` type-parameter or
1702
+ # type-argument position: `struct Pair[A, B]:`, `ref[T]`, `Result[T, E]`,
1703
+ # and generic value parameters `[N: int]`. Returns nil when the brackets
1704
+ # belong to something else (attribute targets, array indices).
1705
+ def type_parameter_hover_signature(tokens, index)
1706
+ return nil unless index && tokens[index]&.type == :identifier
1707
+
1708
+ opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket)
1709
+ return nil unless opener
1710
+
1711
+ before_opener = previous_non_trivia_token_index(tokens, opener)
1712
+ return nil if before_opener && tokens[before_opener].type == :attribute
1713
+
1714
+ token = tokens[index]
1715
+ next_index = next_non_trivia_token_index(tokens, index + 1)
1716
+ prev_index = previous_non_trivia_token_index(tokens, index)
1717
+
1718
+ if next_index && tokens[next_index].type == :colon
1719
+ # Generic value parameter declaration: `[N: int]`.
1720
+ value_type = next_non_trivia_token_index(tokens, next_index + 1)
1721
+ value_type_text = value_type ? tokens[value_type].lexeme : nil
1722
+ type_text = value_type_text ? ": #{value_type_text}" : ""
1723
+ return "generic value parameter #{token.lexeme}#{type_text}"
1724
+ end
1725
+
1726
+ if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) &&
1727
+ next_index && tokens[next_index].type == :implements
1728
+ # Constrained declaration: `[T implements Damageable]`.
1729
+ return "type parameter #{token.lexeme}"
1730
+ end
1731
+
1732
+ if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) &&
1733
+ next_index && [:rbracket, :comma].include?(tokens[next_index].type)
1734
+ return "type parameter #{token.lexeme}"
1735
+ end
1736
+
1737
+ nil
1738
+ end
1739
+
1740
+ def enclosing_bracket_opener_index(tokens, index, opener_type, closer_type)
1741
+ depth = 0
1742
+ i = index
1743
+ while i >= 0
1744
+ tok = tokens[i]
1745
+ if tok.type == closer_type
1746
+ depth += 1
1747
+ elsif tok.type == opener_type
1748
+ return i if depth.zero?
1749
+
1750
+ depth -= 1
1751
+ end
1752
+ i -= 1
1753
+ end
1754
+ nil
1755
+ end
1756
+
1497
1757
  def call_argument_token?(tokens, index)
1498
1758
  return false unless index && tokens[index]&.type == :identifier
1499
1759
 
@@ -1777,9 +2037,15 @@ module MilkTea
1777
2037
  end
1778
2038
 
1779
2039
  def enclosing_completion_frame(facts, line)
2040
+ # Memoized for the duration of one semantic-token build; completion and
2041
+ # hover only resolve a handful of identifiers per request so they
2042
+ # repopulate the (nil) cache harmlessly.
2043
+ memo_key = [:frame, facts.object_id, line]
2044
+ return semantic_build_memo(memo_key) if semantic_build_memo?(memo_key)
2045
+
1780
2046
  frames = Array(facts.local_completion_frames)
1781
2047
  containing = frames.select { |frame| frame.start_line && frame.end_line && frame.start_line <= line && line <= frame.end_line }
1782
- containing.min_by { |frame| frame.end_line - frame.start_line }
2048
+ store_semantic_build_memo(memo_key, containing.min_by { |frame| frame.end_line - frame.start_line })
1783
2049
  end
1784
2050
 
1785
2051
  def latest_completion_snapshot(frame, line, char)
@@ -1823,40 +2089,54 @@ module MilkTea
1823
2089
  def for_binding_context_at(tokens, token_index)
1824
2090
  return nil unless token_index
1825
2091
 
1826
- i = token_index - 1
1827
- passed_in = false
2092
+ tok = tokens[token_index]
2093
+ return nil unless tok&.type == :identifier
1828
2094
 
2095
+ # Find the `for` keyword on the same line at or before the binding.
2096
+ for_index = nil
2097
+ i = token_index
1829
2098
  while i >= 0
1830
2099
  current = tokens[i]
1831
- break if current.type == :newline && current.line != tokens[token_index].line
1832
-
1833
- if current.type == :identifier
1834
- case current.lexeme
1835
- when "in"
1836
- passed_in = true
1837
- when "for"
1838
- if passed_in
1839
- ie = token_index + 1
1840
- while ie < tokens.length
1841
- nt = tokens[ie]
1842
- break if nt.type == :newline || nt.type == :colon
1843
-
1844
- ie += 1
1845
- end
1846
- iterable_text = tokens[(token_index + 1)...ie].map(&:lexeme).join(" ")
1847
- iterable_text = iterable_text.gsub(/\s+/, " ").strip
1848
- return {
1849
- name: tokens[token_index].lexeme,
1850
- iterable_text: iterable_text.empty? ? nil : iterable_text,
1851
- }
1852
- end
1853
- end
2100
+ break if current.type == :newline && current.line != tok.line
2101
+ if current.type == :for && current.line == tok.line
2102
+ for_index = i
2103
+ break
1854
2104
  end
1855
-
1856
2105
  i -= 1
1857
2106
  end
2107
+ return nil unless for_index
2108
+
2109
+ # Find `in` after the bindings, still on the same line.
2110
+ in_index = nil
2111
+ j = for_index + 1
2112
+ while j < tokens.length
2113
+ t = tokens[j]
2114
+ break if t.type == :newline && t.line != tok.line
2115
+ if t.type == :in && t.line == tok.line
2116
+ in_index = j
2117
+ break
2118
+ end
2119
+ j += 1
2120
+ end
2121
+ return nil unless in_index
1858
2122
 
1859
- nil
2123
+ # The hovered token must be one of the bindings between `for` and `in`.
2124
+ return nil unless token_index > for_index && token_index < in_index
2125
+
2126
+ ie = in_index + 1
2127
+ while ie < tokens.length
2128
+ nt = tokens[ie]
2129
+ break if nt.type == :newline || nt.type == :colon
2130
+
2131
+ ie += 1
2132
+ end
2133
+ iterable_text = tokens[(in_index + 1)...ie].map(&:lexeme).join(" ")
2134
+ iterable_text = iterable_text.gsub(/\s+/, " ").strip
2135
+
2136
+ {
2137
+ name: tok.lexeme,
2138
+ iterable_text: iterable_text.empty? ? nil : iterable_text,
2139
+ }
1860
2140
  end
1861
2141
 
1862
2142
  def resolve_lexical_local_hover_signature(definition_uri, name, definition_token)
@@ -23,6 +23,12 @@ module MilkTea
23
23
  @semantic_tokens_cache.clear
24
24
  @semantic_tokens_delta_cache.clear
25
25
  @fixall_cache.clear
26
+ @completion_session_cache.clear
27
+ @completion_session_order.clear
28
+ @completion_session_order_set.clear
29
+ @completion_docs_cache.clear
30
+ @completion_resolve_cache.clear
31
+ @document_symbol_cache.clear
26
32
  @definition_file_token_cache.clear
27
33
  @definition_file_ast_cache.clear
28
34
  MilkTea::Types::Registry.reset!
@@ -138,6 +144,7 @@ module MilkTea
138
144
  @_indexing_thread = Thread.new do
139
145
  progress = create_progress(title: 'Milk Tea LSP: Indexing workspace', message: 'Scanning source files...')
140
146
  @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
147
+ @workspace.refresh_module_index_for_workspace
141
148
  document_count = @workspace.all_documents.length
142
149
  progress.done(message: "#{document_count} document#{document_count == 1 ? '' : 's'} indexed")
143
150
  log_message(:info, "Milk Tea LSP ready — #{document_count} document#{document_count == 1 ? '' : 's'} indexed")
@@ -182,6 +189,7 @@ module MilkTea
182
189
  Thread.new do
183
190
  progress = create_progress(title: 'Milk Tea LSP: Reindexing workspace', message: 'Scanning source files...')
184
191
  @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
192
+ @workspace.refresh_module_index_for_workspace
185
193
  doc_count = @workspace.all_documents.length
186
194
  progress.done(message: "#{doc_count} document#{doc_count == 1 ? '' : 's'} indexed")
187
195
 
@@ -200,6 +208,10 @@ module MilkTea
200
208
  next unless old_uri && new_uri
201
209
 
202
210
  @workspace.rename_indexed_file(old_uri, new_uri)
211
+ @workspace.apply_module_index_events(
212
+ [{ 'uri' => old_uri, 'type' => 3 }, { 'uri' => new_uri, 'type' => 1 }],
213
+ skip_open: false,
214
+ )
203
215
  end
204
216
  nil
205
217
  end
@@ -168,7 +168,9 @@ module MilkTea
168
168
  end
169
169
 
170
170
  def module_level_reference_locations(uri, name, facts, include_declaration:, cross_file_allowed: true)
171
- ast = @workspace.get_ast(uri)
171
+ # Use the facts' own AST so binding_resolution node object_ids line up;
172
+ # a separately-parsed AST would miss every binding lookup.
173
+ ast = facts&.ast || @workspace.get_ast(uri)
172
174
  return [] unless ast
173
175
 
174
176
  results = []