mt-lang 0.3.20 → 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/control_flow/builder.rb +2 -1
- data/lib/milk_tea/core/lowering/resolve.rb +1 -1
- data/lib/milk_tea/core/module_binder.rb +10 -5
- data/lib/milk_tea/core/module_loader.rb +175 -18
- data/lib/milk_tea/core/module_path_resolver.rb +9 -5
- data/lib/milk_tea/core/parser/expressions.rb +1 -1
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +29 -7
- data/lib/milk_tea/core/types.rb +12 -17
- data/lib/milk_tea/lsp/diagnostics.rb +13 -0
- data/lib/milk_tea/tooling/cli/commands/check.rb +6 -16
- data/lib/milk_tea/tooling/sexpr_dumper.rb +7 -7
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dc76fcf0acde63fa4060157d6c6864dedcc32b0f5c2cd60422e9948d9861dab
|
|
4
|
+
data.tar.gz: 0b1bf5aee4d6be4d0f1968a7dacaf35e37557336a5911834a17324d23bd60ba3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b34537660a1c2424b3f829ff6ef18d35de4366fe76d0a0938216261b1f9c7c56d8fce2c8a0ac1162f17ddc8c1a2c565f9e08f2f5f0056624f4959d35833cabf
|
|
7
|
+
data.tar.gz: 9b6b00b5b761ab3128c617acb8398c64b829df1e2d6984661bc6e8bfb2081b9fa408fd5e8774e1b540884a59caef4eef146c3a05f0d176cb62019471b4e28b88
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -413,7 +413,8 @@ module MilkTea
|
|
|
413
413
|
read_identifiers(expression.expression, names, reads_info)
|
|
414
414
|
expression.arms.each do |arm|
|
|
415
415
|
read_identifiers(arm.pattern, names, reads_info)
|
|
416
|
-
|
|
416
|
+
arm_value = arm.is_a?(AST::MatchExprArm) ? arm.value : arm.body
|
|
417
|
+
read_identifiers(arm_value, names, reads_info)
|
|
417
418
|
end
|
|
418
419
|
when AST::MatchStmt
|
|
419
420
|
read_identifiers(expression.expression, names, reads_info)
|
|
@@ -1506,7 +1506,7 @@ module MilkTea
|
|
|
1506
1506
|
case argument
|
|
1507
1507
|
when AST::TypeRef
|
|
1508
1508
|
resolve_type_argument_ref(argument, type_params:)
|
|
1509
|
-
when AST::FunctionType, AST::ProcType
|
|
1509
|
+
when AST::FunctionType, AST::ProcType, AST::TupleType
|
|
1510
1510
|
resolve_type_ref(argument, type_params:)
|
|
1511
1511
|
when AST::IntegerLiteral, AST::FloatLiteral
|
|
1512
1512
|
Types::LiteralTypeArg.new(argument.value)
|
|
@@ -20,19 +20,24 @@ module MilkTea
|
|
|
20
20
|
when AST::StructDecl, AST::UnionDecl, AST::VariantDecl, AST::EnumDecl, AST::FlagsDecl, AST::OpaqueDecl, AST::TypeAliasDecl
|
|
21
21
|
type_declarations[declaration.name] = declaration
|
|
22
22
|
target = exported_declaration?(analysis, declaration) ? types : private_types
|
|
23
|
-
|
|
23
|
+
resolved_type = analysis.types[declaration.name]
|
|
24
|
+
target[declaration.name] = resolved_type if resolved_type
|
|
24
25
|
when AST::InterfaceDecl
|
|
25
26
|
target = exported_declaration?(analysis, declaration) ? interfaces : private_interfaces
|
|
26
|
-
|
|
27
|
+
resolved_iface = analysis.interfaces[declaration.name]
|
|
28
|
+
target[declaration.name] = resolved_iface if resolved_iface
|
|
27
29
|
when AST::AttributeDecl
|
|
28
30
|
target = exported_declaration?(analysis, declaration) ? attributes : private_attributes
|
|
29
|
-
|
|
31
|
+
resolved_attr = analysis.attributes[declaration.name]
|
|
32
|
+
target[declaration.name] = resolved_attr if resolved_attr
|
|
30
33
|
when AST::ConstDecl, AST::VarDecl, AST::EventDecl
|
|
31
34
|
target = exported_declaration?(analysis, declaration) ? values : private_values
|
|
32
|
-
|
|
35
|
+
resolved_val = analysis.values[declaration.name]
|
|
36
|
+
target[declaration.name] = resolved_val if resolved_val
|
|
33
37
|
when AST::FunctionDef, AST::ExternFunctionDecl, AST::ForeignFunctionDecl
|
|
34
38
|
target = exported_declaration?(analysis, declaration) ? functions : private_functions
|
|
35
|
-
|
|
39
|
+
resolved_func = analysis.functions[declaration.name]
|
|
40
|
+
target[declaration.name] = resolved_func if resolved_func
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
@@ -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
|
|
163
167
|
|
|
164
|
-
|
|
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
|
|
165
174
|
|
|
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
|
|
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
|
|
|
@@ -328,6 +401,15 @@ module MilkTea
|
|
|
328
401
|
modules = {}
|
|
329
402
|
errors = []
|
|
330
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
|
|
412
|
+
|
|
331
413
|
ast.imports.each do |import|
|
|
332
414
|
begin
|
|
333
415
|
import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
|
|
@@ -340,7 +422,8 @@ module MilkTea
|
|
|
340
422
|
end
|
|
341
423
|
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
342
424
|
raise unless collecting
|
|
343
|
-
|
|
425
|
+
|
|
426
|
+
handle_circular_import_in_collecting_mode(import, import_path, modules, errors, e)
|
|
344
427
|
end
|
|
345
428
|
end
|
|
346
429
|
|
|
@@ -361,6 +444,8 @@ module MilkTea
|
|
|
361
444
|
return ImportResolution.new(modules: modules.freeze, errors: errors.freeze) if collecting
|
|
362
445
|
|
|
363
446
|
modules.freeze
|
|
447
|
+
ensure
|
|
448
|
+
@import_resolve_depth -= 1 if @import_resolve_depth
|
|
364
449
|
end
|
|
365
450
|
|
|
366
451
|
def build_global_import_index(ast)
|
|
@@ -521,6 +606,74 @@ module MilkTea
|
|
|
521
606
|
normalized_path == normalized_root || normalized_path.start_with?(normalized_root + File::SEPARATOR)
|
|
522
607
|
end
|
|
523
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
|
+
|
|
524
677
|
def create_forward_binding(ast)
|
|
525
678
|
module_name = ast.module_name.to_s
|
|
526
679
|
types = {}
|
|
@@ -528,9 +681,13 @@ module MilkTea
|
|
|
528
681
|
ast.declarations.each do |decl|
|
|
529
682
|
case decl
|
|
530
683
|
when AST::StructDecl
|
|
531
|
-
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))
|
|
532
687
|
when AST::VariantDecl
|
|
533
|
-
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))
|
|
534
691
|
when AST::EnumDecl
|
|
535
692
|
types[decl.name] = Types::Enum.new(decl.name, module_name:)
|
|
536
693
|
when AST::FlagsDecl
|
|
@@ -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,7 +123,7 @@ 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
|
-
|
|
126
|
+
end
|
|
123
127
|
|
|
124
128
|
def package_namespace_match?(module_name, package_name)
|
|
125
129
|
module_name == package_name || module_name.start_with?("#{package_name}.")
|
|
@@ -58,6 +58,20 @@ module MilkTea
|
|
|
58
58
|
imported_candidates << [module_binding, imported_method] if imported_method
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
if imported_candidates.empty?
|
|
62
|
+
# Identity-based lookup failed. Try a name-based fallback across
|
|
63
|
+
# all imports, but only take the first match — not all candidates —
|
|
64
|
+
# to avoid false "ambiguous method" errors when two modules both
|
|
65
|
+
# extend the same receiver type with the same method name.
|
|
66
|
+
@ctx.imports.each_value do |module_binding|
|
|
67
|
+
method = find_method_by_receiver_name(module_binding, dispatch_receiver_type, name)
|
|
68
|
+
if method
|
|
69
|
+
imported_candidates << [module_binding, method]
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
61
75
|
if imported_candidates.empty?
|
|
62
76
|
owner_module = reachable_module_binding_for_type(receiver_type)
|
|
63
77
|
return nil unless owner_module
|
|
@@ -77,17 +91,25 @@ module MilkTea
|
|
|
77
91
|
def module_binding_method(module_binding, receiver_type, dispatch_receiver_type, name)
|
|
78
92
|
method = module_binding.methods.fetch(receiver_type, {})[name]
|
|
79
93
|
method ||= module_binding.methods.fetch(dispatch_receiver_type, {})[name] unless dispatch_receiver_type == receiver_type
|
|
94
|
+
method ||= find_method_by_receiver_name(module_binding, dispatch_receiver_type, name)
|
|
80
95
|
method
|
|
81
96
|
end
|
|
82
97
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
find_reachable_imported_module(module_name)
|
|
98
|
+
def find_method_by_receiver_name(module_binding, receiver_type, name)
|
|
99
|
+
module_binding.methods.each do |key, methods|
|
|
100
|
+
return methods[name] if key.is_a?(receiver_type.class) && key.name == receiver_type.name && methods.key?(name)
|
|
101
|
+
end
|
|
102
|
+
nil
|
|
89
103
|
end
|
|
90
104
|
|
|
105
|
+
def reachable_module_binding_for_type(receiver_type)
|
|
106
|
+
module_name = receiver_type_module_name(receiver_type)
|
|
107
|
+
return nil unless module_name
|
|
108
|
+
return nil if module_name == @ctx.module_name
|
|
109
|
+
|
|
110
|
+
find_reachable_imported_module(module_name)
|
|
111
|
+
end
|
|
112
|
+
|
|
91
113
|
def receiver_type_module_name(receiver_type)
|
|
92
114
|
return receiver_type_module_name(receiver_type.base) if receiver_type.is_a?(Types::Nullable)
|
|
93
115
|
return receiver_type.module_name if receiver_type.respond_to?(:module_name)
|
|
@@ -399,7 +421,7 @@ module MilkTea
|
|
|
399
421
|
case argument
|
|
400
422
|
when AST::TypeRef
|
|
401
423
|
resolve_type_argument_ref(argument, type_params:, type_param_constraints:)
|
|
402
|
-
when AST::FunctionType, AST::ProcType
|
|
424
|
+
when AST::FunctionType, AST::ProcType, AST::TupleType
|
|
403
425
|
resolve_type_ref(argument, type_params:, type_param_constraints:)
|
|
404
426
|
when AST::IntegerLiteral, AST::FloatLiteral
|
|
405
427
|
Types::LiteralTypeArg.new(argument.value)
|
data/lib/milk_tea/core/types.rb
CHANGED
|
@@ -822,6 +822,18 @@ module MilkTea
|
|
|
822
822
|
self
|
|
823
823
|
end
|
|
824
824
|
|
|
825
|
+
def eql?(other)
|
|
826
|
+
other.is_a?(GenericStructDefinition) && other.name == name &&
|
|
827
|
+
other.type_params == type_params && other.module_name == module_name &&
|
|
828
|
+
other.external == external && other.packed == packed && other.alignment == alignment
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
alias == eql?
|
|
832
|
+
|
|
833
|
+
def hash
|
|
834
|
+
[self.class, name, type_params, module_name, external, packed, alignment].hash
|
|
835
|
+
end
|
|
836
|
+
|
|
825
837
|
def set_layout(packed:, alignment:)
|
|
826
838
|
@packed = packed
|
|
827
839
|
@alignment = alignment
|
|
@@ -853,23 +865,6 @@ module MilkTea
|
|
|
853
865
|
name
|
|
854
866
|
end
|
|
855
867
|
|
|
856
|
-
def eql?(other)
|
|
857
|
-
other.class == self.class &&
|
|
858
|
-
other.name == name &&
|
|
859
|
-
other.type_params == type_params &&
|
|
860
|
-
other.module_name == module_name &&
|
|
861
|
-
other.external == external &&
|
|
862
|
-
other.packed == packed &&
|
|
863
|
-
other.alignment == alignment &&
|
|
864
|
-
other.linkage_name == linkage_name
|
|
865
|
-
end
|
|
866
|
-
|
|
867
|
-
alias == eql?
|
|
868
|
-
|
|
869
|
-
def hash
|
|
870
|
-
[self.class, name, type_params, module_name, external, packed, alignment, linkage_name].hash
|
|
871
|
-
end
|
|
872
|
-
|
|
873
868
|
def instantiate(arguments)
|
|
874
869
|
raise ArgumentError, "#{name} expects #{type_params.length} type arguments, got #{arguments.length}" unless arguments.length == type_params.length
|
|
875
870
|
|
|
@@ -179,6 +179,19 @@ module MilkTea
|
|
|
179
179
|
platform: effective_platform,
|
|
180
180
|
)
|
|
181
181
|
module_name = loader.send(:inferred_module_name_for_path, path) rescue nil
|
|
182
|
+
|
|
183
|
+
# Run the full two-pass program check first so that @analysis_cache
|
|
184
|
+
# has fully-checked analyses for all transitive modules. When the
|
|
185
|
+
# import resolution below encounters a cycle member, it finds the
|
|
186
|
+
# cached analysis and resolves correctly. If the program check fails
|
|
187
|
+
# (e.g. missing module), fall through to the standard resolution
|
|
188
|
+
# path so that prelude modules and regular errors are still reported.
|
|
189
|
+
begin
|
|
190
|
+
loader.check_program_collecting(path)
|
|
191
|
+
rescue ModuleLoadError, PackageLockError
|
|
192
|
+
# best-effort — standard path below handles diagnostics
|
|
193
|
+
end
|
|
194
|
+
|
|
182
195
|
resolution_result = loader.imported_modules_for_ast_collecting_errors(ast, importer_path: path)
|
|
183
196
|
unresolved_import_paths = []
|
|
184
197
|
|
|
@@ -76,20 +76,11 @@ module MilkTea
|
|
|
76
76
|
def check_single_reporting_all(path, locked: false)
|
|
77
77
|
loader = make_module_loader(path, locked:, platform: ModuleLoader.default_host_platform)
|
|
78
78
|
resolved_path = File.expand_path(path)
|
|
79
|
-
ast = loader.load_file(resolved_path)
|
|
80
|
-
module_name = ast.module_name.to_s
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
errors =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
begin
|
|
87
|
-
result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules: import_result.modules, path: resolved_path)
|
|
88
|
-
errors.concat(result[:errors])
|
|
89
|
-
analysis = result[:analysis]
|
|
90
|
-
rescue SemanticError => e
|
|
91
|
-
errors << e
|
|
92
|
-
end
|
|
80
|
+
result = loader.check_program_collecting(path)
|
|
81
|
+
errors = result[:errors]
|
|
82
|
+
analysis = result[:root_analysis]
|
|
83
|
+
module_name = result[:module_name]
|
|
93
84
|
|
|
94
85
|
if analysis && errors.empty?
|
|
95
86
|
source = read_source_file(path)
|
|
@@ -97,9 +88,8 @@ module MilkTea
|
|
|
97
88
|
errors.concat(warnings)
|
|
98
89
|
end
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
91
|
+
[errors, module_name, []]
|
|
92
|
+
rescue ModuleLoadError, PackageLockError => e
|
|
103
93
|
[[e], nil, []]
|
|
104
94
|
end
|
|
105
95
|
|
|
@@ -72,11 +72,15 @@ module MilkTea
|
|
|
72
72
|
# ── Token serialisation ───────────────────────────────────────────
|
|
73
73
|
|
|
74
74
|
def emit_token(token, buf)
|
|
75
|
-
buf << "(token :type " <<
|
|
75
|
+
buf << "(token :type :" << token.type.to_s.tr("_", "-")
|
|
76
76
|
buf << " :lexeme "
|
|
77
77
|
emit_string(token.lexeme, buf)
|
|
78
78
|
buf << " :literal "
|
|
79
|
-
|
|
79
|
+
if token.type == :float
|
|
80
|
+
buf << token.lexeme
|
|
81
|
+
else
|
|
82
|
+
emit_value(token.literal, buf)
|
|
83
|
+
end
|
|
80
84
|
buf << " :line " << token.line.to_s
|
|
81
85
|
buf << " :column " << token.column.to_s
|
|
82
86
|
buf << " :start_offset " << token.start_offset.to_s
|
|
@@ -84,10 +88,6 @@ module MilkTea
|
|
|
84
88
|
buf << ")"
|
|
85
89
|
end
|
|
86
90
|
|
|
87
|
-
def emit_token_type(sym)
|
|
88
|
-
":" + sym.to_s.tr("_", "-")
|
|
89
|
-
end
|
|
90
|
-
|
|
91
91
|
# ── Types serialisation ───────────────────────────────────────────
|
|
92
92
|
|
|
93
93
|
TYPES_SHORT_NAMES = {
|
|
@@ -337,7 +337,7 @@ module MilkTea
|
|
|
337
337
|
end
|
|
338
338
|
|
|
339
339
|
def format_float(value)
|
|
340
|
-
s = sprintf("
|
|
340
|
+
s = sprintf("%g", value)
|
|
341
341
|
s.include?(".") || s.include?("e") ? s : s + ".0"
|
|
342
342
|
end
|
|
343
343
|
end
|
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.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -625,7 +625,7 @@ metadata:
|
|
|
625
625
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
626
626
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
627
627
|
post_install_message: |
|
|
628
|
-
Milk Tea 0.3.
|
|
628
|
+
Milk Tea 0.3.22 installed!
|
|
629
629
|
|
|
630
630
|
System requirements:
|
|
631
631
|
- A C compiler (gcc or clang) must be available on PATH
|