mt-lang 0.3.37 → 0.3.38

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: 84f8e5a5913811a35cd5cd470c43f3827aee62bfe5b151add730d45fe341bee2
4
- data.tar.gz: fd76e0d6f88abfe0fda004829430effc5b7030957754b6d3d3eebbd6739b96b5
3
+ metadata.gz: 952280dd2ad14bc3b5751fc7e8a3d29b39363293b1436cc1b3b546c31c938427
4
+ data.tar.gz: e0bdf3468681b07914143c32b0b9cc2470cdbeaf55cf81994186003dc406261b
5
5
  SHA512:
6
- metadata.gz: cc533975db0511ab0f5ac62887ec101ebae699841d3cdbc5079faf1e2a0be877e1ceb71d56b3605ca7c37c60c43019fd3288e690325a21cf87806ca2cff138b5
7
- data.tar.gz: 89d0efc35e9bfec7c1d9c5a612e2f136d3c6fd8f80259f88a657ac46be896ff79c43c0810f98eba8312139151c343c59205b2a3b5fd72af60d2de2fdfc0fd16a
6
+ metadata.gz: 9dcf6f6c955f1428b5374315782fda121669ae3c12a8c84d683205cb44bb4ca7903465e70447860e89c0753695f01af513903e93b82987838fa724be19fc7b2f
7
+ data.tar.gz: 67c259de17bdd81a4f5785d7dbe05cf39ab959ea51c1f27ed2b05a516ed97ce91a4b2291aa415e0abaa695337f1fc1825ead4acdfceb480a9be4db3cd3a24dd4
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.37"
6
+ VERSION = "0.3.38"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -116,13 +116,13 @@ module MilkTea
116
116
  return false if expression.expression.type.is_a?(Types::Null)
117
117
 
118
118
  c_type(expression.target_type) == c_type(expression.expression.type)
119
- rescue StandardError
119
+ rescue CBackendError
120
120
  false
121
121
  end
122
122
 
123
123
  def identity_reinterpret?(target_type, source_type)
124
124
  c_type(target_type) == c_type(source_type)
125
- rescue StandardError
125
+ rescue CBackendError
126
126
  false
127
127
  end
128
128
 
@@ -27,6 +27,18 @@ module MilkTea
27
27
  def code
28
28
  "backend/internal"
29
29
  end
30
+
31
+ def to_diagnostic(path: nil)
32
+ Diagnostic.new(
33
+ path: @path || path,
34
+ line: @line,
35
+ column: @column,
36
+ length: nil,
37
+ code: code,
38
+ message: message,
39
+ severity: :error,
40
+ )
41
+ end
30
42
  end
31
43
 
32
44
  class CBackend
@@ -6,16 +6,27 @@ module MilkTea
6
6
  module CompileTime
7
7
  Layout = ::MilkTea::Types::Layout
8
8
 
9
- class ReturnValue < StandardError
10
- attr_reader :value
9
+ # Carries the value of a `return` statement out of the block evaluator as
10
+ # an ordinary value instead of an exception; callers unwrap it when present.
11
+ ReturnOutcome = Data.define(:value)
11
12
 
12
- def initialize(value)
13
- @value = value
14
- super("return #{value.inspect}")
13
+ class Error < StandardError
14
+ def code
15
+ "compile_time/error"
15
16
  end
16
- end
17
17
 
18
- class Error < StandardError; end
18
+ def to_diagnostic(path: nil)
19
+ Diagnostic.new(
20
+ path: path,
21
+ line: nil,
22
+ column: nil,
23
+ length: nil,
24
+ code: code,
25
+ message: message,
26
+ severity: :error,
27
+ )
28
+ end
29
+ end
19
30
 
20
31
  def self.evaluate(expression, resolve_identifier:, resolve_member_access:, resolve_type_ref: nil, resolve_call: nil)
