mt-lang 0.4.1 → 0.4.21

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.
@@ -190,7 +190,8 @@ module MilkTea
190
190
  "return type mismatch: expected #{return_type}, got #{value_type}",
191
191
  expression: statement.value,
192
192
  contextual_int_to_float: contextual_int_to_float_target?(return_type),
193
- line: statement.line,
193
+ line: (statement.line unless statement.value),
194
+ column: (statement.column unless statement.value),
194
195
  )
195
196
  when AST::DeferStmt
196
197
  with_loop_barrier do
@@ -459,8 +459,8 @@ module MilkTea
459
459
  return nil if prefix.empty?
460
460
  return nil unless @current_completion_trigger_kind == TRIGGER_KIND_INCOMPLETE
461
461
 
462
- content = @workspace.get_content(uri)
463
- line_prefix = (content.split("\n", -1)[lsp_line] || '')[0...lsp_char]
462
+ lines = @workspace.document_lines(uri)
463
+ line_prefix = (lines[lsp_line] || '')[0...lsp_char]
464
464
  cached = @completion_session_cache[[uri, lsp_line]]
465
465
  return nil unless cached
466
466
  return nil unless line_prefix.start_with?(cached[:line_prefix])
@@ -473,8 +473,8 @@ module MilkTea
473
473
  def store_completion_session(uri, lsp_line, lsp_char, prefix, response)
474
474
  return if prefix.empty?
475
475
 
476
- content = @workspace.get_content(uri)
477
- line_prefix = (content.split("\n", -1)[lsp_line] || '')[0...lsp_char]
476
+ lines = @workspace.document_lines(uri)
477
+ line_prefix = (lines[lsp_line] || '')[0...lsp_char]
478
478
  key = [uri, lsp_line]
