mt-lang 0.3.37 → 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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/docs/lsp-performance.md +347 -0
  3. data/lib/milk_tea/base.rb +1 -1
  4. data/lib/milk_tea/core/c_backend/reinterpret.rb +2 -2
  5. data/lib/milk_tea/core/c_backend.rb +12 -0
  6. data/lib/milk_tea/core/compile_time.rb +46 -17
  7. data/lib/milk_tea/core/lexer.rb +12 -0
  8. data/lib/milk_tea/core/lowering/resolve.rb +2 -3
  9. data/lib/milk_tea/core/lowering.rb +12 -0
  10. data/lib/milk_tea/core/module_loader.rb +59 -37
  11. data/lib/milk_tea/core/parser.rb +12 -0
  12. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +2 -1
  13. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +8 -20
  14. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +2 -1
  15. data/lib/milk_tea/core/semantic_analyzer/statements.rb +10 -10
  16. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +9 -11
  17. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +14 -5
  18. data/lib/milk_tea/core/semantic_analyzer.rb +6 -31
  19. data/lib/milk_tea/lsp/diagnostics.rb +15 -5
  20. data/lib/milk_tea/lsp/server/code_actions.rb +12 -2
  21. data/lib/milk_tea/lsp/server/completion.rb +100 -77
  22. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +4 -6
  23. data/lib/milk_tea/lsp/server/formatting.rb +184 -60
  24. data/lib/milk_tea/lsp/server/hover.rb +321 -41
  25. data/lib/milk_tea/lsp/server/inlay_hints.rb +33 -11
  26. data/lib/milk_tea/lsp/server/lifecycle.rb +12 -0
  27. data/lib/milk_tea/lsp/server/references.rb +3 -1
  28. data/lib/milk_tea/lsp/server/selection_range.rb +4 -4
  29. data/lib/milk_tea/lsp/server/semantic_tokens.rb +63 -20
  30. data/lib/milk_tea/lsp/server/text_documents.rb +2 -2
  31. data/lib/milk_tea/lsp/server/type_hierarchy.rb +7 -7
  32. data/lib/milk_tea/lsp/server/utilities.rb +4 -0
  33. data/lib/milk_tea/lsp/server.rb +9 -0
  34. data/lib/milk_tea/lsp/workspace/analysis.rb +14 -3
  35. data/lib/milk_tea/lsp/workspace/caches.rb +17 -1
  36. data/lib/milk_tea/lsp/workspace/collection.rb +4 -0
  37. data/lib/milk_tea/lsp/workspace/module_index.rb +134 -0
  38. data/lib/milk_tea/lsp/workspace/store.rb +9 -4
  39. data/lib/milk_tea/lsp/workspace.rb +8 -0
  40. data/lib/milk_tea/packages/manifest.rb +9 -0
  41. metadata +5 -3
@@ -10,18 +10,29 @@ module MilkTea
10
10
  stages = new_perf_stages
11
11
  total_start = stages ? monotonic_time : nil
12
12
  uri = params['textDocument']['uri']
13
+
14
+ content_hash = @workspace.get_content(uri).hash
15
+ facts = @workspace.get_facts(uri)
16
+ facts_id = facts&.object_id
17
+ cached = @document_symbol_cache[uri]
18
+ if cached && cached[:content_hash] == content_hash && cached[:facts_id] == facts_id
19
+ return cached[:result]
20
+ end
21
+
13
22
  symbols = measure_perf_stage(stages, 'symbols') { @workspace.get_symbols(uri) }
14
23
  result = measure_perf_stage(stages, 'format') { symbols.map { |sym| format_document_symbol(sym) } }
15
24
 
16
- # Enrich with hierarchical children from AST
17
- ast = @workspace.get_ast(uri)
25
+ # Enrich with hierarchical children from AST. Use the facts' own AST so
26
+ # binding_resolution node object_ids line up with the outline locals.
27
+ ast = facts&.ast || @workspace.get_ast(uri)
18
28
  if ast && result
19
- enrich_with_children(result, ast)
29
+ enrich_with_children(result, ast, facts)
20
30
  end
21
31
 
22
32
  module_name = resolve_outline_module_name(uri)
23
33
  result = wrap_in_module_hierarchy(result, module_name, uri) if module_name && result&.any?
24
34
 
35
+ @document_symbol_cache[uri] = { content_hash: content_hash, facts_id: facts_id, result: result }
25
36
  result
26
37
  rescue StandardError => e
27
38
  warn "Error in documentSymbol handler: #{e.message}"
@@ -74,17 +85,42 @@ module MilkTea
74
85
  children
75
86
  end
76
87
 
77
- def enrich_with_children(symbols, ast)
88
+ def enrich_with_children(symbols, ast, facts)
78
89
  removed_local_names = []
79
90
  removed_method_names = []
