t-ruby 0.0.34 → 0.0.36

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.
@@ -442,6 +442,7 @@ module TRuby
442
442
 
443
443
  def simplify
444
444
  return TRUE if @left == @right
445
+
445
446
  self
446
447
  end
447
448
 
@@ -634,17 +635,17 @@ module TRuby
634
635
 
635
636
  # Type hierarchy (built-in)
636
637
  TYPE_HIERARCHY = {
637
- "Integer" => ["Numeric", "Object"],
638
- "Float" => ["Numeric", "Object"],
638
+ "Integer" => %w[Numeric Object],
639
+ "Float" => %w[Numeric Object],
639
640
  "Numeric" => ["Object"],
640
641
  "String" => ["Object"],
641
- "Array" => ["Enumerable", "Object"],
642
- "Hash" => ["Enumerable", "Object"],
642
+ "Array" => %w[Enumerable Object],
643
+ "Hash" => %w[Enumerable Object],
643
644
  "Enumerable" => ["Object"],
644
645
  "Boolean" => ["Object"],
645
646
  "Symbol" => ["Object"],
646
647
  "nil" => ["Object"],
647
- "Object" => []
648
+ "Object" => [],
648
649
  }.freeze
649
650
 
650
651
  def initialize
@@ -694,7 +695,7 @@ module TRuby
694
695
  {
695
696
  success: @errors.empty?,
696
697
  solution: @solution,
697
- errors: @errors
698
+ errors: @errors,
698
699
  }
699
700
  end
700
701
 
@@ -748,18 +749,21 @@ module TRuby
748
749
  # If left is type variable, bind it
749
750
  if left.is_a?(TypeVar)
750
751
  return nil if occurs_check(left, right)
752
+
751
753
  return { left.name => right }
752
754
  end
753
755
 
754
756
  # If right is type variable, bind it
755
757
  if right.is_a?(TypeVar)
756
758
  return nil if occurs_check(right, left)
759
+
757
760
  return { right.name => left }
758
761
  end
759
762
 
760
763
  # Both are concrete types
761
764
  if left.is_a?(ConcreteType) && right.is_a?(ConcreteType)
762
765
  return {} if left.name == right.name
766
+
763
767
  return nil
764
768
  end
765
769
 
@@ -768,6 +772,7 @@ module TRuby
768
772
 
769
773
  def occurs_check(var, type)
770
774
  return false unless type.respond_to?(:free_variables)
775
+
771
776
  type.free_variables.include?(var.name)
772
777
  end
773
778
 
@@ -800,7 +805,7 @@ module TRuby
800
805
  end
801
806
 
802
807
  def instantiate_remaining
803
- @type_vars.each do |name, var|
808
+ @type_vars.each_key do |name|
804
809
  next if @solution[name]
805
810
 
806
811
  # Default to Object if no constraints
@@ -824,24 +829,23 @@ module TRuby
824
829
  # Infer types for a method
825
830
  def infer_method(method_ir)
826
831
  param_types = {}
827
- return_type = nil
828
832
 
829
833
  # Create type variables for parameters without annotations
830
834
  method_ir.params.each do |param|
831
- if param.type_annotation
832
- param_types[param.name] = type_from_ir(param.type_annotation)
833
- else
834
- param_types[param.name] = @solver.fresh_var("P_#{param.name}")
835
- end
835
+ param_types[param.name] = if param.type_annotation
836
+ type_from_ir(param.type_annotation)
837
+ else
838
+ @solver.fresh_var("P_#{param.name}")
839
+ end
836
840
  @type_env[param.name] = param_types[param.name]
837
841
  end
838
842
 
839
843
  # Create type variable for return type if not annotated
840
844
  return_type = if method_ir.return_type
841
- type_from_ir(method_ir.return_type)
842
- else
843
- @solver.fresh_var("R_#{method_ir.name}")
844
- end
845
+ type_from_ir(method_ir.return_type)
846
+ else
847
+ @solver.fresh_var("R_#{method_ir.name}")
848
+ end
845
849
 