479
479
  @completion_session_cache[key] = {
480
480
  line_prefix: line_prefix,
@@ -875,8 +875,7 @@ module MilkTea
875
875
  end
876
876
 
877
877
  def import_completions(uri, lsp_line, lsp_char)
878
- content = @workspace.get_content(uri)
879
- lines = content.split("\n", -1)
878
+ lines = @workspace.document_lines(uri)
880
879
  line = lines[lsp_line] || ''
881
880
  stripped = line.lstrip
882
881
  return nil unless stripped.start_with?('import ')
@@ -936,9 +935,7 @@ module MilkTea
936
935
  end
937
936
 
938
937
  def attribute_completions(facts, uri, line, char)
939
- content = @workspace.get_content(uri)
940
- return nil unless content
941
- lines = content.split("\n", -1)
938
+ lines = @workspace.document_lines(uri)
942
939
  line_text = lines[line] || ''
943
940
  return nil if line_text.empty?
944
941
 
@@ -980,9 +977,7 @@ module MilkTea
980
977
  def format_string_completions(facts, uri, line, char)
981
978
  return nil unless facts
982
979
 
983
- content = @workspace.get_content(uri)
984
- return nil unless content
985
- lines = content.split("\n", -1)
980
+ lines = @workspace.document_lines(uri)
986
981
  line_text = lines[line] || ''
987
982
  return nil if line_text.empty?
988
983
 
@@ -1045,9 +1040,7 @@ module MilkTea
1045
1040
  def named_argument_completions(facts, uri, line, char)
1046
1041
  return nil unless facts
1047
1042
 
1048
- content = @workspace.get_content(uri)
1049
- return nil unless content
1050
- lines = content.split("\n", -1)
1043
+ lines = @workspace.document_lines(uri)
1051
1044
  line_text = lines[line] || ''
1052
1045
  return nil if line_text.empty?
1053
1046
 
@@ -1109,9 +1102,7 @@ module MilkTea
1109
1102
  def specialization_completions(facts, _uri, line, char)
1110
1103
  return nil unless facts
1111
1104
 
1112
- content = @workspace.get_content(_uri)
1113
- return nil unless content
1114
- lines = content.split("\n", -1)
1105
+ lines = @workspace.document_lines(_uri)
1115
1106
  line_text = lines[line] || ''
1116
1107
  return nil if line_text.empty?
1117
1108
 
@@ -295,9 +295,8 @@ module MilkTea
295
295
  end
296
296
 
297
297
  def current_word_prefix(uri, lsp_line, lsp_char)
298
- content = @workspace.get_content(uri)
299
- lines = content.split("\n", -1)
300
- line = lines[lsp_line] || ''
298
+ lines = @workspace.document_lines(uri)
299
+ line = lines[lsp_line] || ''
301
300
  # Walk backwards from cursor to find start of current word
302
301
  char_idx = [lsp_char - 1, line.length - 1].min
303
302
  return '' if char_idx < 0
@@ -284,6 +284,8 @@ module MilkTea
284
284
  def invalidate_cache(uri, clear_last_good: false)
285
285
  @tokens_cache.delete(uri)
286
286
  @ast_cache.delete(uri)
287
+ @definition_token_index.delete(uri)
288
+ @line_cache.delete(uri)
287
289
  @symbols_cache.delete(uri)
288
290
  @doc_comments_cache.delete(uri)
289
291
  @facts_cache_mutex.synchronize do
@@ -48,7 +48,6 @@ module MilkTea
48
48
  private
49
49
 
50
50
  def candidate_definition_uris(name, exclude_uri: nil)
51
- matcher = definition_line_matcher(name)
52
51
  open_uris, indexed_uris = @document_state_mutex.synchronize do
53
52
  [@open_documents.keys, @indexed_documents.keys]
54
53
  end
@@ -57,33 +56,33 @@ module MilkTea
57
56
  warmed_candidates = nil
58
57
  warmed_uris = nil
59
58
  @definition_cache_mutex.synchronize do
60
- warmed_candidates = @definition_candidate_uris[name].dup
59
+ warmed_candidates = @definition_candidate_uris[name].to_a
61
60
  warmed_uris = @definition_names_by_uri.keys.to_set
62
61
  end
63
62
 
64
- matches = warmed_candidates.to_a.filter_map do |doc_uri|
65
- next if doc_uri == exclude_uri
66
-
67
- doc_uri
68
- end
63
+ matches = warmed_candidates.reject { |doc_uri| doc_uri == exclude_uri }
69
64
 
65
+ # One-time lazy index: extract each document's definition names and
66
+ # cache them so repeat lookups are Set-membership checks instead of
67
+ # re-scanning every indexed document's full content with a regex on
68
+ # each lookup (which scaled with total workspace bytes).
70
69
  ordered_uris.each do |doc_uri|
71
70
  next if doc_uri == exclude_uri
72
71
  next if warmed_uris.include?(doc_uri)
73
72
 
74
73
  content = get_content(doc_uri)
75
74
  next if content.empty?
76
- next unless content.match?(matcher)
77
75
 
78
76
  warm_definition_candidates_for_uri(doc_uri, content)
79
- matches << doc_uri
80
77
  end
81
78
 
82
- matches.uniq
83
- end
79
+ @definition_cache_mutex.synchronize do
80
+ @definition_candidate_uris[name].each do |doc_uri|
81
+ matches << doc_uri unless doc_uri == exclude_uri
82
+ end
83
+ end
84
84
 
85
- def definition_line_matcher(name)
86
- /#{DEFINITION_LINE_PREFIX}#{Regexp.escape(name)}\b/
85
+ matches.uniq
87
86
  end
88
87
 
89
88
  def cache_definition_entry(name, entry)
@@ -126,13 +126,18 @@ module MilkTea
126
126
  total = paths.length
127
127
  paths.each_with_index do |path, idx|
128
128
  file_uri = path_to_uri(path)
129
+ content = nil
129
130
  @document_state_mutex.synchronize do
130
- @indexed_documents[file_uri] ||= begin
131
+ content = @indexed_documents[file_uri] ||= begin
131
132
  File.read(path)
132
133
  rescue StandardError
133
134
  nil
134
135
  end
135
136
  end
137
+ # Warm the definition-name index for this file so later global
138
+ # definition lookups hit Set-membership checks instead of scanning
139
+ # every indexed document's content.
140
+ warm_definition_candidates_for_uri(file_uri, content) if content
136
141
  if progress && total > 0
137
142
  pct = ((idx + 1) * 100 / total).clamp(0, 100)
138
143
  progress.call(pct, "#{idx + 1}/#{total} files")
@@ -79,10 +79,9 @@ module MilkTea
79
79
  # Scan text up to the cursor to find the innermost open function call context.
80
80
  # Returns { name:, active_parameter: } or nil if not inside a call.
81
81
  def find_call_context(uri, lsp_line, lsp_char)
82
- content = get_content(uri)
83
- return nil if content.empty?
82
+ lines = document_lines(uri)
83
+ return nil if lines.empty?
84
84
 
85
- lines = content.split("\n", -1)
86
85
  cursor_line = lines[lsp_line] || ''
87
86
  prefix = lsp_line.positive? ? lines[0...lsp_line].join("\n") + "\n" : ''
88
87
  text = prefix + cursor_line[0...lsp_char]
@@ -150,14 +149,19 @@ module MilkTea
150
149
  end
151
150
 
152
151
  def token_contains_position?(token, target_line, target_char)
153
- segments = token.lexeme.split("\n", -1)
154
- end_line = token.line + segments.length - 1
155
- return false if target_line < token.line || target_line > end_line
152
+ return false if target_line < token.line
153
+
154
+ lexeme = token.lexeme
155
+ unless lexeme.include?("\n")
156
+ return false if target_line > token.line
156
157
 
157
- if segments.length == 1
158
- return token.column <= target_char && target_char < (token.column + segments.first.length)
158
+ return token.column <= target_char && target_char < (token.column + lexeme.length)
159
159
  end
160
160
 
161
+ segments = lexeme.split("\n", -1)
162
+ end_line = token.line + segments.length - 1
163
+ return false if target_line > end_line
164
+
161
165
  if target_line == token.line
162
166
  return token.column <= target_char && target_char <= (token.column + segments.first.length - 1)
163
167
  end
@@ -178,8 +182,7 @@ module MilkTea
178
182
  end
179
183
 
180
184
  def find_dot_receiver_path(uri, lsp_line, lsp_char)
181
- content = get_content(uri)
182
- lines = content.split("\n", -1)
185
+ lines = document_lines(uri)
183
186
  line_str = lines[lsp_line] || ''
184
187
 
185
188
  idx = [lsp_char - 1, line_str.length - 1].min
@@ -222,39 +225,56 @@ module MilkTea
222
225
  # (def, struct, union, enum, flags, variant, type, const, var) for the given name.
223
226
  # Returns the identifier Token, or nil if not found.
224
227
  def find_definition_token(uri, name, before_line: nil, before_char: nil)
225
- tokens = get_tokens(uri)
226
- return nil if tokens.nil?
227
-
228
- nearest = nil
229
- tokens.each_cons(2) do |kw_tok, id_tok|
230
- next unless DEFINITION_KEYWORDS.include?(kw_tok.type)
231
- next unless id_tok.type == :identifier && id_tok.lexeme == name
228
+ candidates = definition_token_index(uri)[name]
229
+ return nil if candidates.nil? || candidates.empty?
232
230
 
233
- if before_line
234
- next if id_tok.line > before_line
235
- next if id_tok.line == before_line && before_char && id_tok.column >= before_char
236
- end
237
-
238
- if nearest.nil? || id_tok.line > nearest.line || (id_tok.line == nearest.line && id_tok.column > nearest.column)
239
- nearest = id_tok
231
+ if before_line
232
+ matches = candidates.select do |tok|
233
+ tok.line < before_line || (tok.line == before_line && (!before_char || tok.column < before_char))
240
234
  end
235
+ return matches.max_by { |tok| [tok.line, tok.column] } unless matches.empty?
241
236
  end
242
237
 
243
- return nearest if nearest
238
+ candidates.first
239
+ end
244
240
 
245
- tokens.each_cons(2) do |kw_tok, id_tok|
246
- next unless DEFINITION_KEYWORDS.include?(kw_tok.type)
247
- next unless id_tok.type == :identifier && id_tok.lexeme == name
241
+ # Return the cached line array for +uri+, so hot request paths (e.g.
242
+ # completion) do not re-split the whole document once per helper.
243
+ def document_lines(uri)
244
+ @document_state_mutex.synchronize do
245
+ return @line_cache[uri] if @line_cache.key?(uri)
246
+ end
248
247
 
249
- return id_tok
248
+ lines = get_content(uri).split("\n", -1)
249
+ @document_state_mutex.synchronize do
250
+ @line_cache[uri] = lines
250
251
  end
251
- nil
252
+ lines
252
253
  end
253
254
 
254
255
  private
255
256
 
256
257
  # ── Symbol extraction (token-based, no AST position requirement) ────────
257
258
 
259
+ # Lazily build a per-uri map of definition identifier tokens keyed by
260
+ # name, so repeated definition lookups are O(definitions-with-name)
261
+ # instead of re-scanning the document's full token stream each time.
262
+ def definition_token_index(uri)
263
+ @definition_token_index[uri] ||= begin
264
+ tokens = get_tokens(uri)
265
+ index = Hash.new { |hash, key| hash[key] = [] }
266
+ if tokens
267
+ tokens.each_cons(2) do |kw_tok, id_tok|
268
+ next unless DEFINITION_KEYWORDS.include?(kw_tok.type)
269
+ next unless id_tok.type == :identifier
270
+
271
+ index[id_tok.lexeme] << id_tok
272
+ end
273
+ end
274
+ index
275
+ end
276
+ end
277
+
258
278
  def extract_symbols_from_tokens(uri)
259
279
  tokens = get_tokens(uri)
260
280
  return [] if tokens.nil?
@@ -37,7 +37,7 @@ module MilkTea
37
37
  # adjacent tokens, and the inner keyword (e.g. :function) is always followed
38
38
  # by the identifier. If the lexer ever merges a compound keyword into a
39
39
  # single token type (e.g. :const_function), it must be added here.
40
- DEFINITION_KEYWORDS = %i[function struct union enum flags variant type const var let extending opaque interface event].freeze
40
+ DEFINITION_KEYWORDS = %i[function struct union enum flags variant type const var let extending opaque interface event].to_set.freeze
41
41
  DOC_COMMENT_PREFIX = '##'
42
42
  DOC_TAG_PATTERN = /\A\s*@([A-Za-z_][A-Za-z0-9_-]*)(?:\s+(.*))?\z/
43
43
  DEFINITION_LINE_PREFIX = /^(?:\s)*(?:(?:public|foreign|external)\s+)*(?:function|struct|union|enum|flags|variant|type|const|var|let|extending|opaque|interface|event)\s+/m
@@ -55,6 +55,8 @@ module MilkTea
55
55
  @tokens_cache = {} # uri -> [Token]
56
56
  @last_good_tokens_cache = {} # uri -> last known-good [Token]
57
57
  @ast_cache = {} # uri -> AST::SourceFile (nil on parse failure)
58
+ @definition_token_index = {} # uri -> { name => [Token] } definition tokens by name
59
+ @line_cache = {} # uri -> [String] split lines, refreshed on invalidation
58
60
  @facts_cache = {} # uri -> SemanticAnalyzer::Facts (projection of cached tooling snapshot facts)
59
61
  @tooling_snapshot_cache = {} # uri -> SemanticAnalyzer::ToolingSnapshot (facts may be nil on structural failure)
60
62
  @symbols_cache = {} # uri -> [{name, kind, line, column}]
@@ -103,6 +105,8 @@ module MilkTea
103
105
  @tokens_cache.clear
104
106
  @last_good_tokens_cache.clear
105
107
  @ast_cache.clear
108
+ @definition_token_index.clear
109
+ @line_cache.clear
106
110
  @facts_cache.clear
107
111
  @tooling_snapshot_cache.clear
108
112
  @symbols_cache.clear
@@ -72,11 +72,12 @@ module MilkTea
72
72
  argv: @argv.dup,
73
73
  **options.except(:timings)
74
74
  )