80
91
  removed_nested_type_names = []
81
92
  removed_event_names = []
82
93
  name_index = symbols.each_with_object(Hash.new { |h, k| h[k] = [] }) { |s, h| h[s[:name]] << s }
83
94
 
84
- ast.declarations&.each do |decl|
95
+ flatten_module_declarations(ast.declarations).each do |decl|
85
96
  removed_nested_type_names.concat(collect_nested_type_names(decl)) if decl.is_a?(AST::StructDecl)
86
97
 
87
98
  case decl
99
+ when AST::VarDecl
100
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
101
+ next unless parent
102
+
103
+ detail = decl.type ? type_detail_string(decl.type) : nil
104
+ detail ||= resolved_local_type_detail(decl, facts)
105
+ parent[:detail] = detail if detail
106
+
107
+ when AST::TypeAliasDecl
108
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
109
+ next unless parent
110
+
111
+ if (detail = type_detail_string(decl.target))
112
+ parent[:detail] = "= #{detail}"
113
+ end
114
+
115
+ when AST::ExternFunctionDecl, AST::ForeignFunctionDecl
116
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
117
+ next unless parent
118
+
119
+ detail_parts = []
120
+ detail_parts << 'async' if decl.respond_to?(:async) && decl.async
121
+ detail_parts << "-> #{type_detail_string(decl.return_type) || 'void'}"
122
+ parent[:detail] = detail_parts.join(' ')
123
+
88
124
  when AST::EventDecl
89
125
  parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
90
126
  next unless parent
@@ -100,26 +136,11 @@ module MilkTea
100
136
  detail_parts = []
101
137
  detail_parts << 'const' if decl.respond_to?(:const) && decl.const
102
138
  detail_parts << 'async' if decl.respond_to?(:async) && decl.async
103
- if (ret = type_detail_string(decl.return_type))
104
- detail_parts << "-> #{ret}"
105
- end
106
- parent[:detail] = detail_parts.join(' ') unless detail_parts.empty?
139
+ detail_parts << "-> #{type_detail_string(decl.return_type) || 'void'}"
140
+ parent[:detail] = detail_parts.join(' ')
107
141
 
108
142
  locals = collect_local_decls(decl.body)
109
- next unless locals&.any?
110
-
111
- parent[:children] ||= []
112
- parent_children = parent[:children]
113
- locals.each do |local|
114
- next unless local.name
115
-
116
- child = local_decl_symbol(local)
117
- next unless child
118
-
119
- parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
120
- removed_local_names << local.name
121
- removed_local_names.concat(descendant_names(child))
122
- end
143
+ append_local_children(parent, locals, facts, removed_local_names)
123
144
  when AST::ExtendingBlock
124
145
  type_name_str = decl.type_name.name.parts.join('.')
125
146
 
@@ -153,20 +174,7 @@ module MilkTea
153
174
  removed_method_names << child[:name] if child[:kind] == 6
154
175
 
155
176
  locals = collect_local_decls(method.respond_to?(:body) ? method.body : nil)
156
- next unless locals&.any?
157
-
158
- child[:children] ||= []
159
- child_children = child[:children]
160
- locals.each do |local|
161
- next unless local.name
162
-
163
- local_child = local_decl_symbol(local)
164
- next unless local_child
165
-
166
- child_children << local_child unless child_children.any? { |pc| pc[:name] == local_child[:name] }
167
- removed_local_names << local.name
168
- removed_local_names.concat(descendant_names(local_child))
169
- end
177
+ append_local_children(child, locals, facts, removed_local_names)
170
178
  end
171
179
  when AST::ConstDecl
172
180
  parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
@@ -179,25 +187,13 @@ module MilkTea
179
187
  next unless decl.block_body
180
188
 
181
189
  locals = collect_local_decls(decl.block_body)
182
- next unless locals&.any?
183
-
184
- parent[:children] ||= []
185
- parent_children = parent[:children]
186
- locals.each do |local|
187
- next unless local.name
188
-
189
- child = local_decl_symbol(local)
190
- next unless child
191
-
192
- parent_children << child unless parent_children.any? { |pc| pc[:name] == child[:name] }
193
- removed_local_names << local.name
194
- removed_local_names.concat(descendant_names(child))
195
- end
190
+ append_local_children(parent, locals, facts, removed_local_names)
196
191
  else
197
192
  parent_name = child_parent_name(decl)
198
193
  parent = parent_name ? name_index[parent_name]&.find { |s| symbol_line(s) == (decl.line || 0) } : nil
199
194
  next unless parent
200
195
 
196
+ detail_parts = []
201
197
  if decl.is_a?(AST::StructDecl) && decl.implements&.any?
