mt-lang 0.3.17 → 0.3.22
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/expressions.rb +34 -23
- data/lib/milk_tea/core/c_backend/feature_detection.rb +25 -45
- data/lib/milk_tea/core/c_backend/type_collectors.rb +18 -30
- data/lib/milk_tea/core/c_backend/type_declaration.rb +0 -6
- data/lib/milk_tea/core/c_backend.rb +49 -39
- data/lib/milk_tea/core/compile_time.rb +98 -72
- data/lib/milk_tea/core/control_flow/builder.rb +2 -1
- data/lib/milk_tea/core/intrinsics.rb +7 -0
- data/lib/milk_tea/core/lexer.rb +46 -35
- data/lib/milk_tea/core/lowering/block.rb +9 -0
- data/lib/milk_tea/core/lowering/calls.rb +2 -0
- data/lib/milk_tea/core/lowering/declarations.rb +1 -1
- data/lib/milk_tea/core/lowering/functions.rb +11 -8
- data/lib/milk_tea/core/lowering/resolve.rb +11 -4
- data/lib/milk_tea/core/lowering/scans.rb +13 -18
- data/lib/milk_tea/core/module_binder.rb +19 -15
- data/lib/milk_tea/core/module_loader.rb +212 -59
- data/lib/milk_tea/core/module_path_resolver.rb +8 -7
- data/lib/milk_tea/core/parser/declarations.rb +43 -19
- data/lib/milk_tea/core/parser/expressions.rb +5 -8
- data/lib/milk_tea/core/parser/statements.rb +5 -5
- data/lib/milk_tea/core/parser.rb +26 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +24 -31
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +20 -23
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +146 -92
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +19 -41
- data/lib/milk_tea/core/semantic_analyzer.rb +56 -37
- data/lib/milk_tea/core/types.rb +12 -17
- data/lib/milk_tea/lsp/diagnostics.rb +13 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +6 -0
- data/lib/milk_tea/tooling/cli/commands/bindgen.rb +11 -0
- data/lib/milk_tea/tooling/cli/commands/build.rb +37 -0
- data/lib/milk_tea/tooling/cli/commands/cache.rb +46 -0
- data/lib/milk_tea/tooling/cli/commands/check.rb +106 -0
- data/lib/milk_tea/tooling/cli/commands/command_base.rb +8 -0
- data/lib/milk_tea/tooling/cli/commands/completions.rb +48 -0
- data/lib/milk_tea/tooling/cli/commands/dap.rb +58 -0
- data/lib/milk_tea/tooling/cli/commands/debug.rb +77 -0
- data/lib/milk_tea/tooling/cli/commands/deps.rb +17 -0
- data/lib/milk_tea/tooling/cli/commands/docs.rb +58 -0
- data/lib/milk_tea/tooling/cli/commands/emit_c.rb +64 -0
- data/lib/milk_tea/tooling/cli/commands/format.rb +199 -0
- data/lib/milk_tea/tooling/cli/commands/lex.rb +46 -0
- data/lib/milk_tea/tooling/cli/commands/lint.rb +248 -0
- data/lib/milk_tea/tooling/cli/commands/lower.rb +50 -0
- data/lib/milk_tea/tooling/cli/commands/lsp.rb +43 -0
- data/lib/milk_tea/tooling/cli/commands/new.rb +26 -0
- data/lib/milk_tea/tooling/cli/commands/parse.rb +51 -0
- data/lib/milk_tea/tooling/cli/commands/run.rb +99 -0
- data/lib/milk_tea/tooling/cli/commands/snapshot.rb +117 -0
- data/lib/milk_tea/tooling/cli/commands/test.rb +557 -0
- data/lib/milk_tea/tooling/cli/commands/toolchain.rb +16 -0
- data/lib/milk_tea/tooling/cli.rb +101 -1893
- data/lib/milk_tea/tooling/sexpr_dumper.rb +7 -7
- metadata +24 -2
|
@@ -154,15 +154,37 @@ module MilkTea
|
|
|
154
154
|
check_program(path).root_analysis
|
|
155
155
|
end
|
|
156
156
|
|
|
157
|
-
def
|
|
157
|
+
def with_check_context(path, &block)
|
|
158
158
|
Types::Registry.reset!
|
|
159
159
|
requested_path = File.expand_path(path)
|
|
160
160
|
previous_platform = @platform
|
|
161
161
|
@platform ||= self.class.platform_suffix_for_path(requested_path)
|
|
162
162
|
root_path = self.class.resolve_source_path(requested_path, platform: @platform, error_class: ModuleLoadError)
|
|
163
|
+
block.call(root_path)
|
|
164
|
+
ensure
|
|
165
|
+
@platform = previous_platform
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def check_program(path)
|
|
169
|
+
with_check_context(path) do |root_path|
|
|
170
|
+
check_program_parallel(root_path)
|
|
171
|
+
build_program(root_path)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
# Variant of #check_program that collects errors instead of raising so
|
|
176
|
+
# diagnostic paths (check, debug, LSP) can report all issues at once.
|
|
177
|
+
# Returns { root_analysis: Analysis|nil, errors: [SemanticError], module_name: String|nil }.
|
|
178
|
+
def check_program_collecting(path)
|
|
179
|
+
with_check_context(path) do |root_path|
|
|
180
|
+
errors = []
|
|
181
|
+
check_program_parallel(root_path, collecting_errors: errors)
|
|
182
|
+
root_analysis = @analysis_cache[root_path]
|
|
183
|
+
{ root_analysis: root_analysis, errors: errors, module_name: root_analysis&.module_name }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
165
186
|
|
|
187
|
+
def build_program(root_path)
|
|
166
188
|
root_analysis = @analysis_cache.fetch(root_path)
|
|
167
189
|
analyses_by_module_name = @analysis_cache.each_value.each_with_object({}) do |analysis, modules|
|
|
168
190
|
next unless analysis.module_name
|
|
@@ -176,11 +198,27 @@ module MilkTea
|
|
|
176
198
|
analyses_by_path: @analysis_cache.dup.freeze,
|
|
177
199
|
analyses_by_module_name: analyses_by_module_name.freeze,
|
|
178
200
|
)
|
|
179
|
-
ensure
|
|
180
|
-
@platform = previous_platform
|
|
181
201
|
end
|
|
182
202
|
|
|
183
|
-
def
|
|
203
|
+
def node_in_cycle?(node, graph)
|
|
204
|
+
# Start from each immediate successor to avoid the trivial self-path
|
|
205
|
+
successors = graph[node] || []
|
|
206
|
+
successors.each do |next_node|
|
|
207
|
+
next unless graph.key?(next_node)
|
|
208
|
+
return true if dfs_reachable_from(next_node, node, graph, {})
|
|
209
|
+
end
|
|
210
|
+
false
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def dfs_reachable_from(current, target, graph, visited)
|
|
214
|
+
return false if visited[current]
|
|
215
|
+
return true if current == target
|
|
216
|
+
|
|
217
|
+
visited[current] = true
|
|
218
|
+
(graph[current] || []).any? { |neighbor| dfs_reachable_from(neighbor, target, graph, visited) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def check_program_parallel(root_path, collecting_errors: nil)
|
|
184
222
|
# Phase 1: Parse all transitive modules (sequential)
|
|
185
223
|
parse_all(root_path)
|
|
186
224
|
|
|
@@ -194,12 +232,24 @@ module MilkTea
|
|
|
194
232
|
graph[resolved_path] = deps
|
|
195
233
|
end
|
|
196
234
|
|
|
197
|
-
# Phase 3: Topological sort into independent levels
|
|
235
|
+
# Phase 3: Topological sort into independent levels.
|
|
236
|
+
# Nodes that cannot be sorted are candidates for cycle membership,
|
|
237
|
+
# but not all of them are actually part of a cycle — some are just
|
|
238
|
+
# dependencies downstream from the cycle. We separate true cycle
|
|
239
|
+
# members (nodes reachable from themselves) from tail nodes.
|
|
198
240
|
levels = topo_sort_levels(graph)
|
|
199
241
|
|
|
200
|
-
# Pre-register forward bindings for cycle members
|
|
201
242
|
all_checked = levels.flatten.to_set
|
|
202
|
-
|
|
243
|
+
unsorted = graph.keys.reject { |p| all_checked.include?(p) }
|
|
244
|
+
|
|
245
|
+
# Among unsorted nodes, identify true cycle members: nodes that can
|
|
246
|
+
# reach themselves through the graph (belong to a strongly connected
|
|
247
|
+
# component of size > 1). Tail nodes that only depend on cycle members
|
|
248
|
+
# but have no path back to themselves are NOT cycle members.
|
|
249
|
+
cycle_members = unsorted.select { |node| node_in_cycle?(node, graph) }
|
|
250
|
+
tail_members = unsorted - cycle_members
|
|
251
|
+
|
|
252
|
+
# Pre-register forward bindings only for true cycle members
|
|
203
253
|
cycle_members.each do |resolved_path|
|
|
204
254
|
ast = @parse_cache[resolved_path]
|
|
205
255
|
@forward_bindings[resolved_path] = create_forward_binding(ast)
|
|
@@ -209,10 +259,14 @@ module MilkTea
|
|
|
209
259
|
# Pass 1: Check with forward bindings — registers type declarations.
|
|
210
260
|
# SemanticError is expected (forward types lack fields/constructors)
|
|
211
261
|
# but ModuleLoadError indicates a broken dependency graph.
|
|
262
|
+
# Do NOT collect Pass 1 errors — they are temporary failures caused
|
|
263
|
+
# by incomplete forward bindings that Pass 2 resolves.
|
|
212
264
|
cycle_members.each do |resolved_path|
|
|
213
265
|
check_path(resolved_path)
|
|
214
266
|
rescue SemanticError
|
|
215
|
-
# Forward types can't satisfy constructors
|
|
267
|
+
# Forward types can't satisfy constructors / functions / methods.
|
|
268
|
+
# Re-check in collecting mode to capture the analysis for population.
|
|
269
|
+
capture_analysis_for_cycle_member(resolved_path)
|
|
216
270
|
end
|
|
217
271
|
|
|
218
272
|
# Update forward type objects in-place with field/arm info from the
|
|
@@ -236,26 +290,45 @@ module MilkTea
|
|
|
236
290
|
if real_type.respond_to?(:arms) && fw_type.respond_to?(:define_arms)
|
|
237
291
|
fw_type.define_arms(real_type.arms) unless real_type.arms.empty?
|
|
238
292
|
end
|
|
293
|
+
if real_type.respond_to?(:members) && fw_type.respond_to?(:define_members)
|
|
294
|
+
member_names = real_type.members
|
|
295
|
+
unless member_names.empty?
|
|
296
|
+
fw_type.define_members(real_type.backing_type, member_names)
|
|
297
|
+
values = member_names.each_with_object({}) { |n, h| h[n] = real_type.member_value(n) }
|
|
298
|
+
fw_type.define_member_values(values)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
239
301
|
end
|
|
240
302
|
end
|
|
241
303
|
|
|
242
|
-
#
|
|
243
|
-
#
|
|
304
|
+
# Replace forward bindings with fully populated bindings so that
|
|
305
|
+
# Pass 2 import resolution sees functions, methods, values, and
|
|
306
|
+
# interfaces — not just type skeletons.
|
|
307
|
+
populate_full_forward_bindings(cycle_members)
|
|
308
|
+
|
|
309
|
+
# Pass 2: Re-check with populated bindings — now cycle members see
|
|
310
|
+
# complete module information for each other.
|
|
244
311
|
cycle_members.each do |resolved_path|
|
|
245
312
|
previous = @analysis_cache.delete(resolved_path)
|
|
246
313
|
check_path(resolved_path)
|
|
247
|
-
rescue SemanticError
|
|
314
|
+
rescue SemanticError => e
|
|
248
315
|
@analysis_cache[resolved_path] = previous if previous
|
|
316
|
+
collecting_errors << e if collecting_errors
|
|
249
317
|
end
|
|
250
318
|
@forward_bindings.clear
|
|
251
319
|
|
|
252
|
-
# Phase 5: Check acyclic modules (they see fully-checked analyses for all imports)
|
|
253
|
-
|
|
320
|
+
# Phase 5: Check acyclic modules (they see fully-checked analyses for all imports).
|
|
321
|
+
# Also check tail members — nodes that were in the unsorted set but
|
|
322
|
+
# are not themselves part of a cycle; they just depended on cycle members.
|
|
323
|
+
(levels + [tail_members]).each do |level_paths|
|
|
254
324
|
if level_paths.length == 1
|
|
255
325
|
check_path(level_paths.first)
|
|
256
|
-
|
|
326
|
+
elsif level_paths.any?
|
|
257
327
|
check_level_parallel(level_paths)
|
|
258
328
|
end
|
|
329
|
+
rescue SemanticError => e
|
|
330
|
+
raise unless collecting_errors
|
|
331
|
+
collecting_errors << e
|
|
259
332
|
end
|
|
260
333
|
end
|
|
261
334
|
|
|
@@ -317,22 +390,62 @@ module MilkTea
|
|
|
317
390
|
end
|
|
318
391
|
|
|
319
392
|
def imported_modules_for_ast(ast, importer_path: nil)
|
|
393
|
+
resolve_imports_for_ast(ast, importer_path:, collecting: false)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def imported_modules_for_ast_collecting_errors(ast, importer_path: nil)
|
|
397
|
+
resolve_imports_for_ast(ast, importer_path:, collecting: true)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def resolve_imports_for_ast(ast, importer_path:, collecting: false)
|
|
320
401
|
modules = {}
|
|
402
|
+
errors = []
|
|
403
|
+
|
|
404
|
+
if @import_resolve_depth
|
|
405
|
+
@import_resolve_depth += 1
|
|
406
|
+
if @import_resolve_depth > 80
|
|
407
|
+
raise SemanticError.new("import resolution depth exceeded")
|
|
408
|
+
end
|
|
409
|
+
else
|
|
410
|
+
@import_resolve_depth = 1
|
|
411
|
+
end
|
|
321
412
|
|
|
322
413
|
ast.imports.each do |import|
|
|
323
|
-
|
|
414
|
+
begin
|
|
415
|
+
import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
|
|
324
416
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
417
|
+
if @forward_bindings.key?(import_path)
|
|
418
|
+
modules[import.path.to_s] = @forward_bindings[import_path]
|
|
419
|
+
else
|
|
420
|
+
import_analysis = collecting ? check_path_collecting_errors(import_path) : check_path(import_path)
|
|
421
|
+
modules[import.path.to_s] = @binder.module_binding(import_analysis)
|
|
422
|
+
end
|
|
423
|
+
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
424
|
+
raise unless collecting
|
|
425
|
+
|
|
426
|
+
handle_circular_import_in_collecting_mode(import, import_path, modules, errors, e)
|
|
330
427
|
end
|
|
331
428
|
end
|
|
332
429
|
|
|
333
|
-
|
|
334
|
-
|
|
430
|
+
begin
|
|
431
|
+
@async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: collecting)
|
|
432
|
+
rescue ModuleLoadError, PackageLockError => e
|
|
433
|
+
raise unless collecting
|
|
434
|
+
errors << ImportResolutionError.new(import: nil, error: e)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
begin
|
|
438
|
+
@prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: collecting)
|
|
439
|
+
rescue ModuleLoadError, PackageLockError => e
|
|
440
|
+
raise unless collecting
|
|
441
|
+
errors << ImportResolutionError.new(import: nil, error: e)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
return ImportResolution.new(modules: modules.freeze, errors: errors.freeze) if collecting
|
|
445
|
+
|
|
335
446
|
modules.freeze
|
|
447
|
+
ensure
|
|
448
|
+
@import_resolve_depth -= 1 if @import_resolve_depth
|
|
336
449
|
end
|
|
337
450
|
|
|
338
451
|
def build_global_import_index(ast)
|
|
@@ -358,40 +471,6 @@ module MilkTea
|
|
|
358
471
|
index
|
|
359
472
|
end
|
|
360
473
|
|
|
361
|
-
def imported_modules_for_ast_collecting_errors(ast, importer_path: nil)
|
|
362
|
-
modules = {}
|
|
363
|
-
errors = []
|
|
364
|
-
|
|
365
|
-
ast.imports.each do |import|
|
|
366
|
-
begin
|
|
367
|
-
import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
|
|
368
|
-
|
|
369
|
-
if @forward_bindings.key?(import_path)
|
|
370
|
-
modules[import.path.to_s] = @forward_bindings[import_path]
|
|
371
|
-
else
|
|
372
|
-
import_analysis = check_path_collecting_errors(import_path)
|
|
373
|
-
modules[import.path.to_s] = @binder.module_binding(import_analysis)
|
|
374
|
-
end
|
|
375
|
-
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
376
|
-
errors << ImportResolutionError.new(import:, error: e)
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
begin
|
|
381
|
-
@async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: true)
|
|
382
|
-
rescue ModuleLoadError, PackageLockError => e
|
|
383
|
-
errors << ImportResolutionError.new(import: nil, error: e)
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
begin
|
|
387
|
-
@prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: true)
|
|
388
|
-
rescue ModuleLoadError, PackageLockError => e
|
|
389
|
-
errors << ImportResolutionError.new(import: nil, error: e)
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
ImportResolution.new(modules: modules.freeze, errors: errors.freeze)
|
|
393
|
-
end
|
|
394
|
-
|
|
395
474
|
# Errors collected per analyzed import path during collecting-mode checks.
|
|
396
475
|
# Populated by #check_path_collecting_errors; used by the CLI `check` command
|
|
397
476
|
# to surface errors in a single file's imported modules (otherwise only
|
|
@@ -527,6 +606,74 @@ module MilkTea
|
|
|
527
606
|
normalized_path == normalized_root || normalized_path.start_with?(normalized_root + File::SEPARATOR)
|
|
528
607
|
end
|
|
529
608
|
|
|
609
|
+
def handle_circular_import_in_collecting_mode(import, import_path, modules, errors, error)
|
|
610
|
+
# When a circular import is detected in collecting mode, create a
|
|
611
|
+
# forward binding for the target module so the importing module can
|
|
612
|
+
# at minimum resolve type references. Without this the import is
|
|
613
|
+
# entirely absent, which causes downstream crashes in module_binding.
|
|
614
|
+
if import_path && error.is_a?(ModuleLoadError) && error.message.start_with?("circular import")
|
|
615
|
+
circular_ast = @parse_cache[import_path] || load_file(import_path)
|
|
616
|
+
if circular_ast
|
|
617
|
+
@forward_bindings[import_path] ||= create_forward_binding(circular_ast)
|
|
618
|
+
modules[import.path.to_s] = @forward_bindings[import_path]
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
errors << ImportResolutionError.new(import:, error:)
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
def capture_analysis_for_cycle_member(resolved_path)
|
|
626
|
+
return if @analysis_cache[resolved_path]
|
|
627
|
+
|
|
628
|
+
ast = @parse_cache[resolved_path]
|
|
629
|
+
return unless ast
|
|
630
|
+
|
|
631
|
+
@checking_paths << resolved_path
|
|
632
|
+
import_result = resolve_imports_for_ast(ast, importer_path: resolved_path, collecting: true)
|
|
633
|
+
result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules: import_result.modules, path: resolved_path)
|
|
634
|
+
@analysis_cache[resolved_path] = result[:analysis] if result[:analysis]
|
|
635
|
+
rescue StandardError
|
|
636
|
+
# Analysis capture is best-effort.
|
|
637
|
+
ensure
|
|
638
|
+
@checking_paths.pop
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def populate_full_forward_bindings(cycle_members)
|
|
642
|
+
cycle_members.each do |resolved_path|
|
|
643
|
+
analysis = @analysis_cache[resolved_path]
|
|
644
|
+
next unless analysis
|
|
645
|
+
|
|
646
|
+
fw_binding = @forward_bindings[resolved_path]
|
|
647
|
+
next unless fw_binding
|
|
648
|
+
|
|
649
|
+
full_binding = @binder.module_binding(analysis)
|
|
650
|
+
merged_types = fw_binding.types.merge(
|
|
651
|
+
full_binding.types.reject { |name, _| fw_binding.types.key?(name) }
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
@forward_bindings[resolved_path] = ModuleBinding.new(
|
|
655
|
+
name: full_binding.name,
|
|
656
|
+
types: merged_types,
|
|
657
|
+
type_declarations: full_binding.type_declarations,
|
|
658
|
+
interfaces: full_binding.interfaces,
|
|
659
|
+
attributes: full_binding.attributes,
|
|
660
|
+
attribute_applications: full_binding.attribute_applications,
|
|
661
|
+
values: full_binding.values,
|
|
662
|
+
functions: full_binding.functions,
|
|
663
|
+
methods: full_binding.methods,
|
|
664
|
+
implemented_interfaces: full_binding.implemented_interfaces,
|
|
665
|
+
imports: full_binding.imports,
|
|
666
|
+
private_types: full_binding.private_types,
|
|
667
|
+
private_interfaces: full_binding.private_interfaces,
|
|
668
|
+
private_attributes: full_binding.private_attributes,
|
|
669
|
+
private_values: full_binding.private_values,
|
|
670
|
+
private_functions: full_binding.private_functions,
|
|
671
|
+
private_methods: full_binding.private_methods,
|
|
672
|
+
private_implemented_interfaces: full_binding.private_implemented_interfaces,
|
|
673
|
+
)
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
530
677
|
def create_forward_binding(ast)
|
|
531
678
|
module_name = ast.module_name.to_s
|
|
532
679
|
types = {}
|
|
@@ -534,15 +681,21 @@ module MilkTea
|
|
|
534
681
|
ast.declarations.each do |decl|
|
|
535
682
|
case decl
|
|
536
683
|
when AST::StructDecl
|
|
537
|
-
types[decl.name] =
|
|
684
|
+
types[decl.name] = decl.type_params.empty? ?
|
|
685
|
+
Types::Struct.new(decl.name, module_name:) :
|
|
686
|
+
Types::GenericStructDefinition.new(decl.name, decl.type_params.map(&:name))
|
|
538
687
|
when AST::VariantDecl
|
|
539
|
-
types[decl.name] =
|
|
688
|
+
types[decl.name] = decl.type_params.empty? ?
|
|
689
|
+
Types::Variant.new(decl.name, module_name:) :
|
|
690
|
+
Types::GenericVariantDefinition.new(decl.name, decl.type_params.map(&:name))
|
|
540
691
|
when AST::EnumDecl
|
|
541
692
|
types[decl.name] = Types::Enum.new(decl.name, module_name:)
|
|
542
693
|
when AST::FlagsDecl
|
|
543
694
|
types[decl.name] = Types::Flags.new(decl.name, module_name:)
|
|
544
695
|
when AST::OpaqueDecl
|
|
545
696
|
types[decl.name] = Types::Opaque.new(decl.name, module_name:, external: false)
|
|
697
|
+
when AST::UnionDecl
|
|
698
|
+
types[decl.name] = Types::Union.new(decl.name, module_name:)
|
|
546
699
|
end
|
|
547
700
|
end
|
|
548
701
|
|
|
@@ -16,19 +16,23 @@ module MilkTea
|
|
|
16
16
|
|
|
17
17
|
relative_path = File.join(*module_name.split(".")) + ".mt"
|
|
18
18
|
blocked = false
|
|
19
|
-
candidate =
|
|
20
|
-
|
|
19
|
+
candidate = nil
|
|
20
|
+
@module_roots.each do |root|
|
|
21
|
+
path = ModuleLoader.resolve_source_path(File.join(root, relative_path), platform: @platform)
|
|
22
|
+
next unless source_path_available?(path)
|
|
21
23
|
|
|
22
24
|
allowed = import_allowed?(module_name, importer_path, path)
|
|
23
25
|
blocked ||= !allowed
|
|
24
|
-
allowed
|
|
26
|
+
if allowed
|
|
27
|
+
candidate = path
|
|
28
|
+
break
|
|
29
|
+
end
|
|
25
30
|
end
|
|
26
31
|
raise ModuleLoadError.new("package dependency not declared", path: module_name) if blocked
|
|
27
32
|
unless candidate
|
|
28
33
|
message = namespace_hint_for_missing_module(module_name, importer_path:, importer_module_name:) || "module not found"
|
|
29
34
|
raise ModuleLoadError.new(message, path: module_name)
|
|
30
35
|
end
|
|
31
|
-
|
|
32
36
|
File.expand_path(candidate)
|
|
33
37
|
end
|
|
34
38
|
|
|
@@ -119,9 +123,6 @@ module MilkTea
|
|
|
119
123
|
return @package_manifest_cache[manifest_path] if @package_manifest_cache.key?(manifest_path)
|
|
120
124
|
|
|
121
125
|
@package_manifest_cache[manifest_path] = PackageManifest.load(path)
|
|
122
|
-
rescue PackageManifestError
|
|
123
|
-
@package_manifest_cache[manifest_path] = nil if manifest_path
|
|
124
|
-
nil
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
def package_namespace_match?(module_name, package_name)
|
|
@@ -18,6 +18,39 @@ module MilkTea
|
|
|
18
18
|
AST::Import.new(path:, alias_name:, line:, column: local_column, length: local_name.length)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
DECL_KIND = {
|
|
22
|
+
var: { method: :parse_var_decl, reject_attrs: "var" },
|
|
23
|
+
type: { method: :parse_type_alias_decl, reject_attrs: "type" },
|
|
24
|
+
union: { method: :parse_union_decl },
|
|
25
|
+
enum: { method: :parse_enum_decl, enum_class: AST::EnumDecl },
|
|
26
|
+
flags: { method: :parse_enum_decl, enum_class: AST::FlagsDecl },
|
|
27
|
+
opaque: { method: :parse_opaque_decl, reject_attrs: "opaque" },
|
|
28
|
+
interface: { method: :parse_interface_decl, reject_attrs: "interface" },
|
|
29
|
+
event: { method: :parse_event_decl },
|
|
30
|
+
attribute: { method: :parse_attribute_decl, reject_attrs: "attribute" },
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
def dispatch_decl_kind(kind, visibility:, attributes:)
|
|
34
|
+
info = DECL_KIND.fetch(kind)
|
|
35
|
+
reject_attributes!(attributes, info[:reject_attrs]) if info[:reject_attrs]
|
|
36
|
+
|
|
37
|
+
if info[:enum_class]
|
|
38
|
+
send(info[:method], info[:enum_class], visibility:, attributes:)
|
|
39
|
+
elsif info[:reject_attrs]
|
|
40
|
+
send(info[:method], visibility:)
|
|
41
|
+
else
|
|
42
|
+
send(info[:method], visibility:, attributes:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse_const_or_function_decl(visibility:, attributes:)
|
|
47
|
+
if match(:function)
|
|
48
|
+
parse_function_def(visibility:, const: true, attributes:)
|
|
49
|
+
else
|
|
50
|
+
parse_const_decl(visibility:, attributes:)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
21
54
|
def parse_declaration
|
|
22
55
|
attributes = parse_attribute_applications
|
|
23
56
|
visibility, visibility_token = parse_visibility
|
|
@@ -25,38 +58,29 @@ module MilkTea
|
|
|
25
58
|
if builtin_attribute_identifier?(peek)
|
|
26
59
|
raise error(peek, "layout modifiers must use attributes like @[packed] or @[align(...)]")
|
|
27
60
|
elsif match(:attribute)
|
|
28
|
-
|
|
29
|
-
parse_attribute_decl(visibility:)
|
|
61
|
+
dispatch_decl_kind(:attribute, visibility:, attributes:)
|
|
30
62
|
elsif match(:const)
|
|
31
|
-
|
|
32
|
-
parse_function_def(visibility:, const: true, attributes:)
|
|
33
|
-
else
|
|
34
|
-
parse_const_decl(visibility:, attributes:)
|
|
35
|
-
end
|
|
63
|
+
parse_const_or_function_decl(visibility:, attributes:)
|
|
36
64
|
elsif match(:var)
|
|
37
|
-
|
|
38
|
-
parse_var_decl(visibility:)
|
|
65
|
+
dispatch_decl_kind(:var, visibility:, attributes:)
|
|
39
66
|
elsif match(:event)
|
|
40
|
-
|
|
67
|
+
dispatch_decl_kind(:event, visibility:, attributes:)
|
|
41
68
|
elsif match(:type)
|
|
42
|
-
|
|
43
|
-
parse_type_alias_decl(visibility:)
|
|
69
|
+
dispatch_decl_kind(:type, visibility:, attributes:)
|
|
44
70
|
elsif match(:struct)
|
|
45
71
|
parse_struct_decl(visibility:, attributes:)
|
|
46
72
|
elsif match(:union)
|
|
47
|
-
|
|
73
|
+
dispatch_decl_kind(:union, visibility:, attributes:)
|
|
48
74
|
elsif match(:enum)
|
|
49
|
-
|
|
75
|
+
dispatch_decl_kind(:enum, visibility:, attributes:)
|
|
50
76
|
elsif match(:flags)
|
|
51
|
-
|
|
77
|
+
dispatch_decl_kind(:flags, visibility:, attributes:)
|
|
52
78
|
elsif match(:variant)
|
|
53
79
|
parse_variant_decl(visibility:, attributes:)
|
|
54
80
|
elsif match(:interface)
|
|
55
|
-
|
|
56
|
-
parse_interface_decl(visibility:)
|
|
81
|
+
dispatch_decl_kind(:interface, visibility:, attributes:)
|
|
57
82
|
elsif match(:opaque)
|
|
58
|
-
|
|
59
|
-
parse_opaque_decl(visibility:)
|
|
83
|
+
dispatch_decl_kind(:opaque, visibility:, attributes:)
|
|
60
84
|
elsif match(:extending)
|
|
61
85
|
reject_attributes!(attributes, "extending")
|
|
62
86
|
raise error(visibility_token, "public is not allowed on extending blocks") if visibility == :public
|
|
@@ -35,14 +35,14 @@ module MilkTea
|
|
|
35
35
|
consume(:newline, "expected newline after 'if condition:' in if expression")
|
|
36
36
|
consume(:indent, "expected indented body in if expression")
|
|
37
37
|
then_expression = parse_expression
|
|
38
|
-
|
|
38
|
+
finish_expression_statement(then_expression)
|
|
39
39
|
consume(:dedent, "expected end of if expression body")
|
|
40
40
|
consume(:else, "expected 'else' in if expression")
|
|
41
41
|
consume(:colon, "expected ':' after 'else' in if expression")
|
|
42
42
|
consume(:newline, "expected newline after 'else:' in if expression")
|
|
43
43
|
consume(:indent, "expected indented else body in if expression")
|
|
44
44
|
else_expression = parse_expression
|
|
45
|
-
|
|
45
|
+
finish_expression_statement(else_expression)
|
|
46
46
|
consume(:dedent, "expected end of else expression body")
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -97,7 +97,7 @@ module MilkTea
|
|
|
97
97
|
end
|
|
98
98
|
consume(:colon, "expected ':' after match expression arm pattern")
|
|
99
99
|
value = parse_expression
|
|
100
|
-
|
|
100
|
+
finish_expression_statement(value)
|
|
101
101
|
patterns.map do |pattern|
|
|
102
102
|
AST::MatchExprArm.new(
|
|
103
103
|
pattern:,
|
|
@@ -549,10 +549,7 @@ module MilkTea
|
|
|
549
549
|
end
|
|
550
550
|
|
|
551
551
|
parser = self.class.new(tokens, path: @path)
|
|
552
|
-
parser.
|
|
553
|
-
parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
|
|
554
|
-
parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
|
|
555
|
-
parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)
|
|
552
|
+
parser.apply_parser_context(parser_context)
|
|
556
553
|
|
|
557
554
|
expression = parser.parse_expression
|
|
558
555
|
parser.skip_newlines
|
|
@@ -643,7 +640,7 @@ module MilkTea
|
|
|
643
640
|
|
|
644
641
|
def definite_type_argument?(value)
|
|
645
642
|
case value
|
|
646
|
-
when AST::FunctionType
|
|
643
|
+
when AST::FunctionType, AST::TupleType
|
|
647
644
|
true
|
|
648
645
|
when AST::TypeRef
|
|
649
646
|
known_type_like_name?(value.name.parts.first)
|
|
@@ -147,7 +147,7 @@ module MilkTea
|
|
|
147
147
|
|
|
148
148
|
else_body = parse_block
|
|
149
149
|
else
|
|
150
|
-
|
|
150
|
+
finish_expression_statement(value)
|
|
151
151
|
end
|
|
152
152
|
else
|
|
153
153
|
raise error(name_token, "local declaration without initializer requires a type") unless var_type
|
|
@@ -366,7 +366,7 @@ module MilkTea
|
|
|
366
366
|
if match_arm_expr_form?
|
|
367
367
|
consume(:colon, "expected ':' after match expression arm pattern")
|
|
368
368
|
value = parse_expression
|
|
369
|
-
|
|
369
|
+
finish_expression_statement(value)
|
|
370
370
|
patterns.map do |pattern|
|
|
371
371
|
AST::MatchExprArm.new(
|
|
372
372
|
pattern:,
|
|
@@ -585,7 +585,7 @@ module MilkTea
|
|
|
585
585
|
token = previous
|
|
586
586
|
line = token.line
|
|
587
587
|
value = check(:newline) ? nil : parse_expression
|
|
588
|
-
|
|
588
|
+
finish_expression_statement(value)
|
|
589
589
|
AST::ReturnStmt.new(value:, line:, column: token.column, length: token.lexeme.length)
|
|
590
590
|
end
|
|
591
591
|
|
|
@@ -768,10 +768,10 @@ module MilkTea
|
|
|
768
768
|
operator = previous.lexeme
|
|
769
769
|
column = previous.column
|
|
770
770
|
value = parse_expression
|
|
771
|
-
|
|
771
|
+
finish_expression_statement(value)
|
|
772
772
|
AST::Assignment.new(target: expression, operator:, value:, line:, column:)
|
|
773
773
|
else
|
|
774
|
-
|
|
774
|
+
finish_expression_statement(expression)
|
|
775
775
|
AST::ExpressionStmt.new(expression:, line:)
|
|
776
776
|
end
|
|
777
777
|
end
|
data/lib/milk_tea/core/parser.rb
CHANGED
|
@@ -38,6 +38,8 @@ module MilkTea
|
|
|
38
38
|
def initialize(ast:, errors: []) = super
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
ParseContext = Data.define(:known_type_names, :known_import_aliases, :known_generic_callable_names, :current_type_param_names)
|
|
42
|
+
|
|
41
43
|
include Parse::Blocks
|
|
42
44
|
include Parse::Recovery
|
|
43
45
|
include Parse::Types
|
|
@@ -250,6 +252,10 @@ module MilkTea
|
|
|
250
252
|
consume(:newline, "expected end of statement")
|
|
251
253
|
end
|
|
252
254
|
|
|
255
|
+
def finish_expression_statement(value)
|
|
256
|
+
consume_end_of_statement unless block_expression?(value)
|
|
257
|
+
end
|
|
258
|
+
|
|
253
259
|
def block_expression?(expression)
|
|
254
260
|
expression.is_a?(AST::ProcExpr) || expression.is_a?(AST::MatchExpr) || expression.is_a?(AST::IfExpr)
|
|
255
261
|
end
|
|
@@ -304,6 +310,26 @@ module MilkTea
|
|
|
304
310
|
@current_type_param_names = saved_names
|
|
305
311
|
end
|
|
306
312
|
|
|
313
|
+
# Returns a ParseContext snapshot of the parser state that must propagate
|
|
314
|
+
# to nested parsers (e.g. format string interpolations). When adding
|
|
315
|
+
# parser state that must be cloned, update both this method,
|
|
316
|
+
# apply_parser_context, and the ParseContext Data class.
|
|
317
|
+
def parser_context
|
|
318
|
+
ParseContext.new(
|
|
319
|
+
known_type_names: @known_type_names.dup,
|
|
320
|
+
known_import_aliases: @known_import_aliases.dup,
|
|
321
|
+
known_generic_callable_names: @known_generic_callable_names.dup,
|
|
322
|
+
current_type_param_names: @current_type_param_names.dup,
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def apply_parser_context(context)
|
|
327
|
+
@known_type_names = context.known_type_names
|
|
328
|
+
@known_import_aliases = context.known_import_aliases
|
|
329
|
+
@known_generic_callable_names = context.known_generic_callable_names
|
|
330
|
+
@current_type_param_names = context.current_type_param_names
|
|
331
|
+
end
|
|
332
|
+
|
|
307
333
|
def seed_known_names
|
|
308
334
|
MilkTea::BUILTIN_TYPE_NAMES.each { |name| @known_type_names[name] = true }
|
|
309
335
|
|