846
850
  # Analyze body to generate constraints
847
851
  if method_ir.body
@@ -862,12 +866,12 @@ module TRuby
862
866
  {
863
867
  success: true,
864
868
  params: inferred_params,
865
- return_type: inferred_return
869
+ return_type: inferred_return,
866
870
  }
867
871
  else
868
872
  {
869
873
  success: false,
870
- errors: result[:errors]
874
+ errors: result[:errors],
871
875
  }
872
876
  end
873
877
  end
@@ -977,17 +981,17 @@ module TRuby
977
981
  def infer_method_call(call)
978
982
  # Get receiver type
979
983
  receiver_type = if call.receiver
980
- infer_expression(call.receiver)
981
- else
982
- @type_env["self"] || ConcreteType.new("Object")
983
- end
984
+ infer_expression(call.receiver)
985
+ else
986
+ @type_env["self"] || ConcreteType.new("Object")
987
+ end
984
988
 
985
989
  # Look up method return type
986
990
  return_type = lookup_method_type(receiver_type, call.method_name)
987
991
  return_type || @solver.fresh_var("M_#{call.method_name}")
988
992
  end
989
993
 
990
- def lookup_method_type(receiver, method)
994
+ def lookup_method_type(_receiver, method)
991
995
  # Built-in method types
992
996
  method_types = {
993
997
  "to_s" => ConcreteType.new("String"),
@@ -996,7 +1000,7 @@ module TRuby
996
1000
  "length" => ConcreteType.new("Integer"),
997
1001
  "size" => ConcreteType.new("Integer"),
998
1002
  "empty?" => ConcreteType.new("Boolean"),
999
- "nil?" => ConcreteType.new("Boolean")
1003
+ "nil?" => ConcreteType.new("Boolean"),
1000
1004
  }
1001
1005
 
1002
1006
  method_types[method.to_s]
@@ -1022,16 +1026,14 @@ module TRuby
1022
1026
  end
1023
1027
 
1024
1028
  def infer_array_literal(expr)
1025
- if expr.elements.empty?
1026
- ConcreteType.new("Array")
1027
- else
1029
+ unless expr.elements.empty?
1028
1030
  element_type = @solver.fresh_var("E")
1029
1031
  expr.elements.each do |elem|
1030
1032
  elem_type = infer_expression(elem)
1031
1033
  @solver.add_subtype(elem_type, element_type)
1032
1034
  end
1033
- ConcreteType.new("Array")
1034
1035
  end
1036
+ ConcreteType.new("Array")
1035
1037
  end
1036
1038
  end
1037
1039
 
@@ -45,6 +45,7 @@ module TRuby
45
45
 
46
46
  def valid_type?(name)
47
47
  return true if BUILT_IN_TYPES.include?(name)
48
+
48
49
  @aliases.key?(name)
49
50
  end
50
51
 
@@ -30,7 +30,7 @@ module TRuby
30
30
  location: @location,
31
31
  expected: @expected,
32
32
  actual: @actual,
33
- suggestion: @suggestion
33
+ suggestion: @suggestion,
34
34
  }
35
35
  end
36
36
  end
@@ -42,17 +42,17 @@ module TRuby
42
42
 
43
43
  def initialize
44
44
  @subtypes = {
45
- "Integer" => ["Numeric", "Object"],
46
- "Float" => ["Numeric", "Object"],
45
+ "Integer" => %w[Numeric Object],
46
+ "Float" => %w[Numeric Object],
47
47
  "Numeric" => ["Object"],
48
48
  "String" => ["Object"],
49
49
  "Symbol" => ["Object"],
50
- "Array" => ["Enumerable", "Object"],
51
- "Hash" => ["Enumerable", "Object"],
52
- "Set" => ["Enumerable", "Object"],
50
+ "Array" => %w[Enumerable Object],
51
+ "Hash" => %w[Enumerable Object],
52
+ "Set" => %w[Enumerable Object],
53
53
  "Boolean" => ["Object"],
54
54
  "nil" => ["Object"],
55
- "Object" => []
55
+ "Object" => [],
56
56
  }