202
198
  ifaces = decl.implements.map { |i|
203
199
  base = i.respond_to?(:parts) ? i.parts.join('.') : i.name.parts.join('.')
@@ -210,9 +206,19 @@ module MilkTea
210
206
  end
211
207
  "#{base}#{args}"
212
208
  }.join(', ')
213
- parent[:detail] = "(#{ifaces})"
209
+ detail_parts << "(#{ifaces})"
210
+ end
211
+
212
+ if decl.respond_to?(:backing_type) && decl.backing_type && (bt = type_detail_string(decl.backing_type))
213
+ detail_parts << ": #{bt}"
214
214
  end
215
215
 
216
+ if (generic = generic_signature_detail(decl))
217
+ detail_parts << generic
218
+ end
219
+
220
+ parent[:detail] = detail_parts.join(' ') unless detail_parts.empty?
221
+
216
222
  type_params = expand_generic_type_params(decl)
217
223
  if type_params&.any?
218
224
  parent[:children] ||= []
@@ -374,14 +380,78 @@ module MilkTea
374
380
  line = (a.line) ? a.line : default_line
375
381
  return nil unless a.respond_to?(:name) && a.name && line
376
382
  col = a.column ? a.column : 1
383
+
384
+ detail = nil
385
+ if a.respond_to?(:fields) && a.fields&.any?
386
+ fields = a.fields.map { |f| "#{f.name}: #{type_detail_string(f.type)}" }.join(', ')
387
+ detail = "(#{fields})"
388
+ end
389
+
377
390
  {
378
391
  name: a.name, kind: 22,
392
+ detail: detail,
379
393
  range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
380
394
  selectionRange: {
381
395
  start: { line: line - 1, character: col - 1 },
382
396
  end: { line: line - 1, character: col - 1 + a.name.length },
383
397
  },
384
- }
398
+ }.compact
399
+ end
400
+
401
+ # Renders the generic parameter clause of a type declaration, e.g.
402
+ # `[A, B]` for `struct Pair[A, B]` or `[@a]` for `struct Buffer[@a]`.
403
+ def generic_signature_detail(decl)
404
+ parts = []
405
+ if decl.respond_to?(:lifetime_params) && decl.lifetime_params&.any?
406
+ parts.concat(decl.lifetime_params.map(&:to_s))
407
+ end
408
+ if decl.respond_to?(:type_params) && decl.type_params&.any?
409
+ parts.concat(decl.type_params.map { |tp| tp.respond_to?(:name) ? tp.name : tp.to_s })
410
+ end
411
+ parts.empty? ? nil : "[#{parts.join(', ')}]"
412
+ end
413
+
414
+ # Module-level `when` branches are compile-time conditionals; the token
415
+ # symbol scan lists their declarations, so flatten the branch bodies so
416
+ # the enrichment below can type them like ordinary top-level decls.
417
+ def flatten_module_declarations(declarations)
418
+ (declarations || []).flat_map do |decl|
419
+ if decl.is_a?(AST::WhenStmt)
420
+ (decl.branches || []).flat_map { |b| flatten_module_declarations(b.body) } +
421
+ flatten_module_declarations(decl.else_body)
422
+ else
423
+ [decl]
424
+ end
425
+ end
426
+ end
427
+
428
+ # Adds local declaration children to an outline symbol. Destructure
429
+ # locals (`let Vec2(x, y) = ...`) introduce a spurious flat variable
430
+ # symbol named after the destructure type; that symbol is collected for
431
+ # removal instead of being shown as a typed child.
432
+ def append_local_children(container, locals, facts, removed_local_names)
433
+ return unless locals&.any?
434
+
435
+ container[:children] ||= []
436
+ children = container[:children]
437
+ locals.each do |local|
438
+ if local.respond_to?(:destructure_bindings) && local.destructure_bindings
439
+ type_name = local.destructure_type_name
440
+ if type_name
441
+ name = type_name.is_a?(Array) ? type_name.join('.') : type_name.to_s
442
+ removed_local_names << name unless removed_local_names.include?(name)
443
+ end
444
+ next
445
+ end
446
+ next unless local.name
447
+
448
+ child = local_decl_symbol(local, facts:)
449
+ next unless child
450
+
451
+ children << child unless children.any? { |pc| pc[:name] == child[:name] }
452
+ removed_local_names << local.name
453
+ removed_local_names.concat(descendant_names(child))
454
+ end
385
455
  end
386
456
 
387
457
  def collect_local_decls(body)
@@ -417,7 +487,7 @@ module MilkTea
417
487
  end
418
488
  end
419
489
 
420
- def local_decl_symbol(decl)
490
+ def local_decl_symbol(decl, facts: nil)
421
491
  return nil unless decl.name
422
492
  return nil if decl.name == '_'
423
493
 
@@ -429,8 +499,9 @@ module MilkTea
429
499
  if decl.respond_to?(:value) && decl.value.is_a?(AST::ProcExpr)
