mt-lang 0.4.23 → 0.4.25

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +8 -8
  5. data/lib/milk_tea/base.rb +1 -1
  6. data/lib/milk_tea/core/control_flow/builder.rb +12 -0
  7. data/lib/milk_tea/core/types/layout.rb +90 -19
  8. data/lib/milk_tea/tooling/cli/commands/std.rb +84 -0
  9. data/lib/milk_tea/tooling/cli.rb +25 -0
  10. data/lib/milk_tea/tooling/linter/fix_engine.rb +100 -0
  11. data/lib/milk_tea/tooling/linter/release_rules.rb +69 -11
  12. data/lib/milk_tea/tooling/linter/visitors.rb +58 -31
  13. data/lib/milk_tea/tooling/linter.rb +8 -1
  14. data/lib/milk_tea/tooling/std_catalog.rb +277 -0
  15. data/lib/milk_tea/tooling.rb +1 -0
  16. data/std/async/libuv_runtime.mt +20 -23
  17. data/std/async/mailbox.mt +2 -2
  18. data/std/base64.mt +3 -6
  19. data/std/behavior_tree.mt +1 -6
  20. data/std/binary.mt +2 -5
  21. data/std/color.mt +20 -20
  22. data/std/cookie.mt +1 -4
  23. data/std/deque.mt +2 -8
  24. data/std/encoding.mt +1 -4
  25. data/std/fmt.mt +1 -4
  26. data/std/graph.mt +5 -1
  27. data/std/htn.mt +9 -1
  28. data/std/http/server.mt +1 -4
  29. data/std/http.mt +14 -17
  30. data/std/jobs.mt +9 -10
  31. data/std/json.mt +17 -28
  32. data/std/linked_map.mt +3 -9
  33. data/std/map.mt +1 -4
  34. data/std/net/channel.mt +6 -6
  35. data/std/net/lobby.mt +3 -3
  36. data/std/net/manager.mt +0 -1
  37. data/std/net/mux.mt +1 -1
  38. data/std/net/packet.mt +4 -1
  39. data/std/net/punch.mt +3 -2
  40. data/std/net/rpc.mt +1 -3
  41. data/std/net/session.mt +12 -25
  42. data/std/net/sync.mt +3 -3
  43. data/std/net/turn.mt +7 -3
  44. data/std/net.mt +145 -159
  45. data/std/noise.mt +11 -7
  46. data/std/ordered_map.mt +2 -8
  47. data/std/ordered_set.mt +3 -5
  48. data/std/path.mt +1 -4
  49. data/std/process.mt +7 -18
  50. data/std/random.mt +2 -2
  51. data/std/sdl3/runtime.mt +1 -1
  52. data/std/steering.mt +27 -4
  53. data/std/string.mt +1 -4
  54. data/std/tar.mt +8 -10
  55. data/std/terminal.mt +21 -40
  56. data/std/thread.mt +3 -4
  57. data/std/tls.mt +22 -29
  58. data/std/toml.mt +23 -31
  59. data/std/uri.mt +2 -1
  60. data/std/utility.mt +2 -2
  61. data/std/vec.mt +8 -17
  62. metadata +5 -3
@@ -366,6 +366,10 @@ module MilkTea
366
366
  end
367
367
  def check_redundant_unsafe(node)
368
368
  return unless @sema_facts
369
+ # Generic bodies are only fully checked once instantiated elsewhere,
370
+ # so required-unsafe facts are incomplete for them: an unsafe block in
371
+ # a generic body may be required by concrete instances.
372
+ return if generic_function_context?
369
373
  return if @sema_facts.required_unsafe_lines.include?(node.line)
370
374
 
371
375
  @warnings << Warning.new(
@@ -830,43 +834,64 @@ module MilkTea
830
834
  )
831
835
  end
832
836
 
837
+ # Simulates the actual one-line join: every branch body must be a single
838
+ # physical line with balanced delimiters, and the composed line must fit
839
+ # the column budget. Anything that cannot be joined cleanly is reported
840
+ # as "too wide" so the hint is suppressed.
833
841
  def inline_if_too_wide?(statement, stmts)
834
842
  return false if statement.branches.empty?
835
843
 