21
32
  Evaluator.new(
@@ -249,7 +260,10 @@ module MilkTea
249
260
  result = nil
250
261
 
251
262
  statements.each do |statement|
252
- result = evaluate_statement(statement, scopes:)
263
+ outcome = evaluate_statement(statement, scopes:)
264
+ return outcome if outcome.is_a?(ReturnOutcome)
265
+
266
+ result = outcome
253
267
  end
254
268
 
255
269
  result
@@ -261,7 +275,7 @@ module MilkTea
261
275
  evaluate_local_decl(statement, scopes:)
262
276
  when AST::ReturnStmt
263
277
  value = statement.value ? evaluate_expression(statement.value, scopes:) : nil
264
- raise ReturnValue.new(value)
278
+ ReturnOutcome.new(value)
265
279
  when AST::WhileStmt
266
280
  evaluate_while(statement, scopes:)
267
281
  when AST::ForStmt
@@ -276,6 +290,7 @@ module MilkTea
276
290
  evaluate_expression(statement.expression, scopes:)
277
291
  when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
278
292
  # no-op at compile time
293
+ nil
279
294
  when AST::EmitStmt
280
295
  # emitted declarations are collected during lowering
281
296
  nil
@@ -354,7 +369,10 @@ module MilkTea
354
369
  break unless condition
355
370
  break unless CompileTime.boolean_value?(condition)
356
371
 
357
- statement.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
372
+ statement.body.each do |body_stmt|
373
+ outcome = evaluate_statement(body_stmt, scopes:)
374
+ return outcome if outcome.is_a?(ReturnOutcome)
375
+ end
358
376
  iterations += 1
359
377
  end
360
378
 
@@ -372,7 +390,10 @@ module MilkTea
372
390
 
373
391
  iterable.each do |element|
374
392
  @variables[loop_var_name] = element
375
- statement.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
393
+ statement.body.each do |body_stmt|
394
+ outcome = evaluate_statement(body_stmt, scopes:)
395
+ return outcome if outcome.is_a?(ReturnOutcome)
396
+ end
376
397
  end
377
398
 
378
399
  result
@@ -382,13 +403,19 @@ module MilkTea
382
403
  statement.branches.each do |branch|
383
404
  condition = evaluate_expression(branch.condition, scopes:)
384
405
  if CompileTime.boolean_value?(condition) && condition
385
- branch.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
406
+ branch.body.each do |body_stmt|
407
+ outcome = evaluate_statement(body_stmt, scopes:)
408
+ return outcome if outcome.is_a?(ReturnOutcome)
409
+ end
386
410
  return condition
387
411
  end
388
412
  end
389
413
 
390
414
  if statement.else_body
391
- statement.else_body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
415
+ statement.else_body.each do |body_stmt|
416
+ outcome = evaluate_statement(body_stmt, scopes:)
417
+ return outcome if outcome.is_a?(ReturnOutcome)
418
+ end
392
419
  end
393
420
 
394
421
  nil
@@ -401,7 +428,10 @@ module MilkTea
401
428
  statement.arms.each do |arm|
402
429
  wildcard = arm.pattern.is_a?(AST::Identifier) && arm.pattern.name == "_"
403
430
  if wildcard || CompileTime.equality_result(scrutinee, evaluate_expression(arm.pattern, scopes:)) == true
404
- arm.body.each { |body_stmt| evaluate_statement(body_stmt, scopes:) }
431
+ arm.body.each do |body_stmt|
432
+ outcome = evaluate_statement(body_stmt, scopes:)
433
+ return outcome if outcome.is_a?(ReturnOutcome)
434
+ end
405
435
  return scrutinee
406
436
  end
407
437
  end
@@ -451,9 +481,8 @@ module MilkTea
451
481
  initial_vars[param.name] = arg_value
452
482
  end
453
483
  ctx = BlockContext.new(@checker, initial_variables: initial_vars)
454
- ctx.evaluate_block(func.ast.body, scopes:)
455
- rescue ReturnValue => e
456
- e.value
484
+ result = ctx.evaluate_block(func.ast.body, scopes:)
485
+ result.is_a?(ReturnOutcome) ? result.value : result
457
486
  end
458
487
  end
459
488
 
@@ -26,6 +26,18 @@ module MilkTea
26
26
  def code
27
27
  "lex/error"
28
28
  end
29
+
30
+ def to_diagnostic(path: nil)
31
+ Diagnostic.new(
32
+ path: @path || path,
33
+ line: @line,
34
+ column: @column,
35
+ length: nil,
36
+ code: code,
37
+ message: message,
38
+ severity: :error,
39
+ )
40
+ end
29
41
  end
30
42
 
31
43
  class Lexer
@@ -1850,9 +1850,8 @@ module MilkTea
1850
1850
 
1851
1851
  evaluator = ConstFnLowerEvaluator.new(self)
1852
1852
  ctx = CompileTime::BlockContext.new(evaluator, initial_variables: initial_vars)
1853
- ctx.evaluate_block(func.ast.body, scopes: nil)
1854
- rescue CompileTime::ReturnValue => e
1855
- e.value
1853
+ result = ctx.evaluate_block(func.ast.body, scopes: nil)
1854
+ result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
1856
1855
  end
1857
1856
 
1858
1857
  class ConstFnLowerEvaluator
@@ -59,6 +59,18 @@ module MilkTea
59
59
  def code
60
60
  "lowering/internal"
61
61
  end
62
+
63
+ def to_diagnostic(path: nil)
64
+ Diagnostic.new(
65
+ path: @path || path,
66
+ line: @line,
67
+ column: @column,
68
+ length: nil,
69
+ code: code,
70
+ message: message,
71
+ severity: :error,
72
+ )
73
+ end
62
74
  end
63
75
 
64
76
  module Lowering
@@ -14,6 +14,18 @@ module MilkTea
14
14
  def code
15
15
  "module/error"
16
16
  end
17
+
18
+ def to_diagnostic(path: nil)
19
+ Diagnostic.new(
20
+ path: @path || path,
21
+ line: @line,
22
+ column: @column,
23
+ length: nil,
24
+ code: code,
25
+ message: message,
26
+ severity: :error,
27
+ )
28
+ end
17
29
  end
18
30
 
19
31
  class ModuleLoader
@@ -69,11 +81,10 @@ module MilkTea
69
81
  suffix_platform = platform_suffix_for_path(path)
70
82
  return suffix_platform if suffix_platform
71
83
 
72
- manifest_platform = PackageManifest.load(path).platform
84
+ manifest = PackageManifest.load_option(path)
85
+ manifest_platform = manifest&.platform
73
86
  return manifest_platform if manifest_platform
74
87
 
75
- normalize_platform_name(host_platform || default_host_platform)
76
- rescue PackageManifestError
77
88
  normalize_platform_name(host_platform || default_host_platform)
78
89
  end
79
90
 
@@ -167,7 +178,10 @@ module MilkTea
167
178
 
168
179
  def check_program(path)
169
180
  with_check_context(path) do |root_path|
170
- check_program_parallel(root_path)
181
+ errors = []
182
+ check_program_parallel(root_path, collecting_errors: errors)
183
+ raise errors.first if errors.any?
184
+
171
185
  build_program(root_path)
172
186
  end
173
187
  end
@@ -313,7 +327,7 @@ module MilkTea
313
327
  check_path(resolved_path)
314
328
  rescue SemanticError => e
315
329
  @analysis_cache[resolved_path] = previous if previous
316
- collecting_errors << e if collecting_errors
330
+ collecting_errors << e
317
331
  end
318
332
  @forward_bindings.clear
319
333
 
@@ -322,13 +336,14 @@ module MilkTea
322
336
  # are not themselves part of a cycle; they just depended on cycle members.
323
337
  (levels + [tail_members]).each do |level_paths|
324
338
  if level_paths.length == 1
325
- check_path(level_paths.first)
339
+ begin
340
+ check_path(level_paths.first)
341
+ rescue SemanticError => e
342
+ collecting_errors << e
343
+ end
326
344
  elsif level_paths.any?
327
- check_level_parallel(level_paths)
345
+ collecting_errors.concat(check_level_parallel(level_paths))
328
346
  end
329
- rescue SemanticError => e
330
- raise unless collecting_errors
331
- collecting_errors << e
332
347
  end
333
348
  end
334
349
 
@@ -369,28 +384,31 @@ module MilkTea
369
384
  levels
370
385
  end
371
386
 
387
+ # Checks a level of independent modules on worker threads. Each worker
388
+ # returns { ok: analysis } or { error: e } as an ordinary value; the join
389
+ # point collects the failed outcomes instead of re-raising them, so the
390
+ # caller decides whether to propagate or accumulate.
372
391
  def check_level_parallel(paths)
373
- threads = paths.map do |resolved_path|
392
+ workers = paths.map do |resolved_path|
374
393
  Thread.new do
375
- Thread.current[:resolved_path] = resolved_path
376
394
  begin
377
- analysis = check_path(resolved_path)
378
- Thread.current[:analysis] = analysis
395
+ { ok: check_path(resolved_path) }
379
396
  rescue ModuleLoadError, PackageLockError, SemanticError => e
380
- Thread.current[:error] = e
397
+ { error: e }
381
398
  end
382
399
  end
383
400
  end
384
401
 
385
- threads.each(&:join)
402
+ workers.each(&:join)
386
403
 
387
- paths.zip(threads).each do |resolved_path, t|
388
- raise t[:error] if t[:error]
389
- end
404
+ workers.map(&:value).filter_map { |result| result[:error] }
390
405
  end
391
406
 
392
407
  def imported_modules_for_ast(ast, importer_path: nil)
393
- resolve_imports_for_ast(ast, importer_path:, collecting: false)
408
+ result = resolve_imports_for_ast(ast, importer_path:, collecting: false)
409
+ raise result.errors.first.error if result.errors.any?
410
+
411
+ result.modules
394
412
  end
395
413
 
396
414
  def imported_modules_for_ast_collecting_errors(ast, importer_path: nil)
@@ -421,29 +439,27 @@ module MilkTea
421
439
  modules[import.path.to_s] = @binder.module_binding(import_analysis)
422
440
  end
423
441
  rescue ModuleLoadError, PackageLockError, SemanticError => e
424
- raise unless collecting
425
-
426
- handle_circular_import_in_collecting_mode(import, import_path, modules, errors, e)
442
+ if collecting
443
+ handle_circular_import_in_collecting_mode(import, import_path, modules, errors, e)
444
+ else
445
+ errors << ImportResolutionError.new(import:, error: e)
446
+ end
427
447
  end
428
448
  end
429
449
 
430
450
  begin
431
451
  @async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: collecting)