75
- unless @out.equal?($stdout) || preview_notice_emitted
75
+ live = $stdout.tty?
76
+ unless (@out.equal?($stdout) && live) || preview_notice_emitted
76
77
  @out.write(result.stdout)
77
78
  end
78
79
  @out.flush if @out.respond_to?(:flush)
79
- @err.write(result.stderr) unless @err.equal?($stderr)
80
+ @err.write(result.stderr) unless @err.equal?($stderr) && live
80
81
  info("[cached]") if result.cached
81
82
  result.exit_status
82
83
  end
@@ -399,21 +399,14 @@ module MilkTea
399
399
  return 1
400
400
  end
401
401
 
402
- testing_import = ast.imports.find { |import| import.path.parts == %w[std testing] }
403
- unless testing_import
404
- @err.puts("a test file must import std.testing: #{path}")
405
- return 1
406
- end
407
- testing_alias = testing_import.alias_name || testing_import.path.parts.last
408
-
409
402
  death_tests, normal_tests = tests.partition { |test| expect_fatal_attribute?(test) }
410
403
 
411
404
  exit_code = 0
412
405
 
413
406
  unless normal_tests.empty?
414
407
  runner_source = source.dup
415
- runner_source << "\n\n" << test_runner_main(testing_alias, normal_tests.map(&:name))
416
- exit_code = run_synthesized_tests(path, runner_source, options:, locked:)
408
+ runner_source << "\n\n" << test_runner_main(normal_tests.map(&:name))
409
+ exit_code = run_normal_tests(path, runner_source, normal_tests.map(&:name), options:, locked:)
417
410
  end