57
57
  end
58
58
 
@@ -206,7 +206,7 @@ module TRuby
206
206
  {
207
207
  success: @errors.empty?,
208
208
  errors: @errors,
209
- warnings: @warnings
209
+ warnings: @warnings,
210
210
  }
211
211
  end
212
212
 
@@ -220,10 +220,10 @@ module TRuby
220
220
  params: method_ir.params.map do |p|
221
221
  {
222
222
  name: p.name,
223
- type: result[:params][p.name] || infer_param_type(p)
223
+ type: result[:params][p.name] || infer_param_type(p),
224
224
  }
225
225
  end,
226
- return_type: result[:return_type]
226
+ return_type: result[:return_type],
227
227
  }
228
228
  else
229
229
  # Add errors from SMT solver
@@ -305,7 +305,7 @@ module TRuby
305
305
  def register_function(name, params:, return_type:)
306
306
  @function_signatures[name] = {
307
307
  params: params,
308
- return_type: return_type
308
+ return_type: return_type,
309
309
  }
310
310
  end
311
311
 
@@ -345,15 +345,15 @@ module TRuby
345
345
 
346
346
  next unless arg_type
347
347
 
348
- unless type_compatible?(arg_type, param[:type])
349
- add_error(
350
- message: "Type mismatch in argument '#{param[:name]}' of #{function_name}",
351
- expected: param[:type],
352
- actual: arg_type,
353
- suggestion: suggest_conversion(arg_type, param[:type]),
354
- location: location
355
- )
356
- end
348
+ next if type_compatible?(arg_type, param[:type])
349
+
350
+ add_error(
351
+ message: "Type mismatch in argument '#{param[:name]}' of #{function_name}",
352
+ expected: param[:type],
353
+ actual: arg_type,
354
+ suggestion: suggest_conversion(arg_type, param[:type]),
355
+ location: location
356
+ )
357
357
  end
358
358
 
359
359
  signature[:return_type]
@@ -384,16 +384,14 @@ module TRuby
384
384
  def check_assignment(variable, value, declared_type: nil, location: nil)
385
385
  value_type = infer_type(value)
386
386
 
387
- if declared_type
388
- unless type_compatible?(value_type, declared_type)
389
- add_error(
390
- message: "Cannot assign #{value_type} to variable of type #{declared_type}",
391
- expected: declared_type,
392
- actual: value_type,
393
- location: location
394
- )
395
- return false
396
- end
387
+ if declared_type && !type_compatible?(value_type, declared_type)
388
+ add_error(
389
+ message: "Cannot assign #{value_type} to variable of type #{declared_type}",
390
+ expected: declared_type,
391
+ actual: value_type,
392
+ location: location
393
+ )
394
+ return false
397
395
  end
398
396
 
399
397
  @current_scope.define(variable, declared_type || value_type)
@@ -408,7 +406,7 @@ module TRuby
408
406
  "Array" => %w[length size first last empty? count],
409
407
  "Hash" => %w[keys values size empty? length],
410
408
  "Integer" => %w[abs to_s to_f even? odd? positive? negative?],
411
- "Float" => %w[abs to_s to_i ceil floor round]
409
+ "Float" => %w[abs to_s to_i ceil floor round],
412
410
  }
413
411
 
414
412
  properties = known_properties[receiver_type]
@@ -451,6 +449,7 @@ module TRuby
451
449
 
452
450
  # Result type depends on operands
453
451
  return "Float" if left_type == "Float" || right_type == "Float"
452
+
454
453
  return "Integer"
455
454
  end
456
455
 
