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 +4 -4
- 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/server/code_actions.rb +12 -2
- data/lib/milk_tea/lsp/server/formatting.rb +172 -58
- data/lib/milk_tea/lsp/server/inlay_hints.rb +33 -11
- data/lib/milk_tea/lsp/server/selection_range.rb +4 -4
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +9 -5
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +5 -5
- data/lib/milk_tea/packages/manifest.rb +9 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 952280dd2ad14bc3b5751fc7e8a3d29b39363293b1436cc1b3b546c31c938427
|
|
4
|
+
data.tar.gz: e0bdf3468681b07914143c32b0b9cc2470cdbeaf55cf81994186003dc406261b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9dcf6f6c955f1428b5374315782fda121669ae3c12a8c84d683205cb44bb4ca7903465e70447860e89c0753695f01af513903e93b82987838fa724be19fc7b2f
|
|
7
|
+
data.tar.gz: 67c259de17bdd81a4f5785d7dbe05cf39ab959ea51c1f27ed2b05a516ed97ce91a4b2291aa415e0abaa695337f1fc1825ead4acdfceb480a9be4db3cd3a24dd4
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
class Error < StandardError
|
|
14
|
+
def code
|
|
15
|
+
"compile_time/error"
|
|
15
16
|
end
|
|
16
|
-
end
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
data/lib/milk_tea/core/lexer.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
392
|
+
workers = paths.map do |resolved_path|
|
|
374
393
|
Thread.new do
|
|
375
|
-
Thread.current[:resolved_path] = resolved_path
|
|
376
394
|
begin
|
|
377
|
-
|
|
378
|
-
Thread.current[:analysis] = analysis
|
|
395
|
+
{ ok: check_path(resolved_path) }
|
|
379
396
|
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
380
|
-
|
|
397
|
+
{ error: e }
|
|
381
398
|
end
|
|
382
399
|
end
|
|
383
400
|
end
|
|
384
401
|
|
|
385
|
-
|
|
402
|
+
workers.each(&:join)
|
|
386
403
|
|
|
387
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
data/lib/milk_tea/core/parser.rb
CHANGED
|
@@ -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
|
|
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
|
|
266
|
-
#
|
|
267
|
-
#
|
|
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
|
-
|
|
272
|
-
|
|
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
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
#
|
|
295
|
-
# Structural phases
|
|
296
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
65
|
-
end: { line:
|
|
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
|
-
|
|
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:
|
|
9
|
-
enum:
|
|
10
|
-
flags:
|
|
11
|
-
variant:
|
|
12
|
-
union:
|
|
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.
|
|
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.
|
|
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.
|
|
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: []
|