432
452
  rescue ModuleLoadError, PackageLockError => e
433
- raise unless collecting
434
453
  errors << ImportResolutionError.new(import: nil, error: e)
435
454
  end
436
455
 
437
456
  begin
438
457
  @prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: collecting)
439
458
  rescue ModuleLoadError, PackageLockError => e
440
- raise unless collecting
441
459
  errors << ImportResolutionError.new(import: nil, error: e)
442
460
  end
443
461
 
444
- return ImportResolution.new(modules: modules.freeze, errors: errors.freeze) if collecting
445
-
446
- modules.freeze
462
+ ImportResolution.new(modules: modules.freeze, errors: errors.freeze)
447
463
  ensure
448
464
  @import_resolve_depth -= 1 if @import_resolve_depth
449
465
  end
@@ -528,7 +544,7 @@ module MilkTea
528
544
  if use_shared_cache?
529
545
  entry = @shared_cache[resolved_path]
530
546
  if entry
531
- mtime = File.mtime(resolved_path).to_f rescue nil
547
+ mtime = source_mtime(resolved_path)
532
548
  if mtime && entry[:mtime] == mtime
533
549
  @analysis_cache[resolved_path] = entry[:analysis]
534
550
  return [resolved_path, nil, entry[:analysis]]
@@ -540,10 +556,19 @@ module MilkTea
540
556
  [resolved_path, ast, nil]
541
557
  end
542
558
 
559
+ # Option-style source modification time: nil when the file's mtime cannot
560
+ # be read (e.g. the file was removed between resolution and stat), so
561
+ # shared-cache reads simply miss instead of raising.
562
+ def source_mtime(path)
563
+ File.mtime(path).to_f
564
+ rescue SystemCallError
565
+ nil
566
+ end
567
+
543
568
  def update_shared_cache(resolved_path, analysis)
544
569
  return unless use_shared_cache?
545
570
 
546
- mtime = File.mtime(resolved_path).to_f rescue nil
571
+ mtime = source_mtime(resolved_path)
547
572
  @shared_cache[resolved_path] = { mtime:, analysis: } if mtime
548
573
  end
549
574
 
@@ -575,11 +600,7 @@ module MilkTea
575
600
  end
576
601
 
577
602
  def inferred_module_name_for_path(path)
578
- manifest = begin
579
- PackageManifest.load(path)
580
- rescue PackageManifestError
581
- nil
582
- end
603
+ manifest = PackageManifest.load_option(path)
583
604
 
584
605
  if manifest && path_within_root?(path, manifest.source_root)
585
606
  return module_name_for_path(path, manifest.source_root)
@@ -632,8 +653,9 @@ module MilkTea
632
653
  import_result = resolve_imports_for_ast(ast, importer_path: resolved_path, collecting: true)
633
654
  result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules: import_result.modules, path: resolved_path)
634
655
  @analysis_cache[resolved_path] = result[:analysis] if result[:analysis]
635
- rescue StandardError
636
- # Analysis capture is best-effort.
656
+ rescue ModuleLoadError, PackageLockError, SemanticError
657
+ # Analysis capture is best-effort; named load/check failures leave the
658
+ # cycle member to the pass-2 re-check instead.
637
659
  ensure
638
660
  @checking_paths.pop
639
661
  end
@@ -31,6 +31,18 @@ module MilkTea
31
31
  def code
32
32
  "parse/error"
33
33
  end
34
+
35
+ def to_diagnostic(path: nil)
36
+ Diagnostic.new(
37
+ path: @path || path,
38
+ line: line,
39
+ column: column,
40
+ length: nil,
41
+ code: code,
42
+ message: message,
43
+ severity: :error,
44
+ )
45
+ end
34
46
  end
35
47
 
36
48
  class SyntaxTokenStream
@@ -9,7 +9,8 @@ module MilkTea
9
9
  case decl
10
10
  when AST::StructDecl
11
11
  packed, alignment = check_decl_attribute_applications!(decl.attributes, target_kind: :struct, target_label: "struct #{decl.name}", target_node: decl)
12
- @ctx.types.fetch(decl.name).set_layout(packed:, alignment:)
12
+ struct_type = @ctx.types[decl.name]
13
+ struct_type.set_layout(packed:, alignment:) if struct_type
13
14
 
14
15
  decl.fields.each do |field|
15
16
  with_error_node(field) do
@@ -249,32 +249,20 @@ module MilkTea
249
249
  end
250
250
  end
251
251
 
252
- def check_functions
253
- @ctx.top_level_functions.each_value do |binding|
254
- check_function(binding)
255
- end
256
-
257
- @ctx.methods.each_value do |method_map|
258
- method_map.each_value do |binding|
259
- check_function(binding)
260
- end
261
- end
262
- end
263
-
264
252
  # Validates the body of a specialized (instantiated) function or method
265
- # binding. The owner checker may be in collecting-errors mode, which
266
- # would silently swallow body errors into @structural_errors. We
267
- # temporarily disable collect mode on the owner so the caller receives
268
- # the SemanticError directly.
253
+ # binding. The owner checker always accumulates body errors into its
254
+ # structural buffer, so this pulls out the new errors and surfaces the
255
+ # first non-tolerated one directly; tolerated ones are dropped.
269
256
  def validate_specialized_function_body!(binding)
270
257
  owner = binding.owner
271
- prev_collecting = owner.instance_variable_get(:@collecting_errors)
272
- owner.instance_variable_set(:@collecting_errors, false)
258
+ structural_errors = owner.instance_variable_get(:@structural_errors)
259
+ prev_count = structural_errors.length
273
260
  owner.check_function(binding)
261
+ new_errors = structural_errors[prev_count..].to_a
262
+ structural_errors.slice!(prev_count..) unless new_errors.empty?
263
+ new_errors.each { |error| raise error unless error.message.include?("cannot assign through immutable") }
274
264
  rescue SemanticError => e