836
- text = +""
837
-
838
- statement.branches.each_with_index do |b, i|
839
- cond_src = source_line_from(b.line, b.column)
840
- return false unless cond_src
841
-
842
- stmt_col = stmts[i]&.column
843
- stmt_line = stmts[i]&.line
844
- body_src = source_line_from(stmt_line, stmt_col)
845
- return false unless body_src
846
-
847
- text << cond_src
848
- text << " "
849
- text << body_src
850
- text << " "
851
- end
844
+ if_line_idx = Integer(statement.line) - 1
845
+ return true if if_line_idx.negative? || if_line_idx >= @source_lines.length
846
+
847
+ header = @source_lines[if_line_idx].to_s
848
+ return true unless header.strip.start_with?("if ")
849
+
850
+ indent = header[/\A[ \t]*/].to_s
851
+ segments = []
852
+ cur_header = header.strip
853
+ cur_body = nil
854
+ idx = if_line_idx + 1
855
+ while idx < @source_lines.length
856
+ line = @source_lines[idx].to_s.rstrip
857
+ break if line.empty?
858
+
859
+ line_indent = line[/\A[ \t]*/].to_s
860
+ if line_indent == indent && (line.strip == "else:" || line.strip.start_with?("else if "))
861
+ return true if cur_body.nil?
862
+
863
+ segments << [cur_header, cur_body]
864
+ cur_header = line.strip
865
+ cur_body = nil
866
+ idx += 1
867
+ next
868
+ end
869
+ break if line_indent.length <= indent.length
870
+ return true unless cur_body.nil?
852
871
 
853
- if statement.else_line
854
- else_src = source_line_from(statement.else_line, statement.else_column)
855
- return false unless else_src
856
- text << else_src
857
- text << " "
872
+ cur_body = line.strip
873
+ idx += 1
858
874
  end
875
+ return true if cur_body.nil?
876
+ return true unless cur_header.strip == "else:"
859
877
 
860
- stmt_col = stmts.last&.column
861
- stmt_line = stmts.last&.line
862
- else_body = source_line_from(stmt_line, stmt_col)
863
- return false unless else_body
864
- text << else_body
878
+ segments << [cur_header, cur_body]
879
+ joined = indent + segments.map { |h, b| "#{h} #{b}" }.join(" ")
880
+ joined.length > 120 || !balanced_inline_line?(joined)
881
+ end
865
882
 
866
- # The inline form would start at the `if` keyword's column, so the real
867
- # line width includes the surrounding block's indentation.
868
- indent = statement.branches.first.column.to_i - 1
869
- (indent + text.strip.length) > 120
883
+ def balanced_inline_line?(text)
884
+ depth = 0
885
+ text.each_char do |ch|
886
+ case ch
887
+ when "(", "["
888
+ depth += 1
889
+ when ")", "]"
890
+ depth -= 1
891
+ return false if depth.negative?
892
+ end
893
+ end
894
+ depth.zero?
870
895
  end
871
896
 
872
897
  def source_line_from(line, column)
@@ -966,7 +991,9 @@ module MilkTea
966
991
  struct_type = resolve_expr_type(call)
967
992
  return unless struct_type.is_a?(Types::Struct)
968
993
  source_type = resolve_expr_type(copied.first.value.receiver)
969
- return unless source_type.equal?(struct_type) || (source_type.respond_to?(:name) && source_type.name == struct_type.name)
994
+ # Compare qualified names: bare names collide across modules
995
+ # (e.g. net::Config vs chan::Config).
996
+ return unless source_type.respond_to?(:to_s) && source_type.to_s == struct_type.to_s
970
997
 
971
998
  field_names = struct_type.fields.keys.map(&:to_s).to_set
972
999
  copied_names = copied.map { |arg| arg.name.to_s }.to_set
@@ -77,6 +77,7 @@ module MilkTea
77
77
  redundant-else
78
78
  redundant-return
79
79
  redundant-type-annotation
80
+ redundant-unsafe
80
81
  reserved-primitive-name
81
82
  trailing-list-comma
82
83
  ].freeze
@@ -96,6 +97,7 @@ module MilkTea
96
97
  "redundant-else" => "Remove redundant else",
97
98
  "redundant-return" => "Remove redundant return",
98
99
  "redundant-type-annotation" => "Remove redundant type annotation",
100
+ "redundant-unsafe" => "Remove redundant unsafe",
99
101
  "prefer-let-else" => "Rewrite as let-else",
100
102
  "prefer-var-else" => "Rewrite as var-else",
101
103
  "prefer-inline-methods" => "Inline methods into struct",
@@ -358,7 +360,7 @@ module MilkTea
358
360
  # redundant-else,
