rigortype 0.0.9 → 0.1.1

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -2
  3. data/data/builtins/ruby_core/array.yml +6 -6
  4. data/data/builtins/ruby_core/hash.yml +1 -1
  5. data/data/builtins/ruby_core/io.yml +3 -3
  6. data/data/builtins/ruby_core/numeric.yml +1 -1
  7. data/data/builtins/ruby_core/pathname.yml +100 -100
  8. data/data/builtins/ruby_core/proc.yml +1 -1
  9. data/data/builtins/ruby_core/time.yml +3 -3
  10. data/lib/rigor/analysis/check_rules.rb +228 -40
  11. data/lib/rigor/analysis/diagnostic.rb +15 -1
  12. data/lib/rigor/analysis/runner.rb +269 -7
  13. data/lib/rigor/builtins/regex_refinement.rb +104 -0
  14. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  15. data/lib/rigor/cache/rbs_class_type_param_names.rb +1 -1
  16. data/lib/rigor/cache/rbs_constant_table.rb +2 -2
  17. data/lib/rigor/cache/rbs_descriptor.rb +2 -0
  18. data/lib/rigor/cache/rbs_instance_definitions.rb +79 -0
  19. data/lib/rigor/cache/store.rb +2 -0
  20. data/lib/rigor/cli/type_of_command.rb +3 -3
  21. data/lib/rigor/cli/type_scan_command.rb +4 -4
  22. data/lib/rigor/cli.rb +20 -7
  23. data/lib/rigor/configuration/severity_profile.rb +109 -0
  24. data/lib/rigor/configuration.rb +286 -15
  25. data/lib/rigor/environment/rbs_loader.rb +89 -13
  26. data/lib/rigor/environment.rb +12 -4
  27. data/lib/rigor/flow_contribution/conflict.rb +81 -0
  28. data/lib/rigor/flow_contribution/element.rb +53 -0
  29. data/lib/rigor/flow_contribution/fact.rb +88 -0
  30. data/lib/rigor/flow_contribution/merge_result.rb +67 -0
  31. data/lib/rigor/flow_contribution/merger.rb +275 -0
  32. data/lib/rigor/flow_contribution.rb +51 -0
  33. data/lib/rigor/inference/block_parameter_binder.rb +15 -0
  34. data/lib/rigor/inference/expression_typer.rb +87 -6
  35. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +31 -0
  36. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +136 -9
  37. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +21 -1
  38. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +68 -2
  39. data/lib/rigor/inference/method_dispatcher.rb +50 -1
  40. data/lib/rigor/inference/multi_target_binder.rb +2 -0
  41. data/lib/rigor/inference/narrowing.rb +246 -127
  42. data/lib/rigor/inference/scope_indexer.rb +124 -16
  43. data/lib/rigor/inference/statement_evaluator.rb +406 -37
  44. data/lib/rigor/plugin/access_denied_error.rb +24 -0
  45. data/lib/rigor/plugin/base.rb +284 -0
  46. data/lib/rigor/plugin/fact_store.rb +92 -0
  47. data/lib/rigor/plugin/io_boundary.rb +102 -0
  48. data/lib/rigor/plugin/load_error.rb +35 -0
  49. data/lib/rigor/plugin/loader.rb +307 -0
  50. data/lib/rigor/plugin/manifest.rb +203 -0
  51. data/lib/rigor/plugin/registry.rb +50 -0
  52. data/lib/rigor/plugin/services.rb +77 -0
  53. data/lib/rigor/plugin/trust_policy.rb +99 -0
  54. data/lib/rigor/plugin.rb +62 -0
  55. data/lib/rigor/rbs_extended.rb +57 -9
  56. data/lib/rigor/reflection.rb +2 -2
  57. data/lib/rigor/trinary.rb +1 -1
  58. data/lib/rigor/type/integer_range.rb +6 -2
  59. data/lib/rigor/version.rb +1 -1
  60. data/lib/rigor.rb +7 -0
  61. data/sig/rigor/environment.rbs +10 -3
  62. data/sig/rigor/inference.rbs +1 -0
  63. data/sig/rigor/rbs_extended.rbs +2 -0
  64. data/sig/rigor/scope.rbs +1 -0
  65. data/sig/rigor/type.rbs +7 -0
  66. data/sig/rigor.rbs +8 -2
  67. metadata +20 -1
@@ -340,7 +340,7 @@ module Rigor
340
340
  accumulator.transform_values(&:freeze).freeze
341
341
  end
342
342
 
343
- def walk_methods(node, qualified_prefix, in_singleton_class, accumulator) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
343
+ def walk_methods(node, qualified_prefix, in_singleton_class, accumulator) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
344
344
  return unless node.is_a?(Prism::Node)