275
265
  raise unless e.message.include?("cannot assign through immutable")
276
- ensure
277
- owner.instance_variable_set(:@collecting_errors, prev_collecting) if owner
278
266
  end
279
267
 
280
268
  # Per-function error collection used by check_collecting_errors.
@@ -9,7 +9,8 @@ module MilkTea
9
9
  next unless decl.is_a?(AST::StructDecl) || decl.is_a?(AST::OpaqueDecl)
10
10
  next if decl.implements.empty?
11
11
 
12
- receiver_type = @ctx.types.fetch(decl.name)
12
+ receiver_type = @ctx.types[decl.name]
13
+ next unless receiver_type
13
14
  resolved_interfaces = []
14
15
  seen = {}
15
16
 
@@ -34,17 +34,17 @@ module MilkTea
34
34
  end
35
35
  record_local_completion_snapshot(end_line, 1_000_000, nested_scopes)
36
36
  rescue SemanticError => e
37
- if @collecting_errors
38
- @structural_errors << e
39
- next
37
+ if e.line.nil? && statement.line
38
+ e = SemanticError.new(
39
+ e.message,
40
+ line: statement.line,
41
+ column: source_column(statement),
42
+ length: source_length(statement),
43
+ path: @path,
44
+ )
40
45
  end
41
-
42
- raise e unless e.line.nil?
43
-
44
- stmt_line = statement.line
45
- raise e if stmt_line.nil?
46
-
47
- raise_sema_error(e.message, statement)
46
+ @structural_errors << e
47
+ next
48
48
  end
49
49
  end
50
50
  end
@@ -18,7 +18,8 @@ module MilkTea
18
18
  collect_structural_error(e)
19
19
  end
20
20
  when AST::VarDecl
21
- binding = @ctx.top_level_values.fetch(decl.name)
21
+ binding = @ctx.top_level_values[decl.name]
22
+ next unless binding
22
23
  if decl.value
23
24
  validate_consuming_foreign_expression!(decl.value, scopes: [], root_allowed: false)
24
25
  validate_hoistable_foreign_expression!(decl.value, scopes: [], root_hoistable: false)
@@ -39,7 +40,8 @@ module MilkTea
39
40
  end
40
41
 
41
42
  def check_expr_const(decl)
42
- binding = @ctx.top_level_values.fetch(decl.name)
43
+ binding = @ctx.top_level_values[decl.name]
44
+ return unless binding
43
45
  validate_consuming_foreign_expression!(decl.value, scopes: [], root_allowed: false)
44
46
  validate_hoistable_foreign_expression!(decl.value, scopes: [], root_hoistable: false)
45
47
 
@@ -270,9 +272,7 @@ module MilkTea
270
272
  def evaluate_compile_time_block(statements, scopes: nil)
271
273
  ctx = CompileTime::BlockContext.new(self)
272
274
  result = ctx.evaluate_block(statements, scopes:)
273
- result
274
- rescue CompileTime::ReturnValue => e
275
- e.value
275
+ result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
276
276
  rescue CompileTime::Error => e
277
277
  raise_sema_error(e.message)
278
278
  end
@@ -558,9 +558,8 @@ module MilkTea
558
558
  end
559
559
 
560
560
  ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars)
561
- ctx.evaluate_block(func.ast.body, scopes: nil)
562
- rescue CompileTime::ReturnValue => e
563
- e.value
561
+ result = ctx.evaluate_block(func.ast.body, scopes: nil)
562
+ result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
564
563
  rescue CompileTime::Error => e
565
564
  raise_sema_error(e.message)
566
565
  end
@@ -589,9 +588,8 @@ module MilkTea
589
588
  end
590
589
 
591
590
  ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars)
592
- ctx.evaluate_block(func.ast.body, scopes: nil)
593
- rescue CompileTime::ReturnValue => e
594
- e.value
591
+ result = ctx.evaluate_block(func.ast.body, scopes: nil)
592
+ result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
595
593
  rescue CompileTime::Error => e
596
594
  raise_sema_error(e.message)
597
595
  end
@@ -293,9 +293,15 @@ module MilkTea
293
293
 
294
294
  constraints = resolve_type_param_constraints(decl.type_params)
295
295
  if decl.is_a?(AST::InterfaceDecl)
296
- @ctx.interfaces[decl.name] = @ctx.interfaces.fetch(decl.name).with(type_param_constraints: constraints)
296
+ interface_binding = @ctx.interfaces[decl.name]
297
+ next unless interface_binding
298
+
299
+ @ctx.interfaces[decl.name] = interface_binding.with(type_param_constraints: constraints)
297
300
  else
298
- @ctx.types.fetch(decl.name).define_type_param_constraints(constraints)
301
+ type_binding = @ctx.types[decl.name]
302
+ next unless type_binding
303
+
304
+ type_binding.define_type_param_constraints(constraints)
299
305
  end
300
306
  end
301
307
  end
@@ -455,7 +461,8 @@ module MilkTea
455
461
  with_error_node(decl) do
456
462
  next unless decl.is_a?(AST::StructDecl) || decl.is_a?(AST::UnionDecl)
457
463
 
458
- struct_type = @ctx.types.fetch(decl.name)
464
+ struct_type = @ctx.types[decl.name]
465
+ next unless struct_type
459
466
  struct_type.ast_declaration = decl if struct_type.respond_to?(:ast_declaration=)
460
467
  type_params = if struct_type.is_a?(Types::GenericStructDefinition)
461
468
  seen = {}
@@ -581,7 +588,8 @@ module MilkTea
581
588
  with_error_node(decl) do
582
589
  next unless decl.is_a?(AST::EnumDecl) || decl.is_a?(AST::FlagsDecl)
583
590
 
584
- enum_type = @ctx.types.fetch(decl.name)
591
+ enum_type = @ctx.types[decl.name]
592
+ next unless enum_type
585
593
  backing_type = resolve_type_ref(decl.backing_type)
586
594
  unless backing_type.is_a?(Types::Primitive) && backing_type.integer?
587
595
  raise_sema_error("#{decl.name} backing type must be an integer primitive, got #{backing_type}")
@@ -641,7 +649,8 @@ module MilkTea
641
649
  with_error_node(decl) do
642
650
  next unless decl.is_a?(AST::VariantDecl)
643
651
 
644
- variant_type = @ctx.types.fetch(decl.name)
652
+ variant_type = @ctx.types[decl.name]
653
+ next unless variant_type
645
654
  type_params = if variant_type.is_a?(Types::GenericVariantDefinition)
646
655
  seen = {}
647
656
  variant_type.type_params.each_with_object({}) do |name, params|