@@ -490,14 +489,14 @@ module TRuby
490
489
  else_scope&.narrow(var, remove_nil_from_type(@current_scope.lookup(var) || "Object"))
491
490
  end
492
491
 
493
- if condition.match?(/!(\w+)\.nil\?/)
494
- match = condition.match(/!(\w+)\.nil\?/)
495
- var = match[1]
492
+ return unless condition.match?(/!(\w+)\.nil\?/)
496
493
 
497
- # Variable is not nil in then branch
498
- then_scope.narrow(var, remove_nil_from_type(@current_scope.lookup(var) || "Object"))
499
- else_scope&.narrow(var, "nil")
500
- end
494
+ match = condition.match(/!(\w+)\.nil\?/)
495
+ var = match[1]
496
+
497
+ # Variable is not nil in then branch
498
+ then_scope.narrow(var, remove_nil_from_type(@current_scope.lookup(var) || "Object"))
499
+ else_scope&.narrow(var, "nil")
501
500
  end
502
501
 
503
502
  # Check a complete function
@@ -528,7 +527,7 @@ module TRuby
528
527
 
529
528
  # Return statement
530
529
  if line.match?(/^return\s+(.+)/)
531
- match = line.match(/^return\s+(.+)/)
530
+ line.match(/^return\s+(.+)/)
532
531
  # Would need current function context for proper checking
533
532
  return
534
533
  end
@@ -573,8 +572,10 @@ module TRuby
573
572
 
574
573
  # Check if a type name is known
575
574
  def known_type?(type_name)
576
- return true if %w[String Integer Float Boolean Array Hash Symbol void nil Object Numeric Enumerable].include?(type_name)
575
+ return true if %w[String Integer Float Boolean Array Hash Symbol void nil Object Numeric
576
+ Enumerable].include?(type_name)
577
577
  return true if @type_aliases.key?(type_name)
578
+
578
579
  false
579
580
  end
580
581
 
@@ -611,7 +612,7 @@ module TRuby
611
612
  {
612
613
  success: @errors.empty?,
613
614
  errors: @errors,
614
- warnings: @warnings
615
+ warnings: @warnings,
615
616
  }
616
617
  end
617
618
 
@@ -685,14 +686,14 @@ module TRuby
685
686
 
686
687
  def suggest_conversion(from_type, to_type)
687
688
  conversions = {
688
- ["Integer", "String"] => "Use .to_s to convert to String",
689
- ["Float", "String"] => "Use .to_s to convert to String",
690
- ["String", "Integer"] => "Use .to_i to convert to Integer",
691
- ["String", "Float"] => "Use .to_f to convert to Float",
692
- ["Integer", "Float"] => "Use .to_f to convert to Float",
693
- ["Float", "Integer"] => "Use .to_i to convert to Integer (may lose precision)",
694
- ["Symbol", "String"] => "Use .to_s to convert to String",
695
- ["String", "Symbol"] => "Use .to_sym to convert to Symbol"
689
+ %w[Integer String] => "Use .to_s to convert to String",
690
+ %w[Float String] => "Use .to_s to convert to String",
691
+ %w[String Integer] => "Use .to_i to convert to Integer",
692
+ %w[String Float] => "Use .to_f to convert to Float",
693
+ %w[Integer Float] => "Use .to_f to convert to Float",
694
+ %w[Float Integer] => "Use .to_i to convert to Integer (may lose precision)",
695
+ %w[Symbol String] => "Use .to_s to convert to String",
696
+ %w[String Symbol] => "Use .to_sym to convert to Symbol",
696
697
  }
697
698
 
698
699
  conversions[[from_type, to_type]]
@@ -700,22 +701,22 @@ module TRuby
700
701
 
701
702
  def infer_property_type(receiver_type, property)
