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.
- checksums.yaml +4 -4
- data/docs/lsp-performance.md +347 -0
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/c_backend/reinterpret.rb +2 -2
- data/lib/milk_tea/core/c_backend.rb +12 -0
- data/lib/milk_tea/core/compile_time.rb +46 -17
- data/lib/milk_tea/core/lexer.rb +12 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2 -3
- data/lib/milk_tea/core/lowering.rb +12 -0
- data/lib/milk_tea/core/module_loader.rb +59 -37
- data/lib/milk_tea/core/parser.rb +12 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +2 -1
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +8 -20
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +2 -1
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +10 -10
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +9 -11
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +14 -5
- data/lib/milk_tea/core/semantic_analyzer.rb +6 -31
- data/lib/milk_tea/lsp/diagnostics.rb +15 -5
- data/lib/milk_tea/lsp/server/code_actions.rb +12 -2
- data/lib/milk_tea/lsp/server/completion.rb +100 -77
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +4 -6
- data/lib/milk_tea/lsp/server/formatting.rb +184 -60
- data/lib/milk_tea/lsp/server/hover.rb +321 -41
- data/lib/milk_tea/lsp/server/inlay_hints.rb +33 -11
- data/lib/milk_tea/lsp/server/lifecycle.rb +12 -0
- data/lib/milk_tea/lsp/server/references.rb +3 -1
- data/lib/milk_tea/lsp/server/selection_range.rb +4 -4
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +63 -20
- data/lib/milk_tea/lsp/server/text_documents.rb +2 -2
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +7 -7
- data/lib/milk_tea/lsp/server/utilities.rb +4 -0
- data/lib/milk_tea/lsp/server.rb +9 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +14 -3
- data/lib/milk_tea/lsp/workspace/caches.rb +17 -1
- data/lib/milk_tea/lsp/workspace/collection.rb +4 -0
- data/lib/milk_tea/lsp/workspace/module_index.rb +134 -0
- data/lib/milk_tea/lsp/workspace/store.rb +9 -4
- data/lib/milk_tea/lsp/workspace.rb +8 -0
- data/lib/milk_tea/packages/manifest.rb +9 -0
- 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
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|