@@ -187,31 +187,9 @@ module MilkTea
187
187
  end
188
188
 
189
189
  def check
190
- @completed_phases = Set.new
191
-
192
- run_phase(:install_builtin_types)
193
- run_phase(:install_builtin_attributes)
194
- run_phase(:install_imports)
195
- run_phase(:install_prelude_types, requires: [:install_imports])
196
- run_phase(:declare_named_types, requires: [:install_builtin_types, :install_imports, :install_prelude_types])
197
- run_phase(:resolve_generic_type_param_constraints, requires: [:declare_named_types])
198
- run_phase(:resolve_type_aliases, requires: [:declare_named_types])
199
- run_phase(:declare_attributes)
200
- run_phase(:predeclare_top_level_consts)
201
- run_phase(:resolve_aggregate_fields, requires: [:resolve_type_aliases, :declare_named_types])
202
- run_phase(:resolve_enum_members, requires: [:declare_named_types])
203
- run_phase(:resolve_variant_arms, requires: [:declare_named_types])
204
- run_phase(:collect_emit_declarations)
205
- run_phase(:declare_top_level_values, requires: [:resolve_aggregate_fields, :resolve_type_aliases])
206
- run_phase(:check_attribute_applications, requires: [:declare_attributes])
207
- run_phase(:declare_functions, requires: [:resolve_aggregate_fields, :resolve_enum_members, :resolve_variant_arms])
208
- run_phase(:check_interface_conformances, requires: [:declare_functions, :resolve_aggregate_fields])
209
- run_phase(:check_top_level_values, requires: [:declare_top_level_values])
210
- run_phase(:finalize_top_level_const_values, requires: [:check_top_level_values])
211
- run_phase(:check_top_level_static_asserts, requires: [:finalize_top_level_const_values])
212
- run_phase(:check_functions, requires: [:declare_functions, :resolve_aggregate_fields, :check_interface_conformances])
213
-
214
- build_analysis
190
+ result = check_collecting_errors
191
+ raise result[:errors].first unless result[:errors].empty?
192
+ result[:analysis]
215
193
  end
216
194
 
217
195
  def run_phase(name, requires: [])
@@ -291,12 +269,11 @@ module MilkTea
291
269
  end
292
270
  end
293
271
 
294
- # Like check, but collects per-function errors instead of raising at first.
295
- # Structural phases (imports, type resolution, declaration) collect errors per
296
- # declaration so that the maximum number of diagnostics are surfaced.
272
+ # Runs all sema phases and collects every error instead of stopping at
273
+ # the first one. Structural phases collect per-declaration, and
274
+ # function-body phases collect per function/method.
297
275
  # Returns { analysis: Analysis, errors: [SemanticError] }.
298
276
  def check_collecting_errors
299
- @collecting_errors = true
300
277
  @structural_errors = []
301
278
  @completed_phases = Set.new
302
279
 
@@ -353,8 +330,6 @@ module MilkTea
353
330
  end
354
331
 
355
332
  def collect_structural_error(error)
356
- raise error unless @collecting_errors
357
-
358
333
  @structural_errors << error
359
334
  end
360
335
  end
@@ -404,6 +404,12 @@ module MilkTea
404
404
  end
405
405
 
406
406
  def handle_workspace_diagnostic(params)
407
+ progress = nil
408
+ if (work_done_token = params['workDoneToken'])
409
+ progress = create_progress_handle(@protocol, work_done_token)
410
+ progress.report(percentage: 0, message: 'Collecting workspace diagnostics...')
411
+ end
412
+
407
413
  previous_ids = params['previousResultIds'] || []
408
414
  prev_map = previous_ids.each_with_object({}) do |entry, h|
409
415
  h[entry['uri']] = entry['value'] if entry.is_a?(Hash) && entry['uri']
@@ -420,15 +426,19 @@ module MilkTea
420
426
 
421
427
  cached = @workspace_diagnostic_cache[uri]
422
428
  if cached && cached[:result_id] == prev_map[uri] && cached[:fingerprint] == fingerprint
423
- { uri: uri, kind: 'unchanged', resultId: result_id, items: [] }
429
+ { uri: uri, kind: 'unchanged', resultId: result_id, items: [], version: nil }
424
430
  else
425
431
  @workspace_diagnostic_cache[uri] = { result_id: result_id, fingerprint: fingerprint }
426
- { uri: uri, kind: 'full', resultId: result_id, items: diagnostics }
432
+ { uri: uri, kind: 'full', resultId: result_id, items: diagnostics, version: nil }
427
433
  end
428
434
  end
429
435
 
436
+ progress&.report(percentage: 100, message: "#{items.length} document#{items.length == 1 ? '' : 's'} checked")
437
+ progress&.done(message: 'Workspace diagnostics ready')
438
+
430
439
  { items: items }
431
440
  rescue StandardError => e
441
+ progress&.done(message: 'Workspace diagnostics failed')
432
442
  warn "Error in workspace/diagnostic handler: #{e.message}"
433
443
  { items: [] }
434
444
  end
@@ -16,7 +16,8 @@ module MilkTea
16
16
  # Enrich with hierarchical children from AST
17
17
  ast = @workspace.get_ast(uri)
18
18
  if ast && result
19
- enrich_with_children(result, ast)
19
+ facts = @workspace.get_facts(uri)
20
+ enrich_with_children(result, ast, facts)
20
21
  end
21
22
 
22
23
  module_name = resolve_outline_module_name(uri)
@@ -74,17 +75,42 @@ module MilkTea
74
75
  children
75
76
  end
76
77
 
77
- def enrich_with_children(symbols, ast)
78
+ def enrich_with_children(symbols, ast, facts)
78
79
  removed_local_names = []
79
80
  removed_method_names = []
80
81
  removed_nested_type_names = []
81
82
  removed_event_names = []
82
83
  name_index = symbols.each_with_object(Hash.new { |h, k| h[k] = [] }) { |s, h| h[s[:name]] << s }
83
84
 
84
- ast.declarations&.each do |decl|
85
+ flatten_module_declarations(ast.declarations).each do |decl|
85
86
  removed_nested_type_names.concat(collect_nested_type_names(decl)) if decl.is_a?(AST::StructDecl)
86
87
 
87
88
  case decl
89
+ when AST::VarDecl
90
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
91
+ next unless parent
92
+
93
+ detail = decl.type ? type_detail_string(decl.type) : nil
94
+ detail ||= resolved_local_type_detail(decl, facts)
95
+ parent[:detail] = detail if detail
96
+
97
+ when AST::TypeAliasDecl
98
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
99
+ next unless parent
100
+
101
+ if (detail = type_detail_string(decl.target))
102
+ parent[:detail] = "= #{detail}"
103
+ end
104
+
105
+ when AST::ExternFunctionDecl, AST::ForeignFunctionDecl
106
+ parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
107
+ next unless parent
108
+
109
+ detail_parts = []
110
+ detail_parts << 'async' if decl.respond_to?(:async) && decl.async
111
+ detail_parts << "-> #{type_detail_string(decl.return_type) || 'void'}"
112
+ parent[:detail] = detail_parts.join(' ')
113
+
88
114
  when AST::EventDecl
