t-ruby 0.0.35 → 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.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/lib/t_ruby/benchmark.rb +16 -16
- data/lib/t_ruby/bundler_integration.rb +33 -35
- data/lib/t_ruby/cache.rb +46 -43
- data/lib/t_ruby/cli.rb +3 -3
- data/lib/t_ruby/compiler.rb +25 -27
- data/lib/t_ruby/config.rb +15 -11
- data/lib/t_ruby/constraint_checker.rb +14 -8
- data/lib/t_ruby/declaration_generator.rb +8 -8
- data/lib/t_ruby/doc_generator.rb +33 -33
- data/lib/t_ruby/docs_badge_generator.rb +8 -8
- data/lib/t_ruby/docs_example_verifier.rb +5 -6
- data/lib/t_ruby/error_handler.rb +21 -22
- data/lib/t_ruby/generic_type_parser.rb +3 -3
- data/lib/t_ruby/intersection_type_parser.rb +2 -2
- data/lib/t_ruby/ir.rb +21 -24
- data/lib/t_ruby/lsp_server.rb +128 -138
- data/lib/t_ruby/package_manager.rb +16 -18
- data/lib/t_ruby/parser.rb +7 -7
- data/lib/t_ruby/parser_combinator.rb +22 -22
- data/lib/t_ruby/rbs_generator.rb +2 -4
- data/lib/t_ruby/runtime_validator.rb +41 -37
- data/lib/t_ruby/smt_solver.rb +31 -29
- data/lib/t_ruby/type_alias_registry.rb +1 -0
- data/lib/t_ruby/type_checker.rb +64 -63
- data/lib/t_ruby/type_erasure.rb +2 -4
- data/lib/t_ruby/type_inferencer.rb +22 -26
- data/lib/t_ruby/union_type_parser.rb +3 -3
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +18 -14
- metadata +4 -3
data/lib/t_ruby/smt_solver.rb
CHANGED
|
@@ -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" => [
|
|
638
|
-
"Float" => [
|
|
638
|
+
"Integer" => %w[Numeric Object],
|
|
639
|
+
"Float" => %w[Numeric Object],
|
|
639
640
|
"Numeric" => ["Object"],
|
|
640
641
|
"String" => ["Object"],
|
|
641
|
-
"Array" => [
|
|
642
|
-
"Hash" => [
|
|
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.
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
data/lib/t_ruby/type_checker.rb
CHANGED
|
@@ -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" => [
|
|
46
|
-
"Float" => [
|
|
45
|
+
"Integer" => %w[Numeric Object],
|
|
46
|
+
"Float" => %w[Numeric Object],
|
|
47
47
|
"Numeric" => ["Object"],
|
|
48
48
|
"String" => ["Object"],
|
|
49
49
|
"Symbol" => ["Object"],
|
|
50
|
-
"Array" => [
|
|
51
|
-
"Hash" => [
|
|
52
|
-
"Set" => [
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
494
|
-
match = condition.match(/!(\w+)\.nil\?/)
|
|
495
|
-
var = match[1]
|
|
492
|
+
return unless condition.match?(/!(\w+)\.nil\?/)
|
|
496
493
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
[
|
|
689
|
-
[
|
|
690
|
-
[
|
|
691
|
-
[
|
|
692
|
-
[
|
|
693
|
-
[
|
|
694
|
-
[
|
|
695
|
-
[
|
|
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
|
-
[
|
|
704
|
-
[
|
|
704
|
+
%w[String length] => "Integer",
|
|
705
|
+
%w[String size] => "Integer",
|
|
705
706
|
["String", "empty?"] => "Boolean",
|
|
706
|
-
[
|
|
707
|
-
[
|
|
708
|
-
[
|
|
707
|
+
%w[String chars] => "Array<String>",
|
|
708
|
+
%w[Array length] => "Integer",
|
|
709
|
+
%w[Array size] => "Integer",
|
|
709
710
|
["Array", "empty?"] => "Boolean",
|
|
710
|
-
[
|
|
711
|
-
[
|
|
712
|
-
[
|
|
713
|
-
[
|
|
714
|
-
[
|
|
715
|
-
[
|
|
716
|
-
[
|
|
717
|
-
[
|
|
718
|
-
[
|
|
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]]
|
data/lib/t_ruby/type_erasure.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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?(
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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(
|
|
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
|
|
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
|
data/lib/t_ruby/version.rb
CHANGED
data/lib/t_ruby/watcher.rb
CHANGED
|
@@ -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
|
-
|
|
96
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|