418
411
 
419
412
  death_tests.each do |death_test|
@@ -459,30 +452,53 @@ module MilkTea
459
452
  def death_test_runner_main(test_name)
460
453
  [
461
454
  "function main() -> int:",
462
- " match #{test_name}():",
463
- " Result.success:",
464
- " return 0",
465
- " Result.failure:",
466
- " return 0",
455
+ " #{test_name}()",
456
+ " return 0",
467
457
  ].join("\n") + "\n"
468
458
  end
469
459
 
470
- def test_runner_main(testing_alias, test_names)
471
- lines = ["function main() -> int:"]
472
- lines << " var __mt_test_stats = #{testing_alias}.Stats.create()"
460
+ # The runner binary runs exactly one test per invocation, selected by
461
+ # `argv[1]`. This gives per-test isolation: an aborting assertion in one
462
+ # test cannot suppress the results of its siblings.
463
+ def test_runner_main(test_names)
464
+ lines = ["function main(args: span[str]) -> int:"]
465
+ lines << " let which = if args.len > 1: args[1] else: \"\""
466
+ lines << " match which:"
473
467
  test_names.each do |name|
474
- lines << " __mt_test_stats = #{testing_alias}.record(__mt_test_stats, #{name.inspect}, #{name}())"
468
+ lines << " \"#{name}\":"
469
+ lines << " #{name}()"
475
470
  end
