rigortype 0.0.1 → 0.0.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/data/builtins/ruby_core/array.yml +1470 -0
  3. data/data/builtins/ruby_core/file.yml +501 -0
  4. data/data/builtins/ruby_core/io.yml +1594 -0
  5. data/data/builtins/ruby_core/numeric.yml +1809 -0
  6. data/data/builtins/ruby_core/string.yml +1850 -0
  7. data/lib/rigor/analysis/check_rules.rb +297 -5
  8. data/lib/rigor/analysis/diagnostic.rb +13 -2
  9. data/lib/rigor/analysis/runner.rb +52 -5
  10. data/lib/rigor/builtins/imported_refinements.rb +69 -0
  11. data/lib/rigor/cli/type_of_command.rb +11 -5
  12. data/lib/rigor/cli/type_scan_command.rb +13 -8
  13. data/lib/rigor/cli.rb +26 -6
  14. data/lib/rigor/configuration.rb +18 -2
  15. data/lib/rigor/environment.rb +3 -1
  16. data/lib/rigor/inference/acceptance.rb +180 -0
  17. data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
  18. data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
  19. data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
  20. data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
  21. data/lib/rigor/inference/expression_typer.rb +151 -0
  22. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +650 -16
  23. data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
  24. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +113 -0
  25. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
  26. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +107 -0
  27. data/lib/rigor/inference/method_dispatcher.rb +28 -21
  28. data/lib/rigor/inference/narrowing.rb +471 -10
  29. data/lib/rigor/inference/scope_indexer.rb +66 -0
  30. data/lib/rigor/inference/statement_evaluator.rb +305 -2
  31. data/lib/rigor/rbs_extended.rb +174 -14
  32. data/lib/rigor/scope.rb +44 -5
  33. data/lib/rigor/type/combinator.rb +69 -1
  34. data/lib/rigor/type/difference.rb +155 -0
  35. data/lib/rigor/type/integer_range.rb +137 -0
  36. data/lib/rigor/type.rb +2 -0
  37. data/lib/rigor/version.rb +1 -1
  38. data/sig/rigor/inference.rbs +5 -2
  39. data/sig/rigor/rbs_extended.rbs +25 -1
  40. data/sig/rigor/scope.rbs +4 -0
  41. data/sig/rigor/type.rbs +51 -1
  42. metadata +15 -1
@@ -744,6 +744,33 @@ module Rigor
744
744
  arg_types = call_arg_types(node)
745
745
  block_type = block_return_type_for(node, receiver, arg_types)
746
746
 
747
+ # v0.0.3 A — implicit-self calls prefer a same-named
748
+ # top-level `def` over RBS dispatch. Without this,
749
+ # a helper like `def select(...)` defined inside an
750
+ # `RSpec.describe ... do ... end` block mis-routes
751
+ # through `Enumerable#select` / `Object#select` and
752
+ # the caller observes `Array[Elem]` instead of the
753
+ # helper's actual return type. The check fires only
754
+ # for `node.receiver.nil?` (true implicit self), so
755
+ # explicit-receiver dispatch is unaffected.
756
+ local_def = node.receiver.nil? ? scope.top_level_def_for(node.name) : nil
757
+ if local_def
758
+ local_inference = infer_top_level_user_method(local_def, receiver, arg_types)
759
+ return local_inference if local_inference
760
+
761
+ # The local def matches by name but the
762
+ # parameter shape is too complex for the first-
763
+ # iteration binder (kwargs / optionals / rest).
764
+ # Returning `Dynamic[Top]` is the safest answer:
765
+ # we know RBS dispatch would be wrong (the
766
+ # method is user-defined and shadows whatever
767
+ # ancestor method the dispatch would find), and
768
+ # `Dynamic[Top]` propagates correctly through
769
+ # downstream call chains without surfacing
770
+ # misleading false-positive diagnostics.
771
+ return dynamic_top
772
+ end
773
+
747
774
  result = MethodDispatcher.dispatch(
748
775
  receiver_type: receiver,
749
776
  method_name: node.name,
@@ -753,6 +780,14 @@ module Rigor
753
780
  )
754
781
  return result if result
755
782
 
783
+ # v0.0.2 #5 — inter-procedural inference for
784
+ # user-defined methods. When dispatch misses but the
785
+ # receiver is a user class with a `def` body, re-type
786
+ # the body with the call's argument types bound and
787
+ # return the body's last-expression type.
788
+ user_inference = try_user_method_inference(receiver, node, arg_types)
789
+ return user_inference if user_inference
790
+
756
791
  # Dynamic-origin propagation: when the receiver is Dynamic[T] and
757
792
  # no positive rule resolves the call, the result inherits the
758
793
  # dynamic origin. Per the value-lattice algebra, this is a
@@ -763,6 +798,122 @@ module Rigor
763
798
  fallback_for(node, family: :prism)
764
799
  end
765
800
 