345
345
 
346
346
  case node
@@ -359,6 +359,9 @@ module Rigor
359
359
  when Prism::DefNode
360
360
  record_def_method(node, qualified_prefix, in_singleton_class, accumulator)
361
361
  return
362
+ when Prism::AliasMethodNode
363
+ record_alias_method(node, qualified_prefix, in_singleton_class, accumulator)
364
+ return
362
365
  when Prism::CallNode
363
366
  record_define_method(node, qualified_prefix, in_singleton_class, accumulator) if node.name == :define_method
364
367
  end
@@ -390,6 +393,7 @@ module Rigor
390
393
  def build_discovered_def_nodes(root)
391
394
  accumulator = {}
392
395
  walk_def_nodes(root, [], false, accumulator)
396
+ apply_alias_def_nodes(root, accumulator)
393
397
  accumulator.transform_values(&:freeze).freeze
394
398
  end
395
399
 
@@ -436,6 +440,76 @@ module Rigor
436
440
  accumulator[class_name][def_node.name] = def_node
437
441
  end
438
442
 
443
+ # Registers the alias name in the `discovered_methods` table so
444
+ # `undefined-method` diagnostics are not emitted for calls to the
445
+ # aliased name. The kind mirrors the surrounding class context
446
+ # (instance inside a regular class body, singleton inside
447
+ # `class << self`).
448
+ def record_alias_method(alias_node, qualified_prefix, in_singleton_class, accumulator)
449
+ return if qualified_prefix.empty?
450
+ return unless alias_node.new_name.is_a?(Prism::SymbolNode)
451
+
452
+ class_name = qualified_prefix.join("::")
453
+ new_name = alias_node.new_name.unescaped.to_sym
454
+ kind = in_singleton_class ? :singleton : :instance
455
+ (accumulator[class_name] ||= {})[new_name] = kind
456
+ end
457
+
458
+ # Post-pass over the `def_nodes` accumulator: for every `alias`
459
+ # declaration inside a class body, if the original method name
460
+ # maps to a `Prism::DefNode`, register the new name pointing to
461
+ # the same node so inter-procedural return-type inference works
462
+ # for the aliased name.
463
+ def apply_alias_def_nodes(root, accumulator)
464
+ alias_map = collect_class_alias_map(root, [], {})
465
+ alias_map.each do |class_name, aliases|
466
+ class_defs = accumulator[class_name]
467
+ next unless class_defs
468
+
469
+ aliases.each do |new_name, old_name|
470
+ def_node = class_defs[old_name]
471
+ next unless def_node.is_a?(Prism::DefNode)
472
+
473
+ (accumulator[class_name] ||= {})[new_name] = def_node
474
+ end
475
+ end
476
+ end
477
+
478
+ # Builds a map `{class_name => {new_name_sym => old_name_sym}}` by
479
+ # walking the tree for `AliasMethodNode` nodes inside class bodies.
480
+ # rubocop:disable Metrics/CyclomaticComplexity
481
+ def collect_class_alias_map(node, qualified_prefix, accumulator)
482
+ return accumulator unless node.is_a?(Prism::Node)
483
+
484
+ case node
485
+ when Prism::ClassNode, Prism::ModuleNode
486
+ name = qualified_name_for(node.constant_path)
487
+ if name
488
+ collect_class_alias_map(node.body, qualified_prefix + [name], accumulator) if node.body
489
+ return accumulator
490
+ end
491
+ when Prism::SingletonClassNode
492
+ return accumulator
493
+ when Prism::AliasMethodNode
494
+ record_alias_map_entry(node, qualified_prefix, accumulator)
495
+ return accumulator
496
+ end
497
+
498
+ node.compact_child_nodes.each { |child| collect_class_alias_map(child, qualified_prefix, accumulator) }
499
+ accumulator
500
+ end
501
+ # rubocop:enable Metrics/CyclomaticComplexity
502
+
503
+ def record_alias_map_entry(alias_node, qualified_prefix, accumulator)
504
+ return if qualified_prefix.empty?
505
+ return unless alias_node.new_name.is_a?(Prism::SymbolNode) && alias_node.old_name.is_a?(Prism::SymbolNode)
506
+
507
+ class_name = qualified_prefix.join("::")
508
+ new_name = alias_node.new_name.unescaped.to_sym
509
+ old_name = alias_node.old_name.unescaped.to_sym
510
+ (accumulator[class_name] ||= {})[new_name] = old_name
511
+ end
512
+
439
513
  def record_define_method(call_node, qualified_prefix, in_singleton_class, accumulator)
440
514
  return if qualified_prefix.empty?
441
515
  return if call_node.arguments.nil? || call_node.arguments.arguments.empty?