476
- lines << " return #{testing_alias}.summarize(__mt_test_stats)"
471
+ lines << " _:"
472
+ lines << " return 2"
473
+ lines << " return 0"
477
474
  lines.join("\n") + "\n"
478
475
  end
479
476
 
480
- def run_synthesized_tests(source_path, runner_source, options:, locked:)
477
+ def run_normal_tests(source_path, runner_source, test_names, options:, locked:)
481
478
  with_synthesized_binary(source_path, runner_source, options:, locked:) do |binary_path|
482
- run_test_binary(binary_path)
479
+ exit_code = 0
480
+ test_names.each do |name|
481
+ output, status, timed_out = spawn_sandboxed(binary_path, [name])
482
+ if timed_out
483
+ @out.puts("FAIL - #{name}: timed out")
484
+ exit_code = 1
485
+ elsif status&.exitstatus == 0
486
+ @out.puts("ok - #{name}")
487
+ else
488
+ @out.puts("FAIL - #{name}: #{first_fail_message(output)}")
489
+ exit_code = 1
490
+ end
491
+ @out.flush if @out.respond_to?(:flush)
492
+ end
493
+ exit_code
483
494
  end
484
495
  end
485
496
 
497
+ def first_fail_message(output)
498
+ line = output.to_s.lines.map(&:chomp).find { |entry| !entry.strip.empty? }
499
+ line.nil? || line.strip.empty? ? "test aborted" : line.strip
500
+ end
501
+
486
502
  def with_synthesized_binary(source_path, runner_source, options:, locked:)
487
503
  directory = File.dirname(File.expand_path(source_path))
488
504
  runner_path = File.join(directory, "__mt_test_runner_#{Process.pid}.mt")
@@ -508,30 +524,13 @@ module MilkTea
508
524
  end
509
525
  end
510
526
 
511
- def run_test_binary(binary_path)
512
- output, status, timed_out = spawn_sandboxed(binary_path)
513
- @out.write(output)
514
- @out.flush if @out.respond_to?(:flush)
515
-
516
- if timed_out
517
- @err.puts("test run timed out after #{@test_timeout_seconds || TEST_RUN_TIMEOUT_SECONDS}s")
518
- return 1
519
- end
520
- if status&.signaled?
521
- @err.puts("test run crashed (signal #{status.termsig})")
522
- return 1
523
- end
524
-
525
- status&.exitstatus || 1
526
- end
527
-
528
- def spawn_sandboxed(binary_path)
527
+ def spawn_sandboxed(binary_path, args = [])
529
528
  timeout_seconds = @test_timeout_seconds || TEST_RUN_TIMEOUT_SECONDS