702
703
  property_types = {
703
- ["String", "length"] => "Integer",
704
- ["String", "size"] => "Integer",
704
+ %w[String length] => "Integer",
705
+ %w[String size] => "Integer",
705
706
  ["String", "empty?"] => "Boolean",
706
- ["String", "chars"] => "Array<String>",
707
- ["Array", "length"] => "Integer",
708
- ["Array", "size"] => "Integer",
707
+ %w[String chars] => "Array<String>",
708
+ %w[Array length] => "Integer",
709
+ %w[Array size] => "Integer",
709
710
  ["Array", "empty?"] => "Boolean",
710
- ["Array", "first"] => nil, # Depends on element type
711
- ["Array", "last"] => nil,
712
- ["Hash", "keys"] => "Array",
713
- ["Hash", "values"] => "Array",
714
- ["Hash", "size"] => "Integer",
715
- ["Integer", "abs"] => "Integer",
716
- ["Integer", "to_s"] => "String",
717
- ["Float", "abs"] => "Float",
718
- ["Float", "to_i"] => "Integer"
711
+ %w[Array first] => nil, # Depends on element type
712
+ %w[Array last] => nil,
713
+ %w[Hash keys] => "Array",
714
+ %w[Hash values] => "Array",
715
+ %w[Hash size] => "Integer",
716
+ %w[Integer abs] => "Integer",
717
+ %w[Integer to_s] => "String",
718
+ %w[Float abs] => "Float",
719
+ %w[Float to_i] => "Integer",
719
720
  }
720
721
 
721
722
  property_types[[receiver_type, property]]
@@ -10,7 +10,7 @@ module TRuby
10
10
  result = @source.dup
11
11
 
12
12
  # Remove type alias definitions: type AliasName = TypeDefinition
13
- result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/, '')
13
+ result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/, "")
14
14
 
15
15
  # Remove parameter type annotations: (name: Type) -> (name)
16
16
  # Matches: parameter_name: TypeName
@@ -18,9 +18,7 @@ module TRuby
18
18
 
19
19
  # Remove return type annotations: ): TypeName -> )
20
20
  # Matches: ): TypeName or ): TypeName (with spaces/EOF)
21
- result = result.gsub(/\)\s*:\s*\w+(\s|$)/, ')\1')
22
-
23
- result
21
+ result.gsub(/\)\s*:\s*\w+(\s|$)/, ')\1')
24
22
  end
25
23
  end
26
24
  end
@@ -63,7 +63,7 @@ module TRuby
63
63
  # Hash literals
64
64
  /^\{.*\}$/ => "Hash",
65
65
  # Regex literals
66
- /^\/.*\/$/ => "Regexp"
66
+ %r{^/.*/$} => "Regexp",
67
67
  }.freeze
68
68
 
69
69
  # Method return type patterns
@@ -131,7 +131,7 @@ module TRuby
131
131
  "equal?" => "Boolean",
132
132
  "inspect" => "String",
133
133
  "dup" => nil,
134
- "clone" => nil
134
+ "clone" => nil,
135
135
  }.freeze
136
136
 
137
137
  # Operator return types
@@ -161,7 +161,7 @@ module TRuby
161
161
  "^" => "Integer",
162
162
  "~" => "Integer",
163
163
  "<<" => "Integer",
164
- ">>" => "Integer"
164
+ ">>" => "Integer",
165
165
  }.freeze
166
166
 
167
167
  def initialize
@@ -277,15 +277,15 @@ module TRuby
277
277
  next unless arg_type
278
278
 
279
279
  # Extract generic parameter from function param type