89
115
  parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
90
116
  next unless parent
@@ -100,26 +126,11 @@ module MilkTea
100
126
  detail_parts = []
101
127
  detail_parts << 'const' if decl.respond_to?(:const) && decl.const
102
128
  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?
129
+ detail_parts << "-> #{type_detail_string(decl.return_type) || 'void'}"
130
+ parent[:detail] = detail_parts.join(' ')
107
131
 
108
132
  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
133
+ append_local_children(parent, locals, facts, removed_local_names)
123
134
  when AST::ExtendingBlock
124
135
  type_name_str = decl.type_name.name.parts.join('.')
125
136
 
@@ -153,20 +164,7 @@ module MilkTea
153
164
  removed_method_names << child[:name] if child[:kind] == 6
154
165
 
155
166
  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
167
+ append_local_children(child, locals, facts, removed_local_names)
170
168
  end
171
169
  when AST::ConstDecl
172
170
  parent = name_index[decl.name]&.find { |s| symbol_line(s) == (decl.line || 0) }
@@ -179,25 +177,13 @@ module MilkTea
179
177
  next unless decl.block_body
180
178
 
181
179
  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
180
+ append_local_children(parent, locals, facts, removed_local_names)
196
181
  else
197
182
  parent_name = child_parent_name(decl)
198
183
  parent = parent_name ? name_index[parent_name]&.find { |s| symbol_line(s) == (decl.line || 0) } : nil
199
184
  next unless parent
200
185
 
186
+ detail_parts = []
201
187
  if decl.is_a?(AST::StructDecl) && decl.implements&.any?
202
188
  ifaces = decl.implements.map { |i|
203
189
  base = i.respond_to?(:parts) ? i.parts.join('.') : i.name.parts.join('.')
@@ -210,9 +196,19 @@ module MilkTea
210
196
  end
211
197
  "#{base}#{args}"
212
198
  }.join(', ')
213
- parent[:detail] = "(#{ifaces})"
199
+ detail_parts << "(#{ifaces})"
200
+ end
201
+
202
+ if decl.respond_to?(:backing_type) && decl.backing_type && (bt = type_detail_string(decl.backing_type))
203
+ detail_parts << ": #{bt}"
204
+ end
205
+
206
+ if (generic = generic_signature_detail(decl))
207
+ detail_parts << generic
214
208
  end
215
209
 
210
+ parent[:detail] = detail_parts.join(' ') unless detail_parts.empty?
211
+
216
212
  type_params = expand_generic_type_params(decl)
217
213
  if type_params&.any?
218
214
  parent[:children] ||= []
@@ -374,14 +370,78 @@ module MilkTea
374
370
  line = (a.line) ? a.line : default_line
375
371
  return nil unless a.respond_to?(:name) && a.name && line
376
372
  col = a.column ? a.column : 1
373
+
374
+ detail = nil
375
+ if a.respond_to?(:fields) && a.fields&.any?
376
+ fields = a.fields.map { |f| "#{f.name}: #{type_detail_string(f.type)}" }.join(', ')
377
+ detail = "(#{fields})"
378
+ end
379
+
377
380
  {
378
381
  name: a.name, kind: 22,
382
+ detail: detail,
379
383
  range: { start: { line: line - 1, character: 0 }, end: { line: line, character: 0 } },
380
384
  selectionRange: {
381
385
  start: { line: line - 1, character: col - 1 },
382
386
  end: { line: line - 1, character: col - 1 + a.name.length },
383
387
  },
384
- }
388
+ }.compact
389
+ end
390
+
391
+ # Renders the generic parameter clause of a type declaration, e.g.
392
+ # `[A, B]` for `struct Pair[A, B]` or `[@a]` for `struct Buffer[@a]`.
393
+ def generic_signature_detail(decl)
394
+ parts = []
395
+ if decl.respond_to?(:lifetime_params) && decl.lifetime_params&.any?
396
+ parts.concat(decl.lifetime_params.map(&:to_s))
397
+ end
398
+ if decl.respond_to?(:type_params) && decl.type_params&.any?
399
+ parts.concat(decl.type_params.map { |tp| tp.respond_to?(:name) ? tp.name : tp.to_s })
400
+ end
401
+ parts.empty? ? nil : "[#{parts.join(', ')}]"
402
+ end
403
+
404
+ # Module-level `when` branches are compile-time conditionals; the token
405
+ # symbol scan lists their declarations, so flatten the branch bodies so
406
+ # the enrichment below can type them like ordinary top-level decls.
407
+ def flatten_module_declarations(declarations)
408
+ (declarations || []).flat_map do |decl|
409
+ if decl.is_a?(AST::WhenStmt)
410
+ (decl.branches || []).flat_map { |b| flatten_module_declarations(b.body) } +
411
+ flatten_module_declarations(decl.else_body)
412
+ else
413
+ [decl]
414
+ end
415
+ end
416
+ end
417
+
418
+ # Adds local declaration children to an outline symbol. Destructure
419
+ # locals (`let Vec2(x, y) = ...`) introduce a spurious flat variable
420
+ # symbol named after the destructure type; that symbol is collected for
421
+ # removal instead of being shown as a typed child.
422
+ def append_local_children(container, locals, facts, removed_local_names)
423
+ return unless locals&.any?
424
+
425
+ container[:children] ||= []
426
+ children = container[:children]
427
+ locals.each do |local|
428
+ if local.respond_to?(:destructure_bindings) && local.destructure_bindings
429
+ type_name = local.destructure_type_name
430
+ if type_name
431
+ name = type_name.is_a?(Array) ? type_name.join('.') : type_name.to_s
432
+ removed_local_names << name unless removed_local_names.include?(name)
433
+ end
434
+ next
435
+ end
436
+ next unless local.name
437
+
438
+ child = local_decl_symbol(local, facts:)
439
+ next unless child
440
+
441
+ children << child unless children.any? { |pc| pc[:name] == child[:name] }
442
+ removed_local_names << local.name
443
+ removed_local_names.concat(descendant_names(child))
444
+ end
385
445
  end
386
446
 
387
447
  def collect_local_decls(body)
@@ -417,7 +477,7 @@ module MilkTea
417
477
  end
418
478
  end
419
479
 
420
- def local_decl_symbol(decl)
480
+ def local_decl_symbol(decl, facts: nil)
421
481
  return nil unless decl.name