430
500
  detail ||= proc_signature_detail(decl.value)
431
501
  proc_locals = collect_local_decls(decl.value.body)
432
- children = proc_locals.filter_map { |l| local_decl_symbol(l) }
502
+ children = proc_locals.filter_map { |l| local_decl_symbol(l, facts:) }
433
503
  end
504
+ detail ||= resolved_local_type_detail(decl, facts)
434
505
 
435
506
  {
436
507
  name: decl.name, kind: 13,
@@ -441,6 +512,60 @@ module MilkTea
441
512
  }.compact
442
513
  end
443
514
 
515
+ # Resolves the declared type of an inferred local from semantic facts.
516
+ # Prefers the sema binding type (which reflects let/var ... else: and
517
+ # other flow refinement), falling back to the initializer expression
518
+ # type recorded during checking. Returns nil when facts are unavailable
519
+ # or the binding is missing.
520
+ def resolved_local_type_detail(decl, facts)
521
+ return nil unless facts
522
+ return nil unless facts.respond_to?(:binding_resolution) && facts.binding_resolution
523
+
524
+ binding_id = facts.binding_resolution.declaration_binding_ids[decl.object_id]
525
+ if binding_id
526
+ type = facts.binding_resolution.binding_types[binding_id]
527
+ return nil if type.is_a?(Types::Error)
528
+ return short_type_detail(type) if type
529
+ end
530
+
531
+ return nil unless decl.respond_to?(:value) && decl.value
532
+ return nil unless facts.respond_to?(:resolved_expr_types)
533
+
534
+ node_id = facts.respond_to?(:ast) && facts.ast ? facts.ast.node_ids[decl.value.object_id] : nil
535
+ return nil unless node_id
536
+
537
+ type = facts.resolved_expr_types[node_id]
538
+ return nil if type.is_a?(Types::Error)
539
+
540
+ short_type_detail(type)
541
+ end
542
+
543
+ # Renders a resolved semantic type for outline display without module
544
+ # qualifiers (e.g. std.deque.Deque[int] as Deque[int]). Falls back to
545
+ # the canonical #to_s when a type has no usable short form.
546
+ def short_type_detail(type)
547
+ return nil unless type
548
+
549
+ case type
550
+ when Types::Nullable
551
+ "#{short_type_detail(type.base)}?"
552
+ when Types::Span
553
+ "span[#{short_type_detail(type.element_type)}]"
554
+ when Types::SoA
555
+ "SoA[#{short_type_detail(type.element_type)}, #{type.count}]"
556
+ when Types::StructInstance, Types::VariantInstance, Types::GenericInstance
557
+ args = type.arguments.map { |arg| short_type_arg(arg) }.join(', ')
558
+ args.empty? ? type.name : "#{type.name}[#{args}]"
559
+ else
560
+ name = type.respond_to?(:name) ? type.name.to_s : ''
561
+ name.empty? ? type.to_s : name
562
+ end
563
+ end
564
+
565
+ def short_type_arg(arg)
566
+ arg.is_a?(Types::LiteralTypeArg) ? arg.value.to_s : short_type_detail(arg)
567
+ end
568
+
444
569
  def descendant_names(symbol)
445
570
  return [] unless symbol[:children]
446
571
 
@@ -487,9 +612,7 @@ module MilkTea
487
612
  detail_parts << 'mut' if m.respond_to?(:kind) && m.kind == :editable_function
488
613
  detail_parts << 'static' if m.respond_to?(:kind) && m.kind == :static_function
489
614
  detail_parts << 'async' if m.respond_to?(:async) && m.async
490
- if (ret = type_detail_string(m.return_type))
491
- detail_parts << "-> #{ret}"
492
- end
615
+ detail_parts << "-> #{type_detail_string(m.return_type) || 'void'}"
493
616
  {
494
617
  name: m.name, kind: 6,
495
618
  range: { start: { line: m.line - 1, character: 0 }, end: { line: (m.respond_to?(:end_line) && m.end_line ? m.end_line : m.line), character: 0 } },
@@ -507,10 +630,11 @@ module MilkTea
507
630
  case type
508
631
  when AST::TypeRef
509
632
  type.to_s
510
- when AST::ProcType
633
+ when AST::FunctionType, AST::ProcType
511
634
  params = (type.params || []).map { |p| type_detail_string(p.type) }.join(', ')
512
635
  ret = type_detail_string(type.return_type) || 'void'
513
- "proc(#{params}) -> #{ret}"
636
+ keyword = type.is_a?(AST::FunctionType) ? 'fn' : 'proc'
637
+ "#{keyword}(#{params}) -> #{ret}"
514
638
  when AST::TupleType
515
639
  base = "(#{(type.element_types || []).map { |t| type_detail_string(t) }.join(', ')})"
516
640
  type.nullable ? "#{base}?" : base