@@ -478,7 +552,7 @@ module Rigor
478
552
  when Prism::ModuleNode, Prism::ClassNode
479
553
  return if record_class_or_module?(node, qualified_prefix, identity_table, discovered)
480
554
  when Prism::ConstantWriteNode
481
- return if record_data_define_constant?(node, qualified_prefix, identity_table, discovered)
555
+ return if record_meta_new_constant?(node, qualified_prefix, identity_table, discovered)
482
556
  end
483
557
 
484
558
  node.compact_child_nodes.each do |child|
@@ -499,17 +573,23 @@ module Rigor
499
573
  true
500
574
  end
501
575
 
502
- # Recognises `Const = Data.define(*Symbol) [do ... end]` and registers
503
- # `Const` (qualified by the surrounding class/module path) as a
504
- # discovered class. `Const.new(...)` then resolves to a fresh
505
- # `Nominal[Const]` via `meta_new`, instead of the un-narrowed
506
- # `Dynamic[top]` returned by the default `Class#new` envelope.
576
+ # Recognises class-creating meta calls at constant-write rvalue
577
+ # position and registers `Const` (qualified by the surrounding
578
+ # class/module path) as a discovered class. `Const.new(...)`
579
+ # then resolves to a fresh `Nominal[Const]` via `meta_new`,
580
+ # instead of the un-narrowed `Dynamic[top]` returned by the
581
+ # default `Class#new` envelope.
507
582
  #
508
- # The Data.define block body, if present, is recursed into so any
509
- # nested class/module declarations in the override block (rare but
510
- # legal) still feed the discovered table.
511
- def record_data_define_constant?(node, qualified_prefix, identity_table, discovered)
512
- return false unless data_define_call?(node.value)
583
+ # Two recognised meta forms:
584
+ #
585
+ # - `Const = Data.define(*Symbol) [do ... end]`
586
+ # - `Const = Struct.new(*Symbol [, keyword_init: ...]) [do ... end]`
587
+ #
588
+ # The block body, if present, is recursed into so any nested
589
+ # class/module declarations in the override block (rare but legal)
590
+ # still feed the discovered table.
591
+ def record_meta_new_constant?(node, qualified_prefix, identity_table, discovered)
592
+ return false unless data_define_call?(node.value) || struct_new_call?(node.value)
513
593
 
514
594
  full = (qualified_prefix + [node.name.to_s]).join("::")
515
595
  discovered[full] = Type::Combinator.singleton_of(full)
@@ -525,18 +605,46 @@ module Rigor
525
605
  def data_define_call?(node)
526
606
  return false unless node.is_a?(Prism::CallNode)
527
607
  return false unless node.name == :define
528
- return false unless data_constant_receiver?(node.receiver)
608
+ return false unless meta_constant_receiver?(node.receiver, :Data)
529
609
 
530
610
  args = node.arguments&.arguments || []
531
611
  args.all?(Prism::SymbolNode)
532
612
  end
533
613
 
534
- def data_constant_receiver?(node)
614
+ # Recognises `Struct.new(*Symbol)` and
615
+ # `Struct.new(*Symbol, keyword_init: <expr>)` at constant-write
616
+ # rvalue position. A trailing `KeywordHashNode` (the
617
+ # `keyword_init: ...` form) is accepted but does not contribute
618
+ # to member discovery; every other argument MUST be a
619
+ # `Prism::SymbolNode`. At least one Symbol member is required —
620
+ # `Struct.new()` is a degenerate form callers don't typically use.
621
+ def struct_new_call?(node)
622
+ return false unless meta_call_with_name?(node, :Struct, :new)
623
+
624
+ args = node.arguments&.arguments || []
625
+ positional = struct_new_positionals(args)
626
+ return false if positional.nil? || positional.empty?
627
+
628
+ positional.all?(Prism::SymbolNode)
629
+ end
630
+
631
+ def meta_call_with_name?(node, receiver_name, method_name)
632
+ return false unless node.is_a?(Prism::CallNode)
633
+ return false unless node.name == method_name
634
+
635
+ meta_constant_receiver?(node.receiver, receiver_name)
636
+ end
637
+
638
+ def struct_new_positionals(args)
639
+ args.last.is_a?(Prism::KeywordHashNode) ? args[0..-2] : args
640
+ end
641
+
642
+ def meta_constant_receiver?(node, expected_name)
535
643
  case node
536
644
  when Prism::ConstantReadNode
537
- node.name == :Data
645
+ node.name == expected_name
538
646
  when Prism::ConstantPathNode
539
- node.parent.nil? && node.name == :Data
647
+ node.parent.nil? && node.name == expected_name
540
648
  end
541
649
  end
542
650