280
- if param[:type].match?(/^(\w+)<(\w+)>$/)
281
- match = param[:type].match(/^(\w+)<(\w+)>$/)
282
- generic_name = match[2]
283
-
284
- # If arg type is concrete, bind it
285
- if arg_type.type.match?(/^(\w+)<(\w+)>$/)
286
- arg_match = arg_type.type.match(/^(\w+)<(\w+)>$/)
287
- generic_bindings[generic_name] = arg_match[2]
288
- end
280
+ next unless param[:type].match?(/^(\w+)<(\w+)>$/)
281
+
282
+ match = param[:type].match(/^(\w+)<(\w+)>$/)
283
+ generic_name = match[2]
284
+
285
+ # If arg type is concrete, bind it
286
+ if arg_type.type.match?(/^(\w+)<(\w+)>$/)
287
+ arg_match = arg_type.type.match(/^(\w+)<(\w+)>$/)
288
+ generic_bindings[generic_name] = arg_match[2]
289
289
  end
290
290
  end
291
291
 
@@ -425,7 +425,7 @@ module TRuby
425
425
  end
426
426
 
427
427
  # Arithmetic operation
428
- if line.match?(/#{param_name}\s*[+\-*\/%]/)
428
+ if line.match?(%r{#{param_name}\s*[+\-*/%]})
429
429
  usages << { type: :arithmetic, line: idx + 1 }
430
430
  end
431
431
 
@@ -469,15 +469,13 @@ module TRuby
469
469
  .max_by { |_, v| v.length }
470
470
  &.first
471
471
 
472
- if most_common
473
- InferredType.new(
474
- type: most_common,
475
- confidence: InferredType::MEDIUM,
476
- source: :usage_analysis
477
- )
478
- else
479
- nil
480
- end
472
+ return unless most_common
473
+
474
+ InferredType.new(
475
+ type: most_common,
476
+ confidence: InferredType::MEDIUM,
477
+ source: :usage_analysis
478
+ )
481
479
  end
482
480
 
483
481
  def infer_type_from_method(method_name)
@@ -526,7 +524,7 @@ module TRuby
526
524
  end
527
525
  end
528
526
 
529
- def infer_operator_result(expr, operator, result_type)
527
+ def infer_operator_result(_expr, _operator, result_type)
530
528
  case result_type
531
529
  when "Boolean"
532
530
  InferredType.new(type: "Boolean", confidence: InferredType::HIGH, source: :operator)
@@ -541,8 +539,6 @@ module TRuby
541
539
  when :propagate
542
540
  # Type propagates from operands
543
541
  nil
544
- else
545
- nil
546
542
  end
547
543
  end
548
544
 
@@ -563,7 +559,7 @@ module TRuby
563
559
  )
564
560
  elsif element_types.length > 1
565
561
  InferredType.new(
566
- type: "Array<#{element_types.join(' | ')}>",
562
+ type: "Array<#{element_types.join(" | ")}>",
567
563
  confidence: InferredType::MEDIUM,
568
564
  source: :literal
569
565
  )
@@ -18,20 +18,20 @@ module TRuby
18
18
  private
19
19
 
20
20
  def parse_union
21
- members = @type_string.split("|").map { |m| m.strip }.compact
21
+ members = @type_string.split("|").map(&:strip).compact
22
22
 
23
23
  {
24
24
  type: :union,
25
25
  members: members,
26
26
  has_duplicates: members.length != members.uniq.length,
27
- unique_members: members.uniq
27
+ unique_members: members.uniq,
28
28
  }
29
29
  end
30
30
 
31
31
  def parse_simple
32
32
  {
33
33
  type: :simple,
34
- value: @type_string
34
+ value: @type_string,
35
35
  }
36
36
  end
37
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRuby
4
- VERSION = "0.0.34"
4
+ VERSION = "0.0.36"
5
5
  end
@@ -14,7 +14,7 @@ module TRuby
14
14
  yellow: "\e[33m",
15
15
  blue: "\e[34m",
16
16
  cyan: "\e[36m",
17
- gray: "\e[90m"
17
+ gray: "\e[90m",
18
18
  }.freeze
19
19
 
20
20
  attr_reader :incremental_compiler, :stats
@@ -47,7 +47,7 @@ module TRuby
47
47
  @stats = {
48
48
  total_compilations: 0,
49
49
  incremental_hits: 0,
50
- total_time: 0.0
50
+ total_time: 0.0,
51
51
  }
52
52
  end
53
53
 
@@ -92,8 +92,8 @@ module TRuby
92
92
 
93
93
  def handle_changes(modified, added, removed)
94
94
  changed_files = (modified + added)
95
- .select { |f| f.end_with?(".trb") || f.end_with?(".rb") }
96
- .reject { |f| @config.excluded?(f) }
95
+ .select { |f| f.end_with?(".trb") || f.end_with?(".rb") }
96
+ .reject { |f| @config.excluded?(f) }
97
97
  return if changed_files.empty? && removed.empty?
98
98
 
99
99
  puts
@@ -279,7 +279,7 @@ module TRuby
279
279
  file: file,
280
280
  line: line,
281
281
  col: col,
282
- message: message
282
+ message: message,
283
283
  }
