mt-lang 0.3.20 → 0.3.23
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/analysis_context.rb +25 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +6 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +33 -7
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +9 -11
- data/lib/milk_tea/core/types/layout.rb +15 -0
- data/lib/milk_tea/core/types/predicates.rb +7 -2
- 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: 7cfa07273d22ec7c182da079791513e546752f942e99e85d4ae68c3596a1dd96
|
|
4
|
+
data.tar.gz: 8ca9fb2fa55d9f2a7e9db9dd1f6935a0a82c8ad0f87f907b865007d5a27b68db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f8b9f5966a1bb53bfcf618293fdff445031610f97c316db745db6a06e3e32c50206fde9776b57ef205baab5c1ee240b11e0c003f9bed72d14f6f256d767b6d6
|
|
7
|
+
data.tar.gz: 50bd110c31a30966d1a25560c14e239ffee4f99e4991efd6a3029bb4dd6a05cea2d65c58e35dda94b917ec25ba1d7750e8d7e2d4578d1862b926dadfb4ab8f4b
|
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}.")
|
|
@@ -289,6 +289,31 @@ module MilkTea
|
|
|
289
289
|
closest_dist <= max_distance ? closest : nil
|
|
290
290
|
end
|
|
291
291
|
|
|
292
|
+
# Common std-library types that are usually used bare. When one of these
|
|
293
|
+
# names is unresolved, the compiler points at the module that exports it
|
|
294
|
+
# instead of leaving a generic "unknown name" diagnostic.
|
|
295
|
+
STD_TYPE_IMPORT_HINTS = {
|
|
296
|
+
"String" => "std.string",
|
|
297
|
+
"Vec" => "std.vec",
|
|
298
|
+
"Deque" => "std.deque",
|
|
299
|
+
"Map" => "std.map",
|
|
300
|
+
"Set" => "std.set",
|
|
301
|
+
"LinkedMap" => "std.linked_map",
|
|
302
|
+
"LinkedSet" => "std.linked_set",
|
|
303
|
+
"OrderedMap" => "std.ordered_map",
|
|
304
|
+
"OrderedSet" => "std.ordered_set",
|
|
305
|
+
"BinaryHeap" => "std.binary_heap",
|
|
306
|
+
"PriorityQueue" => "std.priority_queue",
|
|
307
|
+
"Counter" => "std.counter",
|
|
308
|
+
}.freeze
|
|
309
|
+
|
|
310
|
+
def import_hint_for_known_std_name(name)
|
|
311
|
+
module_path = STD_TYPE_IMPORT_HINTS[name.to_s]
|
|
312
|
+
return nil unless module_path
|
|
313
|
+
|
|
314
|
+
"type '#{name}' is available via 'import #{module_path}'"
|
|
315
|
+
end
|
|
316
|
+
|
|
292
317
|
def levenshtein(s, t)
|
|
293
318
|
m = s.length
|
|
294
319
|
n = t.length
|
|
@@ -336,6 +336,9 @@ module MilkTea
|
|
|
336
336
|
scoped_names = scopes.flat_map { |s| s.is_a?(Hash) ? s.keys : [] }.map(&:to_s)
|
|
337
337
|
suggestion = suggest_name(expression.name, func_names + type_names + scoped_names)
|
|
338
338
|
end
|
|
339
|
+
if (import_hint = import_hint_for_known_std_name(expression.name))
|
|
340
|
+
raise_sema_error("unknown name #{expression.name}; #{import_hint}")
|
|
341
|
+
end
|
|
339
342
|
raise_sema_error("unknown name #{expression.name}", expression, suggestion: suggestion ? "did you mean '#{suggestion}'?" : nil)
|
|
340
343
|
end
|
|
341
344
|
|
|
@@ -1505,6 +1508,9 @@ module MilkTea
|
|
|
1505
1508
|
raise_sema_error("unsupported format spec #{part.format_spec.inspect}")
|
|
1506
1509
|
end
|
|
1507
1510
|
else
|
|
1511
|
+
if string_builder_type?(value_type)
|
|
1512
|
+
raise_sema_error("formatted string interpolation does not support #{value_type}; interpolate #{describe_expression(part.expression)}.as_str() instead, or append into it with .append_format(f\"...\")")
|
|
1513
|
+
end
|
|
1508
1514
|
next if format_string_interpolation_supported?(value_type, context: "formatted string interpolation of #{value_type}")
|
|
1509
1515
|
raise_sema_error("formatted string interpolation supports str, cstr, bool, numeric primitives, integer-backed enums/flags, and types implementing format_len()/append_format(output: ref[std.string.String]), got #{value_type}")
|
|
1510
1516
|
end
|
|
@@ -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)
|
|
@@ -343,6 +365,9 @@ module MilkTea
|
|
|
343
365
|
unless suggestion
|
|
344
366
|
suggestion = import_suggestion_for_type(name)
|
|
345
367
|
end
|
|
368
|
+
unless suggestion
|
|
369
|
+
suggestion = import_hint_for_known_std_name(name)
|
|
370
|
+
end
|
|
346
371
|
raise_sema_error("unknown type #{name}", type_ref, suggestion: suggestion ? "did you mean '#{suggestion}'?" : nil)
|
|
347
372
|
end
|
|
348
373
|
raise_sema_error("generic type #{name} requires type arguments", type_ref) if type.is_a?(Types::GenericStructDefinition) || type.is_a?(Types::GenericVariantDefinition)
|
|
@@ -399,7 +424,7 @@ module MilkTea
|
|
|
399
424
|
case argument
|
|
400
425
|
when AST::TypeRef
|
|
401
426
|
resolve_type_argument_ref(argument, type_params:, type_param_constraints:)
|
|
402
|
-
when AST::FunctionType, AST::ProcType
|
|
427
|
+
when AST::FunctionType, AST::ProcType, AST::TupleType
|
|
403
428
|
resolve_type_ref(argument, type_params:, type_param_constraints:)
|
|
404
429
|
when AST::IntegerLiteral, AST::FloatLiteral
|
|
405
430
|
Types::LiteralTypeArg.new(argument.value)
|
|
@@ -616,6 +641,7 @@ module MilkTea
|
|
|
616
641
|
def infer_layout_query_type(type_ref, context:)
|
|
617
642
|
type = resolve_type_ref(type_ref)
|
|
618
643
|
return type if sized_layout_type?(type)
|
|
644
|
+
return type if vector_type?(type) || matrix_type?(type) || quaternion_type?(type) || simd_type?(type)
|
|
619
645
|
|
|
620
646
|
raise_sema_error("#{context} requires a concrete sized type, got #{type_ref}")
|
|
621
647
|
end
|
|
@@ -266,6 +266,7 @@ module MilkTea
|
|
|
266
266
|
|
|
267
267
|
def simd_bitwise_result(left_type, right_type)
|
|
268
268
|
return nil unless simd_type?(left_type) && simd_type?(right_type) && left_type == right_type
|
|
269
|
+
return nil unless left_type.element_type.integer?
|
|
269
270
|
|
|
270
271
|
left_type
|
|
271
272
|
end
|
|
@@ -286,15 +287,16 @@ module MilkTea
|
|
|
286
287
|
|
|
287
288
|
def vector_op_result(operator, left_type, right_type)
|
|
288
289
|
if vector_type?(left_type) && vector_type?(right_type) && left_type.element_type == right_type.element_type
|
|
290
|
+
return nil unless left_type.width == right_type.width
|
|
289
291
|
return left_type if operator == "+" || operator == "-"
|
|
290
292
|
return left_type if operator == "*"
|
|
291
293
|
end
|
|
292
294
|
|
|
293
|
-
if vector_type?(left_type) && right_type
|
|
295
|
+
if vector_type?(left_type) && primitive_numeric_type?(right_type)
|
|
294
296
|
return left_type if operator == "*" || operator == "/"
|
|
295
297
|
end
|
|
296
298
|
|
|
297
|
-
if left_type
|
|
299
|
+
if primitive_numeric_type?(left_type) && vector_type?(right_type) && operator == "*"
|
|
298
300
|
return right_type
|
|
299
301
|
end
|
|
300
302
|
|
|
@@ -307,11 +309,7 @@ module MilkTea
|
|
|
307
309
|
return left_type if operator == "*"
|
|
308
310
|
end
|
|
309
311
|
|
|
310
|
-
if matrix_type?(left_type) &&
|
|
311
|
-
return Types::Vector.new(right_type.name, element_type: right_type.element_type, width: right_type.width) if operator == "*"
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
if matrix_type?(left_type) && right_type.numeric?
|
|
312
|
+
if matrix_type?(left_type) && primitive_numeric_type?(right_type)
|
|
315
313
|
return left_type if operator == "*" || operator == "/"
|
|
316
314
|
end
|
|
317
315
|
|
|
@@ -324,13 +322,13 @@ module MilkTea
|
|
|
324
322
|
return left_type if operator == "*"
|
|
325
323
|
end
|
|
326
324
|
|
|
327
|
-
if quaternion_type?(left_type) && vector_type?(right_type) && right_type.width == 3 && right_type.element_type == Types::BUILTIN_VECTOR_ELEMENT
|
|
328
|
-
return right_type if operator == "*"
|
|
329
|
-
end
|
|
330
|
-
|
|
331
325
|
nil
|
|
332
326
|
end
|
|
333
327
|
|
|
328
|
+
def primitive_numeric_type?(type)
|
|
329
|
+
type.is_a?(Types::Primitive) && type.numeric?
|
|
330
|
+
end
|
|
331
|
+
|
|
334
332
|
def pointer_cast?(source_type, target_type)
|
|
335
333
|
pointer_cast_type?(source_type) && pointer_cast_type?(target_type)
|
|
336
334
|
end
|
|
@@ -76,6 +76,21 @@ module MilkTea
|
|
|
76
76
|
total = element_layout.first * type.lane_count
|
|
77
77
|
alignment = total > 16 ? 32 : 16
|
|
78
78
|
[total, alignment]
|
|
79
|
+
when Types::Vector
|
|
80
|
+
element_layout = size_and_alignment(type.element_type, stack)
|
|
81
|
+
return unless element_layout
|
|
82
|
+
|
|
83
|
+
[element_layout.first * type.width, element_layout.last]
|
|
84
|
+
when Types::Matrix
|
|
85
|
+
element_layout = size_and_alignment(Types::BUILTIN_VECTOR_ELEMENT, stack)
|
|
86
|
+
return unless element_layout
|
|
87
|
+
|
|
88
|
+
[element_layout.first * type.dim * type.dim, element_layout.last]
|
|
89
|
+
when Types::Quaternion
|
|
90
|
+
element_layout = size_and_alignment(Types::BUILTIN_VECTOR_ELEMENT, stack)
|
|
91
|
+
return unless element_layout
|
|
92
|
+
|
|
93
|
+
[element_layout.first * 4, element_layout.last]
|
|
79
94
|
else
|
|
80
95
|
nil
|
|
81
96
|
end
|
|
@@ -100,9 +100,14 @@ module MilkTea
|
|
|
100
100
|
return value.abs <= (1 << 24) if value.is_a?(Integer)
|
|
101
101
|
|
|
102
102
|
exactly_representable_float32?(value.to_f)
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
elsif expected_type.name == "double"
|
|
104
|
+
# Every integer with magnitude <= 2^53 is exactly representable in
|
|
105
|
+
# double precision; larger integer constants would lose precision, so
|
|
106
|
+
# they require an explicit cast instead.
|
|
107
|
+
return value.abs <= (1 << 53) if value.is_a?(Integer)
|
|
105
108
|
|
|
109
|
+
true
|
|
110
|
+
else
|
|
106
111
|
false
|
|
107
112
|
end
|
|
108
113
|
end
|
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.23
|
|
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.23 installed!
|
|
629
629
|
|
|
630
630
|
System requirements:
|
|
631
631
|
- A C compiler (gcc or clang) must be available on PATH
|