530
529
  memory_bytes = @test_memory_bytes || TEST_RUN_MEMORY_LIMIT_BYTES
531
530
  reader, writer = IO.pipe
532
531
  spawn_options = { out: writer, err: writer, pgroup: true }
533
532
  spawn_options[:rlimit_as] = memory_bytes unless @test_sanitize
534
- pid = Process.spawn(binary_path, **spawn_options)
533
+ pid = Process.spawn(binary_path, *args, **spawn_options)
535
534
  writer.close
536
535
 
537
536
  status = nil
@@ -597,10 +597,11 @@ module MilkTea
597
597
  root), recursively discovers every .mt file that contains @[test] functions,
598
598
  runs each as its own test binary, and prints an aggregate summary.
599
599
 
600
- Functions annotated with @[test] must take no parameters and return
601
- std.testing.Check. `mtc test` synthesizes a runner that invokes each
602
- test through std.testing and reports the results; a test file must import
603
- std.testing and must not define `main`.
600
+ Functions annotated with @[test] must take no parameters and return void;
601
+ a failing test aborts via the `assert`/`expect`/`expect_eq`/`expect_ne`
602
+ intrinsics. `mtc test` synthesizes a runner that invokes each test in its
603
+ own process (for isolation) and reports the results. A test file must not
604
+ define `main`.
604
605
 
605
606
  Each test binary runs under a wall-clock timeout and an address-space
606
607
  memory cap so a hanging or runaway test cannot stall or exhaust the host.
@@ -105,14 +105,25 @@ module MilkTea
105
105
  def terminating_expression?(expression)
106
106
  case expression
107
107
  when AST::Call
108
- terminating_callee?(expression.callee) || static_assert_false?(expression)
108
+ terminating_callee?(expression.callee) || static_assert_false?(expression) || assertion_false?(expression)
109
109
  when AST::Specialization
110
- terminating_callee?(expression.callee) || static_assert_false?(expression)
110
+ terminating_callee?(expression.callee) || static_assert_false?(expression) || assertion_false?(expression)
111
111
  else
112
112
  false
113
113
  end
114
114
  end
115
115
 
116
+ def assertion_false?(expression)
117
+ return false unless expression.is_a?(AST::Call)
118
+
119
+ callee = expression.callee
120
+ return false unless callee.is_a?(AST::Identifier)
121
+ return false unless %w[assert expect].include?(callee.name)
122
+
123
+ first_arg = expression.arguments.first
124
+ first_arg.is_a?(AST::BooleanLiteral) && first_arg.value == false
125
+ end
126
+
116
127
  def static_assert_false?(expression)
117
128
  return false unless expression.callee.is_a?(AST::Identifier)
118
129
  return false unless expression.callee.name == "static_assert"
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.4.1
4
+ version: 0.4.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -150,6 +150,7 @@ files:
150
150
  - docs/language-design.md
151
151
  - docs/language-manual.md
152
152
  - docs/lsp-performance.md
153
+ - docs/testing.md
153
154
  - lib/milk_tea.rb
154
155
  - lib/milk_tea/base.rb
155
156
  - lib/milk_tea/bindings.rb
@@ -233,6 +234,7 @@ files:
233
234
  - lib/milk_tea/core/lexer/trivia.rb
234
235
  - lib/milk_tea/core/lowering.rb
235
236
  - lib/milk_tea/core/lowering/artifacts.rb
237
+ - lib/milk_tea/core/lowering/assertions.rb
236
238
  - lib/milk_tea/core/lowering/async/analysis.rb
237
239
  - lib/milk_tea/core/lowering/async/async_lowering.rb
238
240
  - lib/milk_tea/core/lowering/async/frame_builder.rb
@@ -609,7 +611,6 @@ files:
609
611
  - std/sync.mt
610
612
  - std/tar.mt
611
613
  - std/terminal.mt
612
- - std/testing.mt
613
614
  - std/thread.mt
614
615
  - std/time.mt
615
616
  - std/timer.mt
@@ -629,7 +630,7 @@ metadata:
629
630
  homepage_uri: https://teefan.github.io/mt-lang/
630
631
  source_code_uri: https://github.com/teefan/mt-lang
631
632
  post_install_message: |
632
- Milk Tea 0.4.1 installed!
633
+ Milk Tea 0.4.21 installed!
633
634
 
634
635
  System requirements:
635
636
  - A C compiler (gcc or clang) must be available on PATH