422
482
  return nil if decl.name == '_'
423
483
 
@@ -429,8 +489,9 @@ module MilkTea
429
489
  if decl.respond_to?(:value) && decl.value.is_a?(AST::ProcExpr)
430
490
  detail ||= proc_signature_detail(decl.value)
431
491
  proc_locals = collect_local_decls(decl.value.body)
432
- children = proc_locals.filter_map { |l| local_decl_symbol(l) }
492
+ children = proc_locals.filter_map { |l| local_decl_symbol(l, facts:) }
433
493
  end
494
+ detail ||= resolved_local_type_detail(decl, facts)
434
495
 
435
496
  {
436
497
  name: decl.name, kind: 13,
@@ -441,6 +502,60 @@ module MilkTea
441
502
  }.compact
442
503
  end
443
504
 
505
+ # Resolves the declared type of an inferred local from semantic facts.
506
+ # Prefers the sema binding type (which reflects let/var ... else: and
507
+ # other flow refinement), falling back to the initializer expression
508
+ # type recorded during checking. Returns nil when facts are unavailable
509
+ # or the binding is missing.
510
+ def resolved_local_type_detail(decl, facts)
511
+ return nil unless facts
512
+ return nil unless facts.respond_to?(:binding_resolution) && facts.binding_resolution
513
+
514
+ binding_id = facts.binding_resolution.declaration_binding_ids[decl.object_id]
515
+ if binding_id
516
+ type = facts.binding_resolution.binding_types[binding_id]
517
+ return nil if type.is_a?(Types::Error)
518
+ return short_type_detail(type) if type
519
+ end
520
+
521
+ return nil unless decl.respond_to?(:value) && decl.value
522
+ return nil unless facts.respond_to?(:resolved_expr_types)
523
+
524
+ node_id = facts.respond_to?(:ast) && facts.ast ? facts.ast.node_ids[decl.value.object_id] : nil
525
+ return nil unless node_id
526
+
527
+ type = facts.resolved_expr_types[node_id]
528
+ return nil if type.is_a?(Types::Error)
529
+
530
+ short_type_detail(type)
531
+ end
532
+
533
+ # Renders a resolved semantic type for outline display without module
534
+ # qualifiers (e.g. std.deque.Deque[int] as Deque[int]). Falls back to
535
+ # the canonical #to_s when a type has no usable short form.
536
+ def short_type_detail(type)
537
+ return nil unless type
538
+
539
+ case type
540
+ when Types::Nullable
541
+ "#{short_type_detail(type.base)}?"
542
+ when Types::Span
543
+ "span[#{short_type_detail(type.element_type)}]"
544
+ when Types::SoA
545
+ "SoA[#{short_type_detail(type.element_type)}, #{type.count}]"
546
+ when Types::StructInstance, Types::VariantInstance, Types::GenericInstance
547
+ args = type.arguments.map { |arg| short_type_arg(arg) }.join(', ')
548
+ args.empty? ? type.name : "#{type.name}[#{args}]"
549
+ else
550
+ name = type.respond_to?(:name) ? type.name.to_s : ''
551
+ name.empty? ? type.to_s : name
552
+ end
553
+ end
554
+
555
+ def short_type_arg(arg)
556
+ arg.is_a?(Types::LiteralTypeArg) ? arg.value.to_s : short_type_detail(arg)
557
+ end
558
+
444
559
  def descendant_names(symbol)
445
560
  return [] unless symbol[:children]
446
561
 
@@ -487,9 +602,7 @@ module MilkTea
487
602
  detail_parts << 'mut' if m.respond_to?(:kind) && m.kind == :editable_function
488
603
  detail_parts << 'static' if m.respond_to?(:kind) && m.kind == :static_function
489
604
  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