801
+ # v0.0.2 #5 — re-types the body of a user-defined
802
+ # instance method with the call site's argument types
803
+ # bound to the method's parameters. Used as a
804
+ # last-resort tier after `MethodDispatcher.dispatch`
805
+ # has exhausted its catalogue (RBS, shape, constant
806
+ # folding, user-class fallback). Returns nil when:
807
+ #
808
+ # - the receiver is not `Nominal[T]` for some T;
809
+ # - no def_node is recorded for that class/method
810
+ # (the receiver is foreign or has only an RBS sig);
811
+ # - the def has no body, or has a parameter shape we
812
+ # cannot bind from the call's positional args;
813
+ # - the inference is already in progress for this
814
+ # (class, method, signature) tuple — recursion
815
+ # safety net.
816
+ # v0.0.3 A — re-types a top-level (or DSL-block-nested)
817
+ # `def` discovered by `ScopeIndexer` under the
818
+ # `TOP_LEVEL_DEF_KEY` sentinel. Mirrors the
819
+ # `infer_user_method_return` shape but uses the
820
+ # current `scope.self_type` (or implicit `Object`)
821
+ # as the receiver carrier so the body's own self is
822
+ # consistent with the call site's. Returns nil when
823
+ # the parameter shape disqualifies the def, when the
824
+ # body is empty, or when a recursion cycle is
825
+ # detected.
826
+ def infer_top_level_user_method(def_node, receiver, arg_types)
827
+ infer_user_method_return(def_node, receiver, arg_types)
828
+ rescue StandardError
829
+ nil
830
+ end
831
+
832
+ def try_user_method_inference(receiver, call_node, arg_types)
833
+ return nil unless receiver.is_a?(Type::Nominal)
834
+
835
+ def_node = scope.user_def_for(receiver.class_name, call_node.name)
836
+ return nil if def_node.nil?
837
+
838
+ infer_user_method_return(def_node, receiver, arg_types)
839
+ rescue StandardError
840
+ nil
841
+ end
842
+
843
+ INFERENCE_GUARD_KEY = :__rigor_user_method_inference_stack__
844
+ private_constant :INFERENCE_GUARD_KEY
845
+
846
+ def infer_user_method_return(def_node, receiver, arg_types)
847
+ return nil if def_node.body.nil?
848
+
849
+ body_scope = build_user_method_body_scope(def_node, receiver, arg_types)
850
+ return nil if body_scope.nil?
851
+
852
+ # Recursion-guard signature. Uses `describe(:short)`
853
+ # so non-Nominal receivers (e.g. the implicit
854
+ # `Object` carrier used for top-level / DSL-block
855
+ # defs in v0.0.3 A) can participate without raising.
856
+ signature = [receiver.describe(:short), def_node.name, arg_types.map { |t| t.describe(:short) }]
857
+ stack = (Thread.current[INFERENCE_GUARD_KEY] ||= [])
858
+ return Type::Combinator.untyped if stack.include?(signature)
859
+
860
+ stack.push(signature)
861
+ begin
862
+ type, _post = body_scope.evaluate(def_node.body)
863
+ type
864
+ ensure
865
+ stack.pop
866
+ end
867
+ end
868
+
869
+ # Builds the body scope for a user-defined instance
870
+ # method call: a fresh `Scope` with `self_type` set to
871
+ # the receiver's nominal type, the project-wide
872
+ # accumulators inherited (so the body sees the same
873
+ # `discovered_classes` / `class_ivars` / etc. the
874
+ # caller does), and required positional parameters
875
+ # bound from the call's `arg_types` by index. Returns
876
+ # nil when the parameter shape is too complex for the
877
+ # first-iteration binder (rest args, keyword args,
878
+ # block params, etc.).
879
+ def build_user_method_body_scope(def_node, receiver, arg_types) # rubocop:disable Metrics/AbcSize
880
+ params = def_node.parameters
881
+ required = params&.requireds || []
882
+ return nil unless params.nil? || user_method_param_shape_simple?(params)
883
+ return nil unless required.size == arg_types.size
884
+
885
+ fresh = Scope.empty(environment: scope.environment)
886
+ .with_declared_types(scope.declared_types)
887
+ .with_discovered_classes(scope.discovered_classes)
888
+ .with_in_source_constants(scope.in_source_constants)
889
+ .with_class_ivars(scope.class_ivars)
890
+ .with_class_cvars(scope.class_cvars)
891
+ .with_program_globals(scope.program_globals)
892
+ .with_discovered_methods(scope.discovered_methods)
893
+ .with_discovered_def_nodes(scope.discovered_def_nodes)
894
+ .with_self_type(receiver)
895
+
896
+ required.each_with_index do |param, index|
897
+ fresh = fresh.with_local(param.name, arg_types[index])
898
+ end
899
+ fresh
900
+ end
901
+
902
+ # First iteration accepts only required positional
903
+ # parameters: `def foo(a, b, c)`. Optionals, rest,
904
+ # keyword params, and block params disqualify the
905
+ # method from inference (the caller observes
906
+ # `Dynamic[Top]` instead).
907
+ def user_method_param_shape_simple?(params)
908
+ return false unless params.is_a?(Prism::ParametersNode)
909
+
910
+ params.optionals.empty? &&
911
+ params.rest.nil? &&
912
+ params.keywords.empty? &&
913
+ params.keyword_rest.nil? &&
914
+ params.block.nil?
915
+ end
916
+
766
917
  # Slice A-engine. Implicit-self calls (no `node.receiver`)
767
918
  # adopt the surrounding scope's `self_type` as their receiver
768
919
  # so calls like `attr_reader_method_name` or