mt-lang 0.3.34 → 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/README.md +2 -2
- data/docs/index.html +5 -5
- data/docs/language-design.md +3 -3
- data/docs/language-manual.md +1 -1
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +12 -2
- data/lib/milk_tea/bindings/bindgen.rb +5 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +4 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +25 -3
- data/lib/milk_tea/core/c_backend/reinterpret.rb +2 -2
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +15 -2
- data/lib/milk_tea/core/c_backend/type_collectors.rb +39 -2
- data/lib/milk_tea/core/c_backend.rb +12 -0
- data/lib/milk_tea/core/compile_time.rb +109 -74
- data/lib/milk_tea/core/lexer.rb +12 -0
- data/lib/milk_tea/core/lowering/block.rb +6 -7
- data/lib/milk_tea/core/lowering/declarations.rb +40 -1
- data/lib/milk_tea/core/lowering/loops.rb +25 -1
- data/lib/milk_tea/core/lowering/resolve.rb +13 -6
- data/lib/milk_tea/core/lowering/utils.rb +6 -2
- 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/statements.rb +35 -7
- data/lib/milk_tea/core/parser.rb +12 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +4 -1
- 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/name_resolution.rb +12 -2
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +10 -10
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
- 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
- data/lib/milk_tea/tooling/linter/visitors.rb +4 -1
- data/std/box2d.mt +14 -14
- data/std/c/box2d.mt +19 -19
- metadata +3 -3
|
@@ -186,6 +186,7 @@ module MilkTea
|
|
|
186
186
|
mutable: false,
|
|
187
187
|
pointer: false,
|
|
188
188
|
const_value: element,
|
|
189
|
+
substitute_const_value: true,
|
|
189
190
|
)
|
|
190
191
|
emit_stmts, other_stmts = statement.body.partition { |s| s.is_a?(AST::EmitStmt) }
|
|
191
192
|
emit_stmts.each do |emit_stmt|
|
|
@@ -486,13 +487,11 @@ module MilkTea
|
|
|
486
487
|
expected_type: return_type,
|
|
487
488
|
contextual_int_to_float: contextual_int_to_float_target?(return_type),
|
|
488
489
|
) : nil
|
|
489
|
-
if prepared_cleanups.any? && cstr_trackable_type?(return_type)
|
|
490
|
-
raise LoweringError.new("formatted string temporaries cannot be returned as borrowed text; use std.fmt.format(f\"...\") when ownership must escape",
|
|
491
|
-
line: statement.line, column: statement.column, path: @ctx.current_analysis_path)
|
|
492
|
-
end
|
|
493
|
-
|
|
494
490
|
prepared_cleanup_list = prepared_cleanups.flat_map(&:itself)
|
|
495
|
-
if prepared_cleanup_list.any? &&
|
|
491
|
+
if prepared_cleanup_list.any? && (cstr_trackable_type?(return_type) ||
|
|
492
|
+
(return_type.is_a?(Types::Struct) && struct_contains_string_field?(return_type)))
|
|
493
|
+
# A dynamic f-string temp owns its heap buffer; returning it transfers
|
|
494
|
+
# ownership to the caller, so the format release must not run.
|
|
496
495
|
prepared_cleanup_list = prepared_cleanup_list.reject { |stmt| stmt.is_a?(IR::ExpressionStmt) && stmt.expression.is_a?(IR::Call) && stmt.expression.callee == "mt_format_str_release" }
|
|
497
496
|
end
|
|
498
497
|
cleanup = prepared_cleanup_list + cleanup_statements(local_defers, active_defers)
|
|
@@ -793,7 +792,7 @@ module MilkTea
|
|
|
793
792
|
else_body: nil,
|
|
794
793
|
)
|
|
795
794
|
end
|
|
796
|
-
local_defers.concat(prepared_cleanups)
|
|
795
|
+
local_defers.concat(reject_format_releases_for_assignment(prepared_cleanups, storage_type))
|
|
797
796
|
if contains_proc_storage_type?(storage_type)
|
|
798
797
|
local_value = IR::Name.new(name: linkage_name, type: storage_type, pointer: false)
|
|
799
798
|
local_defers << lower_proc_nullable_release_statements(local_value, storage_type)
|
|
@@ -23,6 +23,7 @@ module MilkTea
|
|
|
23
23
|
expanded_declarations.grep(AST::ConstDecl).filter_map do |decl|
|
|
24
24
|
type = @ctx.values.fetch(decl.name).type
|
|
25
25
|
const_value = @ctx.values.fetch(decl.name).const_value
|
|
26
|
+
ensure_registered_storage_type_types(type)
|
|
26
27
|
|
|
27
28
|
next if type == Types::BUILTIN_TYPE_META_TYPE
|
|
28
29
|
|
|
@@ -35,7 +36,7 @@ module MilkTea
|
|
|
35
36
|
value = lower_const_value_literal(type, const_value)
|
|
36
37
|
end
|
|
37
38
|
elsif decl.block_body || decl.value.is_a?(AST::ExpressionList)
|
|
38
|
-
raise LoweringError.new("constant #{decl.name} has no compile-time value", line: decl.line, column: decl.column)
|
|
39
|
+
raise LoweringError.new("constant #{decl.name} has no compile-time value", line: decl.line, column: decl.column, path: @ctx.current_analysis_path)
|
|
39
40
|
else
|
|
40
41
|
value = lower_static_storage_initializer(decl.value, env: empty_env, expected_type: type)
|
|
41
42
|
if (decl.value.is_a?(AST::Call) || decl.value.is_a?(AST::Specialization)) && static_initializer_ir_has_call?(value)
|
|
@@ -78,6 +79,43 @@ module MilkTea
|
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
## Register C struct typedefs for every tuple type reachable from a
|
|
83
|
+
## module-storage type (consts and globals). `ensure_tuple_struct` is
|
|
84
|
+
## normally called while lowering tuple *expressions*, so a tuple that
|
|
85
|
+
## only appears in static storage never gets its `mt_tuple_...` typedef
|
|
86
|
+
## emitted; the C compiler then fails with "unknown type name".
|
|
87
|
+
def ensure_registered_storage_type_types(type, seen = {})
|
|
88
|
+
return if type.nil? || seen[type]
|
|
89
|
+
|
|
90
|
+
seen[type] = true
|
|
91
|
+
if type.is_a?(Types::Tuple)
|
|
92
|
+
ensure_tuple_struct(type)
|
|
93
|
+
type.element_types.each { |element_type| ensure_registered_storage_type_types(element_type, seen) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
case type
|
|
97
|
+
when Types::Nullable
|
|
98
|
+
ensure_registered_storage_type_types(type.base, seen)
|
|
99
|
+
when Types::Span
|
|
100
|
+
ensure_registered_storage_type_types(type.element_type, seen)
|
|
101
|
+
when Types::Task
|
|
102
|
+
ensure_registered_storage_type_types(type.result_type, seen)
|
|
103
|
+
when Types::GenericInstance, Types::StructInstance, Types::VariantInstance
|
|
104
|
+
type.arguments.each do |argument|
|
|
105
|
+
ensure_registered_storage_type_types(argument, seen) unless argument.is_a?(Types::LiteralTypeArg)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if type.respond_to?(:fields) && type.fields
|
|
110
|
+
type.fields.each_value { |field_type| ensure_registered_storage_type_types(field_type, seen) }
|
|
111
|
+
end
|
|
112
|
+
if type.respond_to?(:arms)
|
|
113
|
+
type.arms.each_value do |arm_fields|
|
|
114
|
+
arm_fields.each_value { |field_type| ensure_registered_storage_type_types(field_type, seen) }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
81
119
|
def lower_const_value_literal(type, const_value)
|
|
82
120
|
case const_value
|
|
83
121
|
when Integer
|
|
@@ -130,6 +168,7 @@ module MilkTea
|
|
|
130
168
|
next unless decl.is_a?(AST::VarDecl) || decl.is_a?(AST::EventDecl)
|
|
131
169
|
|
|
132
170
|
type = @ctx.values.fetch(decl.name).type
|
|
171
|
+
ensure_registered_storage_type_types(type)
|
|
133
172
|
ensure_event_runtime(type) if type.is_a?(Types::Event)
|
|
134
173
|
value = if decl.is_a?(AST::VarDecl) && decl.value
|
|
135
174
|
lower_static_storage_initializer(decl.value, env: empty_env, expected_type: type)
|
|
@@ -643,6 +643,8 @@ module MilkTea
|
|
|
643
643
|
end
|
|
644
644
|
end
|
|
645
645
|
|
|
646
|
+
written_scalar_captures = captures.select { |c| written_names.include?(c.name) && !array_capture_names.include?(c.name) }
|
|
647
|
+
|
|
646
648
|
@artifacts.synthetic_structs << IR::StructDecl.new(
|
|
647
649
|
name: cap_struct_c_name, linkage_name: cap_struct_c_name,
|
|
648
650
|
fields: cap_fields, packed: false, alignment: nil,
|
|
@@ -674,6 +676,14 @@ module MilkTea
|
|
|
674
676
|
|
|
675
677
|
rewritten_body = array_capture_names.empty? ? block_body : rewrite_pfor_array_captures(block_body, array_capture_names)
|
|
676
678
|
worker_body.concat(rewritten_body)
|
|
679
|
+
|
|
680
|
+
written_scalar_captures.each do |c|
|
|
681
|
+
worker_body << IR::Assignment.new(
|
|
682
|
+
target: IR::Member.new(receiver: cap_name_ir, member: c.name, type: c.type),
|
|
683
|
+
operator: "=",
|
|
684
|
+
value: IR::Name.new(name: c.name, type: c.type, pointer: false),
|
|
685
|
+
)
|
|
686
|
+
end
|
|
677
687
|
end
|
|
678
688
|
|
|
679
689
|
@artifacts.synthetic_functions << IR::Function.new(
|
|
@@ -715,7 +725,7 @@ module MilkTea
|
|
|
715
725
|
)
|
|
716
726
|
end
|
|
717
727
|
|
|
718
|
-
{ worker_c_name:, cap_local_name:, cap_struct_type:, cap_init:, capture_names: Set.new(captures.map(&:name)), written_names:, captureless: }
|
|
728
|
+
{ worker_c_name:, cap_local_name:, cap_struct_type:, cap_init:, capture_names: Set.new(captures.map(&:name)), written_names:, written_scalar_captures: captureless ? [] : written_scalar_captures, captureless: }
|
|
719
729
|
end
|
|
720
730
|
|
|
721
731
|
validate_pfor_write_conflicts!(block_infos)
|
|
@@ -768,6 +778,20 @@ module MilkTea
|
|
|
768
778
|
type: void_type,
|
|
769
779
|
))
|
|
770
780
|
|
|
781
|
+
block_infos.each do |info|
|
|
782
|
+
info[:written_scalar_captures].each do |c|
|
|
783
|
+
call_site << IR::Assignment.new(
|
|
784
|
+
target: IR::Name.new(name: c.name, type: c.type, pointer: false),
|
|
785
|
+
operator: "=",
|
|
786
|
+
value: IR::Member.new(
|
|
787
|
+
receiver: IR::Name.new(name: info[:cap_local_name], type: info[:cap_struct_type], pointer: false),
|
|
788
|
+
member: c.name,
|
|
789
|
+
type: c.type,
|
|
790
|
+
),
|
|
791
|
+
)
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
771
795
|
IR::BlockStmt.new(body: call_site)
|
|
772
796
|
end
|
|
773
797
|
|
|
@@ -745,7 +745,7 @@ module MilkTea
|
|
|
745
745
|
end
|
|
746
746
|
return function_type_for_name(expression.name) if @ctx.functions.key?(expression.name)
|
|
747
747
|
|
|
748
|
-
raise LoweringError.new("unknown identifier #{expression.name}", line: expression.line, column: expression.column)
|
|
748
|
+
raise LoweringError.new("unknown identifier #{expression.name}", line: expression.line, column: expression.column, path: @ctx.current_analysis_path)
|
|
749
749
|
when AST::MemberAccess
|
|
750
750
|
if (type_expr = resolve_type_expression(expression.receiver))
|
|
751
751
|
member_type = resolve_type_member(type_expr, expression.member)
|
|
@@ -784,7 +784,7 @@ module MilkTea
|
|
|
784
784
|
end
|
|
785
785
|
|
|
786
786
|
return receiver_type.field(expression.member) if receiver_type.respond_to?(:field)
|
|
787
|
-
raise LoweringError.new("unknown member #{expression.member}", line: expression.line, column: expression.column)
|
|
787
|
+
raise LoweringError.new("unknown member #{expression.member}", line: expression.line, column: expression.column, path: @ctx.current_analysis_path)
|
|
788
788
|
when AST::IndexAccess
|
|
789
789
|
receiver_type = infer_expression_type(expression.receiver, env:)
|
|
790
790
|
index_type = infer_expression_type(expression.index, env:)
|
|
@@ -1677,7 +1677,15 @@ module MilkTea
|
|
|
1677
1677
|
evaluate_attribute_arg_call(expression.arguments, env:)
|
|
1678
1678
|
else
|
|
1679
1679
|
callee_name = expression.callee.callee.is_a?(AST::Identifier) ? expression.callee.callee.name : nil
|
|
1680
|
-
if callee_name
|
|
1680
|
+
if callee_name == "array"
|
|
1681
|
+
values = []
|
|
1682
|
+
expression.arguments.each do |argument|
|
|
1683
|
+
val = compile_time_const_value(argument.value, env:)
|
|
1684
|
+
return nil unless val
|
|
1685
|
+
values << val
|
|
1686
|
+
end
|
|
1687
|
+
values
|
|
1688
|
+
elsif callee_name
|
|
1681
1689
|
func = @ctx.functions[callee_name]
|
|
1682
1690
|
if func&.ast&.respond_to?(:const) && func.ast.const
|
|
1683
1691
|
evaluate_const_function_body_lower(func, expression.arguments)
|
|
@@ -1842,9 +1850,8 @@ module MilkTea
|
|
|
1842
1850
|
|
|
1843
1851
|
evaluator = ConstFnLowerEvaluator.new(self)
|
|
1844
1852
|
ctx = CompileTime::BlockContext.new(evaluator, initial_variables: initial_vars)
|
|
1845
|
-
ctx.evaluate_block(func.ast.body, scopes: nil)
|
|
1846
|
-
|
|
1847
|
-
e.value
|
|
1853
|
+
result = ctx.evaluate_block(func.ast.body, scopes: nil)
|
|
1854
|
+
result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
|
|
1848
1855
|
end
|
|
1849
1856
|
|
|
1850
1857
|
class ConstFnLowerEvaluator
|
|
@@ -459,8 +459,8 @@ module MilkTea
|
|
|
459
459
|
end
|
|
460
460
|
end
|
|
461
461
|
|
|
462
|
-
def local_binding(type:, linkage_name:, mutable:, pointer:, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false, const_value: nil)
|
|
463
|
-
{ type:, storage_type: storage_type || type, linkage_name:, mutable:, pointer:, projection:, cstr_backed:, cstr_list_backed:, const_value: }
|
|
462
|
+
def local_binding(type:, linkage_name:, mutable:, pointer:, storage_type: nil, projection: nil, cstr_backed: false, cstr_list_backed: false, const_value: nil, substitute_const_value: false)
|
|
463
|
+
{ type:, storage_type: storage_type || type, linkage_name:, mutable:, pointer:, projection:, cstr_backed:, cstr_list_backed:, const_value:, substitute_const_value: }
|
|
464
464
|
end
|
|
465
465
|
|
|
466
466
|
def callable_type?(type)
|
|
@@ -904,6 +904,10 @@ module MilkTea
|
|
|
904
904
|
visible_type = binding[:type]
|
|
905
905
|
projection = binding[:projection]
|
|
906
906
|
|
|
907
|
+
if binding[:substitute_const_value] && !binding[:const_value].nil?
|
|
908
|
+
return lower_const_value_literal(visible_type, binding[:const_value])
|
|
909
|
+
end
|
|
910
|
+
|
|
907
911
|
if projection == :result_success_value
|
|
908
912
|
local_ref = IR::Name.new(name: binding[:linkage_name], type: storage_type, pointer: binding[:pointer])
|
|
909
913
|
return variant_binding_projection_expression(local_ref, storage_type, "success", "value", visible_type)
|
|
@@ -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
|
|
@@ -297,7 +297,7 @@ module MilkTea
|
|
|
297
297
|
expression = nil
|
|
298
298
|
arms = []
|
|
299
299
|
expression = parse_expression
|
|
300
|
-
arms = parse_match_arms(arms)
|
|
300
|
+
arms = normalize_mixed_match_arms(parse_match_arms(arms))
|
|
301
301
|
if arms.first&.is_a?(AST::MatchExprArm)
|
|
302
302
|
expr = AST::MatchExpr.new(expression:, arms:, line:, column: token.column, length: token.lexeme.length)
|
|
303
303
|
AST::ExpressionStmt.new(expression: expr, line:)
|
|
@@ -311,12 +311,13 @@ module MilkTea
|
|
|
311
311
|
recovered_arms = synchronize_to_match_arm_boundary
|
|
312
312
|
target_line = line
|
|
313
313
|
if recovered_arms
|
|
314
|
+
combined_arms = normalize_mixed_match_arms(arms + recovered_arms)
|
|
314
315
|
stmt =
|
|
315
|
-
if
|
|
316
|
-
expr = AST::MatchExpr.new(expression: expression || recovery_error_expr(e), arms:
|
|
316
|
+
if combined_arms.first&.is_a?(AST::MatchExprArm)
|
|
317
|
+
expr = AST::MatchExpr.new(expression: expression || recovery_error_expr(e), arms: combined_arms, line: target_line, column: token.column, length: token.lexeme.length)
|
|
317
318
|
AST::ExpressionStmt.new(expression: expr, line: target_line)
|
|
318
319
|
else
|
|
319
|
-
AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms:
|
|
320
|
+
AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: combined_arms, line: target_line, column: token.column, length: token.lexeme.length)
|
|
320
321
|
end
|
|
321
322
|
return stmt
|
|
322
323
|
end
|
|
@@ -335,6 +336,33 @@ module MilkTea
|
|
|
335
336
|
arms
|
|
336
337
|
end
|
|
337
338
|
|
|
339
|
+
## A statement match may mix inline value arms (`pattern: expr`) with
|
|
340
|
+
## block arms. The inline form is a single expression statement, so when
|
|
341
|
+
## any arm is a block the inline arms are rewritten to block arms with a
|
|
342
|
+
## single expression-statement body. This keeps MatchStmt arms
|
|
343
|
+
## homogeneous (all MatchArm) and MatchExpr arms homogeneous (all
|
|
344
|
+
## MatchExprArm); the sema / control-flow / lowering consumers rely on
|
|
345
|
+
## that invariant.
|
|
346
|
+
def normalize_mixed_match_arms(arms)
|
|
347
|
+
return arms unless arms.any? { |arm| arm.is_a?(AST::MatchExprArm) } && arms.any? { |arm| arm.is_a?(AST::MatchArm) }
|
|
348
|
+
|
|
349
|
+
arms.map do |arm|
|
|
350
|
+
if arm.is_a?(AST::MatchExprArm)
|
|
351
|
+
AST::MatchArm.new(
|
|
352
|
+
pattern: arm.pattern,
|
|
353
|
+
binding_name: arm.binding_name,
|
|
354
|
+
binding_line: arm.binding_line,
|
|
355
|
+
binding_column: arm.binding_column,
|
|
356
|
+
body: [AST::ExpressionStmt.new(expression: arm.value, line: arm.line)],
|
|
357
|
+
line: arm.line,
|
|
358
|
+
column: arm.column,
|
|
359
|
+
)
|
|
360
|
+
else
|
|
361
|
+
arm
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
338
366
|
def parse_match_arm_body(arms = [])
|
|
339
367
|
skip_newlines
|
|
340
368
|
until check(:dedent) || eof?
|
|
@@ -652,7 +680,7 @@ module MilkTea
|
|
|
652
680
|
token = previous
|
|
653
681
|
line = token.line
|
|
654
682
|
discriminant = parse_expression
|
|
655
|
-
branches = parse_match_arms([])
|
|
683
|
+
branches = normalize_mixed_match_arms(parse_match_arms([]))
|
|
656
684
|
else_body = if check(:else)
|
|
657
685
|
if check_next(:newline) || check_next(:indent)
|
|
658
686
|
parse_else_branch_body
|
|
@@ -734,14 +762,14 @@ module MilkTea
|
|
|
734
762
|
line = token.line
|
|
735
763
|
arms = []
|
|
736
764
|
expression = parse_expression
|
|
737
|
-
arms = parse_match_arms(arms)
|
|
765
|
+
arms = normalize_mixed_match_arms(parse_match_arms(arms))
|
|
738
766
|
AST::MatchStmt.new(expression:, arms:, inline: true, line:, column: token.column, length: token.lexeme.length)
|
|
739
767
|
rescue ParseError => e
|
|
740
768
|
raise unless @recovery_errors
|
|
741
769
|
|
|
742
770
|
@recovery_errors << e
|
|
743
771
|
recovered_arms = synchronize_to_match_arm_boundary
|
|
744
|
-
return AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, inline: true, line:, column: token.column, length: token.lexeme.length) if recovered_arms
|
|
772
|
+
return AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: normalize_mixed_match_arms(arms + recovered_arms), inline: true, line:, column: token.column, length: token.lexeme.length) if recovered_arms
|
|
745
773
|
|
|
746
774
|
recovery_error_stmt(e)
|
|
747
775
|
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
|
|
@@ -211,7 +211,8 @@ module MilkTea
|
|
|
211
211
|
when IR::NullableSpanIndex
|
|
212
212
|
"nullable_span_index<#{expression.receiver_type}>(#{render_expression(expression.receiver)}, #{render_expression(expression.index)})"
|
|
213
213
|
when IR::Call
|
|
214
|
-
|
|
214
|
+
callee_text = expression.callee.is_a?(String) ? expression.callee : render_expression(expression.callee)
|
|
215
|
+
wrap("#{callee_text}(#{expression.arguments.map { |argument| render_expression(argument) }.join(', ')})", parent_precedence, POSTFIX_PRECEDENCE)
|
|
215
216
|
when IR::Unary
|
|
216
217
|
operand = render_expression(expression.operand, UNARY_PRECEDENCE)
|
|
217
218
|
text = expression.operator == "not" ? "not #{operand}" : "#{expression.operator}#{operand}"
|
|
@@ -258,6 +259,8 @@ module MilkTea
|
|
|
258
259
|
end
|
|
259
260
|
when IR::ArrayLiteral
|
|
260
261
|
"#{expression.type}(#{expression.elements.map { |element| render_expression(element) }.join(', ')})"
|
|
262
|
+
when IR::SimdLaneWith
|
|
263
|
+
"#{render_expression(expression.src)}.with(#{render_expression(expression.index)}, #{render_expression(expression.value)})"
|
|
261
264
|
when IR::Assignment
|
|
262
265
|
"#{render_expression(expression.target)} #{expression.operator} #{render_expression(expression.value)}"
|
|
263
266
|
else
|
|
@@ -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
|
|