605
+ detail_parts << "-> #{type_detail_string(m.return_type) || 'void'}"
493
606
  {
494
607
  name: m.name, kind: 6,
495
608
  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 +620,11 @@ module MilkTea
507
620
  case type
508
621
  when AST::TypeRef
509
622
  type.to_s
510
- when AST::ProcType
623
+ when AST::FunctionType, AST::ProcType
511
624
  params = (type.params || []).map { |p| type_detail_string(p.type) }.join(', ')
512
625
  ret = type_detail_string(type.return_type) || 'void'
513
- "proc(#{params}) -> #{ret}"
626
+ keyword = type.is_a?(AST::FunctionType) ? 'fn' : 'proc'
627
+ "#{keyword}(#{params}) -> #{ret}"
514
628
  when AST::TupleType
515
629
  base = "(#{(type.element_types || []).map { |t| type_detail_string(t) }.join(', ')})"
516
630
  type.nullable ? "#{base}?" : base
@@ -108,13 +108,11 @@ module MilkTea
108
108
  def collect_inferred_type_hints(facts, start_line, start_char, end_line, end_char)
109
109
  hints = []
110
110
  collect_local_decls(facts.ast).each do |decl|
111
+ next unless decl.name && decl.name != '_'
111
112
  next unless decl.type.nil?
112
113
  next unless position_in_range?(decl.line - 1, decl.column - 1, start_line, start_char, end_line, end_char)
113
114
 
114
- binding = facts.values[decl.name]
115
- next unless binding
116
-
117
- display_type = describe_type_for_hint(binding.storage_type)
115
+ display_type = resolved_decl_type_detail(decl, facts)
118
116
  next unless display_type
119
117
 
120
118
  hints << {
@@ -127,20 +125,30 @@ module MilkTea
127
125
  hints
128
126
  end
129
127
 
128
+ # Resolves the declared type of an inferred local from semantic facts so
129
+ # the hint reflects flow refinement (let/var ... else:, ?-propagation).
130
+ def resolved_decl_type_detail(decl, facts)
131
+ resolution = facts.respond_to?(:binding_resolution) ? facts.binding_resolution : nil
132
+ return nil unless resolution
133
+
134
+ binding_id = resolution.declaration_binding_ids[decl.object_id]
135
+ return nil unless binding_id
136
+
137
+ type = resolution.binding_types[binding_id]
138
+ return nil if type.is_a?(Types::Error)
139
+
140
+ short_type_detail(type)
141
+ end
142
+
130
143
  def collect_inferred_return_hints(facts, start_line, start_char, end_line, end_char)
131
144
  hints = []
132
145
  collect_function_defs(facts.ast).each do |func|
133
146
  next unless func.return_type.nil?
134
147
  next unless position_in_range?(func.line - 1, func.column - 1, start_line, start_char, end_line, end_char)
135
148
 
136
- binding = facts.functions[func.name]
137
- unless binding
138
- facts.imports.each_value do |mod|
139
- binding = mod.functions[func.name]
140
- break if binding
141
- end
142
- end
149
+ binding = resolve_function_binding(facts, func)
143
150
  next unless binding
151
+ next unless binding.respond_to?(:type) && binding.type.respond_to?(:return_type)
144
152
 
145
153
  return_type = binding.type.return_type
146
154
  next unless return_type
@@ -160,6 +168,20 @@ module MilkTea
160
168
  hints
161
169
  end
162
170
 
171
+ def resolve_function_binding(facts, func)
172
+ name = func.name
173
+ if func.is_a?(AST::MethodDef)
174
+ facts.methods.each_value do |methods|
175
+ binding = methods[name] || methods["static:#{name}"]
176
+ return binding if binding
177
+ end
178
+ return nil
179
+ end
180
+
181
+ facts.functions[name] ||
182
+ facts.imports.each_value.find { |mod| mod.functions.key?(name) }&.functions&.dig(name)
183
+ end
184
+
163
185
  def collect_local_decls(ast_node)
164
186
  results = []
165
187
  case ast_node
@@ -24,7 +24,7 @@ module MilkTea
24
24
  line_str = lines[lsp_line] || ""
25
25
  return nil if line_str.empty?
26
26
 
27
- token_range = token_bounds_at(line_str, lsp_char)
27
+ token_range = token_bounds_at(line_str, lsp_line, lsp_char)
28
28
  line_range = { start: { line: lsp_line, character: 0 },
29
29
  end: { line: lsp_line, character: line_str.length } }
30
30
 
@@ -47,7 +47,7 @@ module MilkTea
47
47
  current
48
48
  end
49
49
 
50
- def token_bounds_at(line_str, lsp_char)
50
+ def token_bounds_at(line_str, lsp_line, lsp_char)
51
51
  col = [lsp_char, line_str.length - 1].min
52
52
  col = [col, 0].max
53
53
 
@@ -61,8 +61,8 @@ module MilkTea
61
61
  return nil if left == right && line_str[left] !~ /[A-Za-z0-9_]/
62
62
 
63
63
  {
64
- start: { line: 0, character: left },
65
- end: { line: 0, character: right + 1 },
64
+ start: { line: lsp_line, character: left },
65
+ end: { line: lsp_line, character: right + 1 },
66
66
  }
67
67
  end
68
68
 
@@ -18,7 +18,7 @@ module MilkTea
18
18
  short_uri = shorten_uri(uri) || uri
19
19
  log_perf_breakdown('textDocument/semanticTokens/full', elapsed,
20
20
  "uri=#{short_uri} bytes=#{content.bytesize} lines=#{content.count("\n") + 1} cache=hit data_len=#{cached[:data].length}")
21
- return { data: cached[:data] }
21
+ return { resultId: cached[:result_id], data: cached[:data] }
22
22
  end
23
23
 
24
24
  tokens_start = monotonic_time
@@ -37,9 +37,8 @@ module MilkTea
37
37
  data = encode_semantic_tokens(semantic_entries)
38
38
  encode_ms = elapsed_ms(encode_start)
39
39
 
40
- @semantic_tokens_cache[uri] = { content_hash: cache_key, data: data }
41
-
42
40
  result_id = next_semantic_token_result_id(uri)
41
+ @semantic_tokens_cache[uri] = { content_hash: cache_key, data: data, result_id: result_id }
43
42
  @semantic_tokens_delta_cache[uri] = {
44
43
  result_id: result_id,
45
44
  content_hash: cache_key,
@@ -51,7 +50,7 @@ module MilkTea
51
50
  log_perf_breakdown('textDocument/semanticTokens/full', elapsed,
52
51
  "uri=#{short_uri} bytes=#{content.bytesize} lines=#{content.count("\n") + 1} cache=miss tokens=#{tokens.length} entries=#{semantic_entries.length} data_len=#{data.length} facts=on stages_ms=tokens:#{tokens_ms},facts:#{facts_ms},build:#{build_ms},encode:#{encode_ms}")
53
52
 
54
- { data: data }
53
+ { resultId: result_id, data: data }
55
54
  rescue StandardError => e
56
55
  warn "Error in semanticTokens/full handler: #{e.message}"
57
56
  { data: [] }
@@ -1745,7 +1744,12 @@ module MilkTea
1745
1744
 
1746
1745
  start_offset = prefix * 5
1747
1746
  delete_count = old_mid * 5
1748
- insert_tokens = encode_semantic_tokens(new_entries[prefix...(new_entries.length - suffix)])
1747
+
1748
+ # The inserted segment must keep the relative encoding against the
1749
+ # last unchanged token preceding the edit, so encode the full token
1750
+ # stream from the document origin and slice out the middle segment.
1751
+ full_encoded = encode_semantic_tokens(new_entries)
1752
+ insert_tokens = full_encoded[start_offset...(new_entries.length - suffix) * 5]
1749
1753
 
1750
1754
  [{ start: start_offset, deleteCount: delete_count, data: insert_tokens }]
1751
1755
  end
@@ -5,11 +5,11 @@ module MilkTea
5
5
  class Server
6
6
  module ServerTypeHierarchy
7
7
  TYPE_KIND_MAP = {
8
- struct: 22,
9
- enum: 13,
10
- flags: 13,
11
- variant: 13,
12
- union: 22,
8
+ struct: 23,
9
+ enum: 10,
10
+ flags: 10,
11
+ variant: 23,
12
+ union: 23,
13
13
  interface: 11,
14
14
  }.freeze
15
15
 
@@ -42,6 +42,15 @@ module MilkTea
42
42
  new(path).load
43
43
  end
44
44
 
45
+ # Best-effort manifest load for optional lookups: returns nil when no
46
+ # manifest applies to the path or when the manifest is invalid, instead
47
+ # of raising PackageManifestError.
48
+ def self.load_option(path)
49
+ load(path)
50
+ rescue PackageManifestError
51
+ nil
52
+ end
53
+
45
54
  def self.manifest_exists_for?(path)
46
55
  path = File.expand_path(path)
47
56
  current = File.directory?(path) ? path : File.dirname(path)
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.3.37
4
+ version: 0.3.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -624,7 +624,7 @@ metadata:
624
624
  homepage_uri: https://teefan.github.io/mt-lang/
625
625
  source_code_uri: https://github.com/teefan/mt-lang
626
626
  post_install_message: |
627
- Milk Tea 0.3.37 installed!
627
+ Milk Tea 0.3.38 installed!
628
628
 
629
629
  System requirements:
630
630
  - A C compiler (gcc or clang) must be available on PATH
@@ -646,7 +646,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
646
646
  - !ruby/object:Gem::Version
647
647
  version: '0'
648
648
  requirements: []
649
- rubygems_version: 4.0.6
649
+ rubygems_version: 4.0.18
650
650
  specification_version: 4
651
651
  summary: The Milk Tea programming language compiler toolchain
652
652
  test_files: []