359
361
  # redundant-return, reserved-primitive-name,
360
362
  # trailing-list-comma.
361
- # Returns the fixed source (may be identical if nothing was fixable).
363
+ # Returns the fixed source (may be identical to nothing-was-fixable input).
362
364
  def self.fix_source(source, path: nil, sema_facts: nil, select: nil, ignore: nil, max_passes: 5, profile: nil)
363
365
  pass_limit = [max_passes.to_i, 1].max
364
366
  current_source = source
@@ -446,6 +448,11 @@ module MilkTea
446
448
  working_context = sema_facts ? nil : best_effort_lint_context(source, path:)
447
449
  working_sema_facts = sema_facts || working_context[:facts]
448
450
  unresolved_import_paths = sema_facts ? Set.new : working_context[:unresolved_import_paths]
451
+ # With unresolved imports the analysis facts are incomplete (receiver
452
+ # editability, casts, ownership), so hint rules can misfire and fixes
453
+ # would land on false positives. Leave such sources to the author.
454
+ return source if unresolved_import_paths.any?
455
+
449
456
  warnings = lint_source(
450
457
  source,
451
458
  path:,
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ # Discovers the hand-written standard library modules shipped in std/, and
5
+ # resolves module names to source paths for tooling commands.
6
+ #
7
+ # Generated modules (bindgen output under std/c/ and imported-binding
8
+ # wrappers such as raylib or zstd) are excluded from discovery but stay
9
+ # resolvable, so `std show` can still print them.
10
+ class StdCatalog
11
+ Entry = Struct.new(:name, :description, :category, :path, keyword_init: true)
12
+
13
+ GENERATED_HEADER_PREFIX = "# generated by mtc"
14
+ PLATFORM_SUFFIXES = %w[linux windows wasm].freeze
15
+ EXCLUDED_DIRECTORIES = %w[c].freeze
16
+
17
+ CATEGORY_ORDER = [
18
+ "Core & Patterns",
19
+ "Collections",
20
+ "Memory",
21
+ "Text & Formatting",
22
+ "Math & Simulation",
23
+ "Data & Serialization",
24
+ "Concurrency & Time",
25
+ "Files & System",
26
+ "Network",
27
+ "Security",
28
+ "AI & Behavior",
29
+ "Game & Graphics",
30
+ ].freeze
31
+
32
+ # name => [category, description]. Must cover every hand-written module
33
+ # discovered under std/; a test enforces that coverage.
34
+ REGISTRY = {
35
+ "asset_pack" => ["Game & Graphics", "MTAP asset pack reader and writer for bundled runtime assets."],
36
+ "async" => ["Concurrency & Time", "Async/await runtime facade over a pluggable event-loop backend."],
37
+ "async.libuv_runtime" => ["Concurrency & Time", "libuv-backed async runtime implementation."],
38
+ "async.mailbox" => ["Concurrency & Time", "Typed mailboxes for message passing between async tasks."],
39
+ "async.runtime" => ["Concurrency & Time", "Async backend selection and event-loop plumbing shared by async code."],
40
+ "base64" => ["Text & Formatting", "Base64 encode and decode."],
41
+ "behavior_tree" => ["AI & Behavior", "Behavior trees with sequence, selector, decorator, and leaf nodes."],
42
+ "binary" => ["Data & Serialization", "Binary reader/writer with explicit endianness and error reporting."],
43
+ "binary_heap" => ["Collections", "Binary heap over a flat array with sift-up/sift-down operations."],
44
+ "bitset" => ["Collections", "Fixed-capacity bit array with set, clear, test, and population count."],
45
+ "blackboard" => ["AI & Behavior", "Typed key-value shared state for AI agents."],
46
+ "box" => ["Core & Patterns", "Explicit single-value heap storage for owned boxed values."],
47
+ "bytes" => ["Data & Serialization", "Byte buffer type with slicing, comparison, and stream I/O."],
48
+ "cli" => ["Files & System", "Command-line parsing helpers for flags, options, and positionals."],
49
+ "cmd" => ["Core & Patterns", "Command pattern with paired do/undo callbacks for undo/redo stacks."],
50
+ "color" => ["Game & Graphics", "RGBA color conversion, blending, and named presets."],
51
+ "cookie" => ["Data & Serialization", "HTTP cookie parsing and serialization."],
52
+ "counter" => ["Collections", "Occurrence counter keyed by value, a counting multiset."],
53
+ "crypto" => ["Security", "Digests, HMAC, and secure random via the platform crypto bindings."],
54
+ "cstring" => ["Text & Formatting", "Null-terminated C string helpers backed by libc memory routines."],
55
+ "ctype" => ["Text & Formatting", "Character classification: alnum, digit, space, and case tests."],
56
+ "curl.runtime" => ["Network", "libcurl runtime glue for handle setup and error reporting."],
57
+ "deque" => ["Collections", "Double-ended queue with amortized O(1) operations at both ends."],
58
+ "encoding" => ["Text & Formatting", "UTF-8 validation, codepoint iteration, and encoding helpers."],
59
+ "env" => ["Files & System", "Environment variable access returning Option values."],
60
+ "errno" => ["Files & System", "errno code constants and error message lookup."],
61
+ "fmt" => ["Text & Formatting", "Value formatting helpers for integers, floats, and fixed buffers."],
62
+ "fs" => ["Files & System", "File and directory operations with platform-specific variants."],
63
+ "fsm" => ["AI & Behavior", "Finite state machines with table-driven transitions and dispatch."],
64
+ "goap" => ["AI & Behavior", "Goal-oriented action planning (GOAP) with forward search."],
65
+ "graph" => ["Game & Graphics", "Weighted graph algorithms such as shortest paths and reachability."],
66
+ "gzip" => ["Data & Serialization", "Gzip and zlib compression and decompression."],
67
+ "hash" => ["Core & Patterns", "Canonical hash, equality, and ordering for common primitive types."],
68
+ "htn" => ["AI & Behavior", "Hierarchical Task Network (HTN) planner."],
69
+ "http" => ["Network", "HTTP client for requests, responses, and chunked bodies."],
70
+ "http.server" => ["Network", "Small HTTP server with routing, static files, and JSON responses."],
71
+ "input" => ["Game & Graphics", "Input action mapping that decouples logical actions from physical devices."],
72
+ "intern" => ["Text & Formatting", "String interning table producing unique identifier handles."],
73
+ "iter" => ["Core & Patterns", "Composable lazy iteration adaptors over sequences."],
74
+ "jobs" => ["Concurrency & Time", "Job scheduler for fan-out parallel work with completion tracking."],
75
+ "json" => ["Data & Serialization", "JSON parsing and serialization over cjson with arena-backed values."],
76
+ "libc" => ["Files & System", "Curated libc facade for memory, string, process, and math functions."],
77
+ "linear_algebra" => ["Math & Simulation", "Vectors and matrices for 2D/3D graphics and physics."],
78
+ "linked_map" => ["Collections", "Hash map with a doubly-linked order list for O(1) reordering."],
79
+ "linked_map_view" => ["Collections", "Read-only snapshot views over linked_map instances."],
80
+ "linked_set" => ["Collections", "Insertion-ordered hash set backed by a linked list."],
81
+ "log" => ["Files & System", "Leveled logging with configurable output sinks."],
82
+ "lru_cache" => ["Collections", "Least-recently-used cache with bounded capacity."],
83
+ "map" => ["Collections", "Chained hash map with pluggable hashing for generic keys and values."],
84
+ "math" => ["Math & Simulation", "Mathematical constants and functions beyond the language builtins."],
85
+ "mem.arena" => ["Memory", "Region allocator: bump allocation with O(1) mark/reset."],
86
+ "mem.endian" => ["Memory", "Byte-swap and endian conversion helpers."],
87
+ "mem.heap" => ["Memory", "General-purpose heap allocator wrapper with alignment control."],
88
+ "mem.pool" => ["Memory", "Fixed-slot pool allocator for uniform-size objects."],
89
+ "mem.ptr" => ["Memory", "Safe pointer load/store wrappers around raw memory access."],
90
+ "mem.stack" => ["Memory", "Stack allocator with mark/release layered on the arena."],
91
+ "mem.tracking" => ["Memory", "Allocation tracker for leak detection and usage diagnostics."],
92
+ "multiset" => ["Collections", "Hash multiset that counts duplicate values."],
93
+ "net" => ["Network", "UDP and TCP sockets, address resolution, and packet I/O."],
94
+ "net.channel" => ["Network", "Multiplexed message channels over a single connection."],
95
+ "net.clock" => ["Network", "Synchronized network clock with ping and offset estimation."],
96
+ "net.discovery" => ["Network", "LAN service discovery via UDP broadcast."],
97
+ "net.lobby" => ["Network", "Lobby listing and joining built on discovery."],
98
+ "net.manager" => ["Network", "Connection and session manager for multiplayer topologies."],
99
+ "net.mux" => ["Network", "Stream multiplexer with reliable and unordered delivery flags."],
100
+ "net.nat" => ["Network", "NAT type detection and address binding via STUN."],
101
+ "net.packet" => ["Network", "Framed packet reading and writing over byte streams."],
102
+ "net.punch" => ["Network", "UDP hole punching for peer-to-peer connections."],
103
+ "net.rpc" => ["Network", "Request/response RPC framing over net channels."],
104
+ "net.session" => ["Network", "Client session state machine with heartbeats and timeouts."],
105
+ "net.stun" => ["Network", "STUN client for public address and port discovery."],
106
+ "net.sync" => ["Network", "State synchronization primitives for replicated values."],
107
+ "net.turn" => ["Network", "TURN relay client for NAT-restricted peers."],
108
+ "noise" => ["Math & Simulation", "Coherent Perlin noise and fractal variants."],
109
+ "oauth2" => ["Data & Serialization", "OAuth2 token requests and refresh flows."],
110
+ "option" => ["Core & Patterns", "Option[T] optional values, some(value) or none, with combinators."],
111
+ "ordered_map" => ["Collections", "Hash map that preserves insertion order."],
112
+ "ordered_set" => ["Collections", "Hash set that preserves insertion order."],
113
+ "parse" => ["Text & Formatting", "Numeric string parsing for integers and floats."],
114
+ "path" => ["Files & System", "Path join, split, and normalization utilities."],
115
+ "pcre2.runtime" => ["Text & Formatting", "Compiled regex matching runtime over the pcre2 bindings."],
116
+ "pool" => ["Collections", "Fixed-capacity reusable object storage."],
117
+ "priority_queue" => ["Collections", "Priority queue backed by a binary heap."],
118
+ "process" => ["Files & System", "Child process spawning with pipes and exit status."],
119
+ "queue" => ["Collections", "First-in first-out queue."],
120
+ "random" => ["Math & Simulation", "PCG pseudo-random number generation with seeding and ranges."],
121
+ "raylib.debug_console" => ["Game & Graphics", "In-game debug console overlay with command history."],
122
+ "raylib.easing" => ["Game & Graphics", "Easing curves for animation interpolation."],
123
+ "raylib.packed_assets" => ["Game & Graphics", "Load images, textures, and audio from assets.mtpack packs."],
124
+ "raylib.runtime" => ["Game & Graphics", "raylib app lifecycle helpers: window loop, asset directory, fatal errors."],
125
+ "raylib.tracy_gpu" => ["Game & Graphics", "Tracy profiler GPU zone calibration for raylib."],
126
+ "result" => ["Core & Patterns", "Result[T, E] success/failure type for recoverable errors."],
127
+ "ring_buffer" => ["Collections", "Fixed-capacity circular buffer with overwrite behavior."],
128
+ "sdl3.runtime" => ["Game & Graphics", "SDL3 app lifecycle helpers and main-loop wrappers."],
129
+ "serialize" => ["Data & Serialization", "Struct field serialization helpers layered on std.binary."],
130
+ "set" => ["Collections", "Hash set of unique values."],
131
+ "signal" => ["Concurrency & Time", "Fixed-capacity observer and publish-subscribe signals."],
132
+ "simd" => ["Math & Simulation", "SIMD load, store, and reduction helpers."],
133
+ "sparse_set" => ["Collections", "Sparse set with dense iteration for integer-keyed data."],
134
+ "spatial" => ["Math & Simulation", "Uniform spatial hash grid for broad-phase queries."],
135
+ "stack" => ["Collections", "Last-in first-out stack."],
136
+ "steering" => ["AI & Behavior", "Craig Reynolds steering behaviors for autonomous motion."],
137
+ "stdio" => ["Files & System", "Stdin, stdout, and stderr FILE wrappers and console I/O."],
138
+ "str" => ["Text & Formatting", "Borrowed string slice helpers: length, compare, search, split."],
139
+ "string" => ["Text & Formatting", "Heap-owned growable string type with builder operations."],
140
+ "sync" => ["Concurrency & Time", "Mutex, condition variable, and once primitives over libuv."],
141
+ "tar" => ["Data & Serialization", "TAR archive reading and writing with 512-byte blocks."],
142
+ "terminal" => ["Files & System", "Terminal size, color, and raw-mode helpers."],
143
+ "thread" => ["Concurrency & Time", "Threads via libuv with join and handle management."],
144
+ "time" => ["Concurrency & Time", "Wall-clock timestamps and monotonic time helpers."],
145
+ "timer" => ["Concurrency & Time", "Countdown and repeating timers."],
146
+ "tls" => ["Security", "TLS client and server streams over libuv handles."],
147
+ "toml" => ["Data & Serialization", "TOML parser for configuration files."],
148
+ "tween" => ["Game & Graphics", "Tweening and easing for smooth value interpolation."],
149
+ "uri" => ["Data & Serialization", "URI parsing and construction, including file and web schemes."],
150
+ "url" => ["Data & Serialization", "URL parsing: scheme, host, path, query, and percent-encoding."],
151
+ "utility" => ["AI & Behavior", "Utility AI with score-based action selection."],
152
+ "vec" => ["Collections", "Growable contiguous array Vec[T], the default dynamic sequence."],
153
+ }.freeze
154
+
155
+ class << self
156
+ def std_root
157
+ File.join(MilkTea.root.to_s, "std")
158
+ end
159
+
160
+ def entries
161
+ discover
162
+ end
163
+
164
+ # Resolves a module name ("mem.arena", "mem/arena", "c.cjson", "fs") to a
165
+ # source path. Prefers the active platform variant, matching the
166
+ # compiler's import resolution rule. Returns nil when unresolvable.
167
+ def resolve(name)
168
+ clean = name.to_s.strip
169
+ return nil if clean.empty? || clean.include?("..")
170
+
171
+ relative = clean.tr(".", "/").delete_suffix(".mt")
172
+ root = std_root
173
+
174
+ variant = File.join(root, "#{relative}.#{MilkTea.host_platform}.mt")
175
+ return variant if File.file?(variant)
176
+
177
+ shared = File.join(root, "#{relative}.mt")
178
+ return shared if File.file?(shared)
179
+
180
+ nil
181
+ end
182
+
183
+ def discover
184
+ root = std_root
185
+ by_name = {}
186
+
187
+ module_files(root).each do |path|
188
+ add_module(by_name, logical_name(path), path)
189
+ end
190
+
191
+ namespace_directories(root).each do |directory|
192
+ namespace = File.basename(directory)
193
+ module_files(directory).each do |path|
194
+ add_module(by_name, "#{namespace}.#{logical_name(path)}", path)
195
+ end
196
+ end
197
+
198
+ by_name.values.sort_by(&:name)
199
+ end
200
+
201
+ def add_module(by_name, name, path)
202
+ return if generated?(path)
203
+
204
+ category, description = REGISTRY.fetch(name, [nil, nil])
205
+ description ||= doc_comment_summary(path)
206
+ entry = by_name[name]
207
+ unless entry
208
+ by_name[name] = Entry.new(name:, description:, category:, path:)
209
+ return
210
+ end
211
+
212
+ entry.path = path if prefer_path?(path, entry.path)
213
+ end
214
+
215
+ # A shared file beats any variant; among variants the host platform
216
+ # wins, then the first alphabetically.
217
+ def prefer_path?(candidate, current)
218
+ candidate_rank = path_rank(candidate)
219
+ current_rank = path_rank(current)
220
+ order = candidate_rank <=> current_rank
221
+ order.negative? || (order.zero? && candidate < current)
222
+ end
223
+
224
+ def path_rank(path)
225
+ host = MilkTea.host_platform.to_s
226
+ [platform_variant?(path) ? 1 : 0, platform_of(path) == host ? 0 : 1]
227
+ end
228
+
229
+ def module_files(directory)
230
+ Dir.glob(File.join(directory, "*.mt")).sort
231
+ end
232
+
233
+ def namespace_directories(root)
234
+ Dir.children(root).map { |child| File.join(root, child) }
235
+ .select { |path| File.directory?(path) }
236
+ .reject { |path| EXCLUDED_DIRECTORIES.include?(File.basename(path)) }
237
+ .sort
238
+ end
239
+
240
+ def logical_name(path)
241
+ stem = File.basename(path, ".mt")
242
+ platform = platform_of(path)
243
+ platform ? stem.delete_suffix(".#{platform}") : stem
244
+ end
245
+
246
+ def platform_variant?(path)
247
+ PLATFORM_SUFFIXES.include?(platform_of(path))
248
+ end
249
+
250
+ def platform_of(path)
251
+ stem = File.basename(path, ".mt")
252
+ suffix = stem[/\.([^.]+)\z/, 1]
253
+ suffix && PLATFORM_SUFFIXES.include?(suffix) ? suffix : nil
254
+ end
255
+
256
+ def generated?(path)
257
+ File.open(path, "r") { |file| file.readline.start_with?(GENERATED_HEADER_PREFIX) }
258
+ rescue EOFError, Errno::ENOENT
259
+ false
260
+ end
261
+
262
+ def doc_comment_summary(path)
263
+ File.foreach(path) do |line|
264
+ stripped = line.strip
265
+ next if stripped.empty?
266
+
267
+ return stripped.sub(/\A##\s?/, "").rstrip if stripped.start_with?("##")
268
+
269
+ break
270
+ end
271
+ nil
272
+ rescue Errno::ENOENT
273
+ nil
274
+ end
275
+ end
276
+ end
277
+ end
@@ -15,6 +15,7 @@ require_relative "tooling/error_formatter"
15
15
  require_relative "tooling/formatter"
16
16
  require_relative "tooling/linter"
17
17
  require_relative "tooling/docs_app"
18
+ require_relative "tooling/std_catalog"
18
19
  require_relative "tooling/project_scaffold"
19
20
  require_relative "tooling/toolchain_cli"
20
21
  require_relative "tooling/bindgen_cli"
@@ -79,23 +79,22 @@ function work_as_req(work: ptr[NativeWorkRequest]) -> ptr[NativeRequest]:
79
79
  return unsafe: ptr[NativeRequest]<-work
80
80
 
81
81
 
82
- function close_all_handles_cb(handle: ptr[NativeHandle], arg: ptr[void]) -> void:
82
+ function close_all_handles_cb(handle: ptr[NativeHandle], _arg: ptr[void]) -> void:
83
83
  if libuv.is_closing(handle) == 0:
84
84
  libuv.close(handle, close_all_handles_close_cb)
85
- unsafe: arg
86
85
 
87
86
 
88
- function close_all_handles_close_cb(handle: ptr[NativeHandle]) -> void:
89
- unsafe: ptr[void]<-handle
87
+ function close_all_handles_close_cb(_handle: ptr[NativeHandle]) -> void:
88
+ pass
90
89
 
91
90
 
92
- function noop_waiter(frame: ptr[void]) -> void:
93
- unsafe: frame
91
+ function noop_waiter(_frame: ptr[void]) -> void:
92
+ pass
94
93
 
95
94
 
96
95
  function require_current_runtime() -> Runtime:
97
96
  if not current_runtime_active:
98
- fatal(c"async runtime requires an active runtime; use async.wait or async.run, or call the explicit *_on helpers")
97
+ fatal(c"async runtime requires an active runtime; use async.wait or async.run, or call the *_on helpers")
99
98
  return current_runtime
100
99
 
101
100
 
@@ -337,20 +336,19 @@ public function sleep_on(runtime: Runtime, timeout: ptr_uint) -> Task[int]:
337
336
  let loop = live_loop(runtime)
338
337
 
339
338
  let state = heap.must_alloc_zeroed[SleepState](1)
340
- unsafe:
341
- state.ready = false
342
- state.status = 0
343
- state.waiter_frame = null
344
- state.waiter = noop_waiter
345
- state.waiter_registered = false
346
- state.timer = null
347
- state.closing = false
348
- state.closed = false
349
- state.released = false
339
+ state.ready = false
340
+ state.status = 0
341
+ state.waiter_frame = null
342
+ state.waiter = noop_waiter
343
+ state.waiter_registered = false
344
+ state.timer = null
345
+ state.closing = false
346
+ state.closed = false
347
+ state.released = false
350
348
 
351
349
  let timer_size = libuv.handle_size(libuv.uv_handle_type.UV_TIMER)
352
350
  let timer = unsafe: ptr[NativeTimerHandle]<-heap.must_alloc_zeroed_bytes(1, timer_size)
353
- unsafe: state.timer = timer
351
+ state.timer = timer
354
352
 
355
353
  let init_status = libuv.timer_init(loop, timer)
356
354
  if init_status != 0:
@@ -367,11 +365,10 @@ public function sleep_on(runtime: Runtime, timeout: ptr_uint) -> Task[int]:
367
365
 
368
366
  let start_status = libuv.timer_start(timer, sleep_timer_fire, timeout, 0)
369
367
  if start_status != 0:
370
- unsafe:
371
- state.status = start_status
372
- state.ready = true
373
- state.closing = true
374
- libuv.close(timer_as_handle(timer), sleep_timer_close)
368
+ state.status = start_status
369
+ state.ready = true
370
+ state.closing = true
371
+ libuv.close(timer_as_handle(timer), sleep_timer_close)
375
372
 
376
373
  return sleep_task(state)
377
374
 
data/std/async/mailbox.mt CHANGED
@@ -45,8 +45,8 @@ function handle_as_async(handle: ptr[NativeHandle]) -> ptr[NativeAsyncHandle]:
45
45
  return unsafe: ptr[NativeAsyncHandle]<-handle
46
46
 
47
47
 
48
- function mailbox_async_callback(handle: ptr[NativeAsyncHandle]) -> void:
49
- unsafe: handle
48
+ function mailbox_async_callback(_handle: ptr[NativeAsyncHandle]) -> void:
49
+ pass
50
50
 
51
51
 
52
52
  function mailbox_destroy_state[T](state_frame: ptr[void]) -> void:
data/std/base64.mt CHANGED
@@ -163,18 +163,15 @@ public function decode(text_value: str) -> Result[bytes.Bytes, Error]:
163
163
 
164
164
  let combined = (ulong<-raw0 << 18) | (ulong<-raw1 << 12) | (ulong<-raw2 << 6) | ulong<-raw3
165
165
 
166
- unsafe:
167
- read(output + out_index) = ubyte<-( (combined >> 16) & 0xFF)
166
+ read(output + out_index) = ubyte<-( (combined >> 16) & 0xFF)
168
167
  out_index += 1
169
168
 
170
169
  if out_index < output_len:
171
- unsafe:
172
- read(output + out_index) = ubyte<-( (combined >> 8) & 0xFF)
170
+ read(output + out_index) = ubyte<-( (combined >> 8) & 0xFF)
173
171
  out_index += 1
174
172
 
175
173
  if out_index < output_len:
176
- unsafe:
177
- read(output + out_index) = ubyte<-(combined & 0xFF)
174
+ read(output + out_index) = ubyte<-(combined & 0xFF)
178
175
  out_index += 1
179
176
 
180
177
  return Result[bytes.Bytes, Error].success(value = bytes.Bytes(data = output, len = output_len))
data/std/behavior_tree.mt CHANGED
@@ -233,11 +233,8 @@ function tick_node[Context](tree: ref[Tree[Context]], node_id: ptr_uint, context
233
233
  match child_status:
234
234
  Status.success:
235
235
  return Status.success
236
- Status.failure:
237
- return Status.running
238
- Status.running:
236
+ Status.failure | Status.running:
239
237
  return Status.running
240
-
241
238
  NodeKind.until_failure:
242
239
  let child_ptr = read(node).children.first() else:
243
240
  return Status.failure
@@ -251,8 +248,6 @@ function tick_node[Context](tree: ref[Tree[Context]], node_id: ptr_uint, context
251
248
  Status.running:
252
249
  return Status.running
253
250
 
254
- return Status.failure
255
-
256
251
 
257
252
  extending Node[Context]:
258
253
  static function create(kind: NodeKind) -> Node[Context]:
data/std/binary.mt CHANGED
@@ -95,10 +95,7 @@ extending Writer:
95
95
 
96
96
 
97
97
  public editable function write_bool(value: bool) -> void:
98
- if value:
99
- writer_append_byte(ref_of(this), 1)
100
- else:
101
- writer_append_byte(ref_of(this), 0)
98
+ if value: writer_append_byte(ref_of(this), 1) else: writer_append_byte(ref_of(this), 0)
102
99
 
103
100
 
104
101
  public editable function write_bytes(data: span[ubyte]) -> void:
@@ -268,7 +265,7 @@ extending Reader:
268
265
 
269
266
  public editable function read_span(count: ptr_uint) -> Result[span[ubyte], Error]:
270
267
  if count == 0:
271
- let empty = unsafe: span[ubyte](data = this.data.data, len = 0)
268
+ let empty = span[ubyte](data = this.data.data, len = 0)
272
269
  return Result[span[ubyte], Error].success(value = empty)
273
270
 
274
271
  reader_check_remaining(ref_of(this), count)?