284
284
  end
285
285
 
@@ -287,7 +287,10 @@ module TRuby
287
287
  errors.each do |error|
288
288
  puts
289
289
  # TypeScript-style error format: file:line:col - error TSXXXX: message
290
- location = "#{colorize(:cyan, relative_path(error[:file]))}:#{colorize(:yellow, error[:line])}:#{colorize(:yellow, error[:col])}"
290
+ location = "#{colorize(:cyan,
291
+ relative_path(error[:file]))}:#{colorize(:yellow,
292
+ error[:line])}:#{colorize(:yellow,
293
+ error[:col])}"
291
294
  puts "#{location} - #{colorize(:red, "error")} #{colorize(:gray, "TRB0001")}: #{error[:message]}"
292
295
  end
293
296
  end
@@ -298,7 +301,8 @@ module TRuby
298
301
  end
299
302
 
300
303
  def print_file_change_message
301
- puts "#{colorize(:gray, timestamp)} #{colorize(:bold, "File change detected. Starting incremental compilation...")}"
304
+ puts "#{colorize(:gray,
305
+ timestamp)} #{colorize(:bold, "File change detected. Starting incremental compilation...")}"
302
306
  puts
303
307
  end
304
308
 
@@ -306,12 +310,11 @@ module TRuby
306
310
  puts
307
311
  if @error_count.zero?
308
312
  msg = "Found #{colorize(:green, "0 errors")}. Watching for file changes."
309
- puts "#{colorize(:gray, timestamp)} #{msg}"
310
313
  else
311
314
  error_word = @error_count == 1 ? "error" : "errors"
312
315
  msg = "Found #{colorize(:red, "#{@error_count} #{error_word}")}. Watching for file changes."
313
- puts "#{colorize(:gray, timestamp)} #{msg}"
314
316
  end
317
+ puts "#{colorize(:gray, timestamp)} #{msg}"
315
318
  end
316
319
 
317
320
  def print_watching_message
@@ -323,11 +326,12 @@ module TRuby
323
326
  puts "#{colorize(:gray, timestamp)} #{colorize(:bold, "Watch Mode Statistics:")}"
324
327
  puts " Total compilations: #{@stats[:total_compilations]}"
325
328
  puts " Incremental cache hits: #{@stats[:incremental_hits]}"
326
- hit_rate = if @stats[:total_compilations] + @stats[:incremental_hits] > 0
327
- (@stats[:incremental_hits].to_f / (@stats[:total_compilations] + @stats[:incremental_hits]) * 100).round(1)
328
- else
329
- 0
330
- end
329
+ total = @stats[:total_compilations] + @stats[:incremental_hits]
330
+ hit_rate = if total.positive?
331
+ (@stats[:incremental_hits].to_f / total * 100).round(1)
332
+ else
333
+ 0
334
+ end
331
335
  puts " Cache hit rate: #{hit_rate}%"
332
336
  puts " Total compile time: #{@stats[:total_time].round(2)}s"
333
337
  end