rigortype 0.4.0 → 0.4.2

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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/data/builtins/ruby_core/io.yml +1 -0
  4. data/data/builtins/ruby_core/proc.yml +4 -0
  5. data/data/builtins/ruby_core/random.yml +4 -0
  6. data/data/builtins/ruby_core/struct.yml +3 -0
  7. data/data/core_overlay/env.rbs +34 -0
  8. data/data/core_overlay/gc.rbs +50 -0
  9. data/data/core_overlay/hash.rbs +45 -0
  10. data/data/core_overlay/integer.rbs +31 -0
  11. data/data/core_overlay/io_buffer.rbs +27 -0
  12. data/data/core_overlay/kernel.rbs +40 -0
  13. data/data/core_overlay/match_data.rbs +28 -0
  14. data/data/core_overlay/module.rbs +51 -0
  15. data/data/core_overlay/object_space.rbs +29 -0
  16. data/data/core_overlay/proc.rbs +45 -0
  17. data/data/core_overlay/range.rbs +40 -0
  18. data/data/core_overlay/source_range.rbs +78 -0
  19. data/data/core_overlay/string.rbs +86 -0
  20. data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
  21. data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
  22. data/docs/handbook/01-getting-started.md +3 -3
  23. data/docs/handbook/06-classes.md +3 -1
  24. data/docs/handbook/08-understanding-errors.md +3 -2
  25. data/docs/handbook/11-sig-gen.md +6 -0
  26. data/docs/handbook/appendix-liskov.md +5 -0
  27. data/docs/install.md +87 -15
  28. data/docs/manual/02-cli-reference.md +2 -2
  29. data/docs/manual/03-configuration.md +6 -1
  30. data/docs/manual/04-diagnostics.md +2 -1
  31. data/docs/manual/06-baseline.md +2 -0
  32. data/docs/manual/07-plugins.md +1 -1
  33. data/docs/manual/08-skills.md +4 -3
  34. data/docs/manual/12-caching.md +43 -4
  35. data/docs/manual/14-rails-quickstart.md +19 -11
  36. data/docs/manual/18-removing-dead-code.md +32 -2
  37. data/docs/manual/plugins/README.md +6 -1
  38. data/docs/manual/plugins/rigor-activerecord.md +21 -0
  39. data/docs/manual/plugins/rigor-alba.md +60 -0
  40. data/docs/manual/plugins/rigor-minitest.md +22 -4
  41. data/docs/manual/plugins/rigor-typelizer.md +71 -0
  42. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
  43. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
  44. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
  45. data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
  46. data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
  47. data/lib/rigor/analysis/check_rules.rb +407 -144
  48. data/lib/rigor/analysis/dependency_recorder.rb +215 -18
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
  52. data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
  53. data/lib/rigor/analysis/erb_template_detector.rb +2 -1
  54. data/lib/rigor/analysis/incremental.rb +29 -9
  55. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
  56. data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
  57. data/lib/rigor/analysis/incremental_session.rb +287 -50
  58. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  59. data/lib/rigor/analysis/reachability/graph.rb +123 -23
  60. data/lib/rigor/analysis/reachability/scan.rb +290 -36
  61. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
  62. data/lib/rigor/analysis/rule_catalog.rb +43 -0
  63. data/lib/rigor/analysis/run_cache_key.rb +43 -9
  64. data/lib/rigor/analysis/run_stats.rb +2 -2
  65. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
  68. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  69. data/lib/rigor/analysis/runner.rb +371 -49
  70. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  71. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  72. data/lib/rigor/analysis/template_unit_paths.rb +6 -4
  73. data/lib/rigor/analysis/template_units.rb +15 -5
  74. data/lib/rigor/analysis/worker_session.rb +11 -7
  75. data/lib/rigor/cache/descriptor.rb +40 -1
  76. data/lib/rigor/cache/file_digest.rb +28 -0
  77. data/lib/rigor/cache/incremental_snapshot.rb +92 -8
  78. data/lib/rigor/cache/store.rb +63 -3
  79. data/lib/rigor/cache/value_digest.rb +325 -0
  80. data/lib/rigor/cli/annotate_command.rb +5 -0
  81. data/lib/rigor/cli/baseline_command.rb +7 -18
  82. data/lib/rigor/cli/check_command.rb +85 -14
  83. data/lib/rigor/cli/doctor_command.rb +1 -1
  84. data/lib/rigor/cli/effects_report.rb +2 -2
  85. data/lib/rigor/cli/trace_command.rb +5 -0
  86. data/lib/rigor/cli/type_of_command.rb +5 -3
  87. data/lib/rigor/cli/unused_command.rb +16 -10
  88. data/lib/rigor/cli.rb +11 -3
  89. data/lib/rigor/config_audit.rb +3 -2
  90. data/lib/rigor/config_path.rb +29 -0
  91. data/lib/rigor/configuration/severity_profile.rb +11 -0
  92. data/lib/rigor/configuration.rb +52 -7
  93. data/lib/rigor/effects/plugin_facts.rb +6 -6
  94. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  95. data/lib/rigor/environment/gated_signature_guard.rb +43 -0
  96. data/lib/rigor/environment/gated_signature_plan.rb +339 -0
  97. data/lib/rigor/environment/lockfile_resolver.rb +10 -1
  98. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  99. data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
  100. data/lib/rigor/environment/rbs_loader.rb +284 -15
  101. data/lib/rigor/environment/required_features.rb +206 -0
  102. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  103. data/lib/rigor/environment.rb +34 -73
  104. data/lib/rigor/inference/acceptance.rb +28 -3
  105. data/lib/rigor/inference/block_parameter_binder.rb +29 -13
  106. data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
  107. data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
  108. data/lib/rigor/inference/captured_locals.rb +7 -1
  109. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  110. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  111. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  112. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  113. data/lib/rigor/inference/declaration_walk.rb +7 -0
  114. data/lib/rigor/inference/def_node_resolver.rb +26 -3
  115. data/lib/rigor/inference/definer_resolution.rb +266 -0
  116. data/lib/rigor/inference/expression_typer.rb +351 -61
  117. data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
  118. data/lib/rigor/inference/global_write_census.rb +7 -4
  119. data/lib/rigor/inference/guard_rebinding.rb +54 -13
  120. data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
  121. data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
  122. data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
  123. data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
  124. data/lib/rigor/inference/in_effect_refinements.rb +256 -0
  125. data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
  126. data/lib/rigor/inference/key_presence_guard.rb +303 -0
  127. data/lib/rigor/inference/macro_block_self_type.rb +73 -14
  128. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  129. data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
  130. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
  131. data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
  132. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
  133. data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
  134. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
  135. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
  136. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
  137. data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
  138. data/lib/rigor/inference/method_dispatcher.rb +44 -11
  139. data/lib/rigor/inference/module_function_state.rb +214 -0
  140. data/lib/rigor/inference/multi_target_binder.rb +21 -16
  141. data/lib/rigor/inference/narrowing.rb +166 -22
  142. data/lib/rigor/inference/object_mixins.rb +180 -0
  143. data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
  144. data/lib/rigor/inference/project_patched_methods.rb +10 -5
  145. data/lib/rigor/inference/project_patched_scanner.rb +71 -3
  146. data/lib/rigor/inference/refine_census.rb +239 -0
  147. data/lib/rigor/inference/refine_self.rb +317 -0
  148. data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
  149. data/lib/rigor/inference/refined_dispatch.rb +272 -0
  150. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  151. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  152. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  153. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  154. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  155. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
  156. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  157. data/lib/rigor/inference/scope_indexer.rb +2129 -354
  158. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  159. data/lib/rigor/inference/statement_evaluator.rb +149 -32
  160. data/lib/rigor/inference/string_mutation.rb +9 -3
  161. data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
  162. data/lib/rigor/inference/version_guard.rb +19 -10
  163. data/lib/rigor/language_server/project_context.rb +31 -0
  164. data/lib/rigor/language_server/server.rb +18 -0
  165. data/lib/rigor/plugin/base.rb +28 -8
  166. data/lib/rigor/plugin/inflector.rb +1 -1
  167. data/lib/rigor/plugin/io_boundary.rb +124 -7
  168. data/lib/rigor/plugin/isolation.rb +1 -1
  169. data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
  170. data/lib/rigor/plugin/trust_policy.rb +3 -3
  171. data/lib/rigor/project_environment.rb +29 -6
  172. data/lib/rigor/protection/discovery_seed.rb +18 -9
  173. data/lib/rigor/protection/mutation_cache.rb +4 -4
  174. data/lib/rigor/protection/mutator.rb +1 -1
  175. data/lib/rigor/reflection/constant_ancestors.rb +211 -46
  176. data/lib/rigor/reflection/constant_path.rb +33 -7
  177. data/lib/rigor/reflection.rb +41 -10
  178. data/lib/rigor/scope/discovery_index.rb +258 -3
  179. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  180. data/lib/rigor/scope/resolution_chain.rb +1032 -0
  181. data/lib/rigor/scope.rb +480 -328
  182. data/lib/rigor/sig_gen/alias_index.rb +1 -1
  183. data/lib/rigor/sig_gen/classification.rb +4 -1
  184. data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
  185. data/lib/rigor/sig_gen/generator.rb +77 -21
  186. data/lib/rigor/sig_gen/method_candidate.rb +2 -2
  187. data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
  188. data/lib/rigor/source/alias_names.rb +52 -0
  189. data/lib/rigor/source/literals.rb +3 -2
  190. data/lib/rigor/source/symbol_name.rb +20 -0
  191. data/lib/rigor/type/combinator.rb +59 -3
  192. data/lib/rigor/type/float_range.rb +4 -1
  193. data/lib/rigor/type/nominal.rb +6 -1
  194. data/lib/rigor/version.rb +1 -1
  195. data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
  196. data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
  197. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
  198. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
  199. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
  200. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
  201. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
  202. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
  203. data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
  204. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
  205. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
  206. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
  207. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
  208. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
  209. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
  210. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
  211. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
  212. data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
  213. data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
  214. data/plugins/rigor-alba/sig/alba.rbs +4 -0
  215. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
  216. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
  217. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
  218. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
  219. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
  220. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
  221. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
  222. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
  223. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
  224. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
  225. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
  226. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
  227. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
  228. data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
  229. data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
  230. data/sig/rigor/environment.rbs +2 -0
  231. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  232. data/sig/rigor/scope.rbs +69 -4
  233. data/sig/rigor/source.rbs +4 -0
  234. data/sig/rigor.rbs +3 -1
  235. data/skills/rigor-project-init/SKILL.md +37 -11
  236. data/skills/rigor-project-init/references/02-configure.md +7 -2
  237. metadata +90 -1
@@ -18,6 +18,7 @@ require_relative "method_dispatcher/data_folding"
18
18
  require_relative "method_dispatcher/struct_folding"
19
19
  require_relative "method_dispatcher/rbs_dispatch"
20
20
  require_relative "method_dispatcher/iterator_dispatch"
21
+ require_relative "method_dispatcher/arithmetic_sequence_elements"
21
22
  require_relative "method_dispatcher/reduce_folding"
22
23
  require_relative "method_dispatcher/block_folding"
23
24
  require_relative "method_dispatcher/array_to_h_folding"
@@ -101,7 +102,7 @@ module Rigor
101
102
  # the tier commits the buffer only if it goes on to answer. See {#try_composite_receiver}.
102
103
  def resolve(receiver_type:, method_name:, arg_types:, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
103
104
  block_type: nil, environment: nil,
104
- call_node: nil, scope: nil, plugin_typed_sink: nil)
105
+ call_node: nil, scope: nil, plugin_typed_sink: nil, skip_project_patched: false)
105
106
  return nil if receiver_type.nil?
106
107
 
107
108
  # Build the call context once and thread it — unchanged — through every tier
@@ -205,7 +206,10 @@ module Rigor
205
206
  # the pre-pass populated `ProjectPatchedMethods` with the `(class, method, kind)` triple;
206
207
  # this tier surfaces it as `Dynamic[top]` so the patched call resolves cross-file without
207
208
  # `call.undefined-method`.
208
- patched_result = try_project_patched_method(receiver_type, method_name, environment)
209
+ patched_result =
210
+ unless skip_project_patched
211
+ try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
212
+ end
209
213
  if patched_result
210
214
  scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
211
215
  return patched_result
@@ -614,7 +618,7 @@ module Rigor
614
618
  # `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
615
619
  # `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
616
620
  # `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
617
- def try_project_patched_method(receiver_type, method_name, environment)
621
+ def try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
618
622
  registry = environment&.project_patched_methods
619
623
  return nil if registry.nil? || registry.empty?
620
624
 
@@ -624,9 +628,25 @@ module Rigor
624
628
  kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
625
629
  entry = registry.lookup(class_name: class_name, method_name: method_name, kind: kind)
626
630
  return nil if entry.nil?
627
- return Type::Combinator.untyped if entry.return_type.nil?
631
+ return Type::Combinator.dynamic(entry.return_type) if entry.return_type
632
+
633
+ aliased = project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
634
+ aliased ? Type::Combinator.dynamic(aliased) : Type::Combinator.untyped
635
+ end
628
636
 
629
- Type::Combinator.dynamic(entry.return_type)
637
+ # Issue #1702 — an alias in a `pre_eval:` patch whose old name no earlier patch binding defines
638
+ # (`alias old_plus +` on Integer, or `alias_method :orig_succ, :succ` ahead of the patch's own `def succ`)
639
+ # names the method the class had before the patch, so it answers what that method answers for the same
640
+ # arguments, behind the `Dynamic` every patched method carries. The re-entry skips this tier: a later patch
641
+ # `def` of the old name is not the body the alias copied, and the skip is also what ends an alias cycle. It
642
+ # carries no call node or scope, so it answers a type and records nothing at the call site.
643
+ def project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
644
+ return nil if entry.alias_of.nil?
645
+
646
+ resolve(
647
+ receiver_type: receiver_type, method_name: entry.alias_of, arg_types: arg_types,
648
+ block_type: block_type, environment: environment, skip_project_patched: true
649
+ )
630
650
  end
631
651
 
632
652
  # ADR-10 slice 2b-ii. Consults the per-run `Analysis::DependencySourceInference::Index`
@@ -847,7 +867,8 @@ module Rigor
847
867
  private_constant :STDLIB_SINGLETON_FOLDERS
848
868
 
849
869
  PRECISE_TIERS_TAIL = Ractor.make_shareable([
850
- MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding
870
+ MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding,
871
+ ArithmeticSequenceElements
851
872
  ].freeze)
852
873
  private_constant :PRECISE_TIERS_TAIL
853
874
 
@@ -1104,14 +1125,23 @@ module Rigor
1104
1125
  # `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.
1105
1126
  def try_meta_introspection(receiver_type, method_name, arg_types = [], context = nil)
1106
1127
  case method_name
1107
- when :class then meta_class(receiver_type)
1128
+ when :class then meta_class(receiver_type, context&.environment)
1108
1129
  when :new then meta_new(receiver_type, arg_types, context)
1109
1130
  end
1110
1131
  end
1111
1132
 
1112
- def meta_class(receiver_type)
1133
+ def meta_class(receiver_type, environment = nil)
1113
1134
  case receiver_type
1114
- when Type::Nominal then Type::Combinator.singleton_of(receiver_type.class_name)
1135
+ when Type::Nominal
1136
+ # An instance of module `M` is an instance of some class that includes `M`, never of `M`
1137
+ # itself: `self.class` in `M`'s instance method is that includer, which no RBS type names.
1138
+ # `Dynamic[Class]` keeps the surface every class shares (`name`, `ancestors`) and answers
1139
+ # nothing for the includer's own singleton methods.
1140
+ if environment&.rbs_module?(receiver_type.class_name)
1141
+ return Type::Combinator.dynamic(Type::Combinator.nominal_of("Class"))
1142
+ end
1143
+
1144
+ Type::Combinator.singleton_of(receiver_type.class_name)
1115
1145
  when Type::Constant then constant_metaclass(receiver_type.value)
1116
1146
  end
1117
1147
  end
@@ -1555,13 +1585,16 @@ module Rigor
1555
1585
  # method definition, or selected overload does not provide statically declared block
1556
1586
  # parameter types. Callers MUST treat the empty array as "no information"; the binder
1557
1587
  # falls back to `Dynamic[Top]` for every parameter slot in that case.
1588
+ #
1589
+ # `call_node` lets the overload selector read the call's keyword arguments as keywords (#1727, #1737), as
1590
+ # the return path does; without it they are read positionally.
1558
1591
  def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil,
1559
- scope: nil)
1592
+ scope: nil, call_node: nil)
1560
1593
  return [] if receiver_type.nil?
1561
1594
 
1562
1595
  context = CallContext.build(
1563
1596
  receiver: receiver_type, method_name: method_name,
1564
- args: arg_types, environment: environment, scope: scope
1597
+ args: arg_types, environment: environment, scope: scope, call_node: call_node
1565
1598
  )
1566
1599
  iterator_result = IteratorDispatch.block_param_types(context)
1567
1600
  return iterator_result if iterator_result
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "../source/literals"
5
+ require_relative "../source/node_children"
6
+
7
+ module Rigor
8
+ module Inference
9
+ # The one home for what a receiverless `module_function` call does to the methods around it
10
+ # ([#1507](https://github.com/rigortype/rigor/issues/1507),
11
+ # [#1197](https://github.com/rigortype/rigor/issues/1197)). Four readers used to compute it in four
12
+ # places. They disagree with each other and, in places, with Ruby. Each entry point below reproduces
13
+ # one reader's answer exactly: this module gives the readings one place to live, and does not yet make
14
+ # them agree. A change to one reading lands here, beside the other three. Examples are #1550's
15
+ # named-form snapshot and the reset a bare `public`, `private` or `protected` makes.
16
+ #
17
+ # - The singleton def-node table (`ScopeIndexer#walk_singleton_body`) reads
18
+ # {.each_singleton_sibling} and {.each_singleton_copy}. A bare call covers the later direct
19
+ # statements of its own body and is never reset.
20
+ # - The deferred ranges (`ScopeIndexer#walk_deferred_body`) read {.prescan_deferred} and
21
+ # {.deferred_module_function?}. A bare call covers every later def by offset, and a call inside
22
+ # control flow or a block counts.
23
+ # - The extends table (`ScopeIndexer#record_extend_call`) reads {.extends_self?}. A bare call covers
24
+ # every instance def of the module, in any order.
25
+ # - sig-gen (`SigGen::Generator#walk_statements`) reads {.each_sig_gen_statement} and
26
+ # {.sig_gen_module_function?}. A bare call covers the later statements of its own statement list and
27
+ # what nests in them.
28
+ #
29
+ # A fifth reader does not come here yet: `rigor-activerecord`'s `ModelDiscoverer#table_name_decorators`
30
+ # (`plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb`) reads a module body's
31
+ # direct statements for its table-name readers. It also accepts `self.module_function`, and there a bare
32
+ # call covers the instance methods written before it as well as after it, while a named call takes only a
33
+ # def written before it. A change to what `module_function` means must decide whether that reader follows.
34
+ #
35
+ # The deferred-ranges prescan asks `ScopeIndexer.receiver_eval_call?` and `ScopeIndexer.def_singleton?`, so
36
+ # `rigor/inference/scope_indexer` must be loaded before it runs. This file does not require it:
37
+ # `scope_indexer.rb` requires this file, and a require back would be circular. The other entry points need
38
+ # only Prism.
39
+ #
40
+ # `spec/rigor/inference/module_function_state_spec.rb` pins each reader's answers, and marks the ones
41
+ # Ruby contradicts with a "flip this when" note.
42
+ module ModuleFunctionState
43
+ module_function
44
+
45
+ # A receiverless `module_function` call, bare or named.
46
+ def call?(node)
47
+ node.name == :module_function && node.receiver.nil?
48
+ end
49
+
50
+ # The toggle form: a `module_function` call with no arguments.
51
+ def bare?(node)
52
+ node.arguments.nil? || node.arguments.arguments.empty?
53
+ end
54
+
55
+ # The singleton def-node table's reading, over one class-ish body's direct statements in source
56
+ # order. `ScopeIndexer#statements_of` builds that list, folding a body-level `rescue`, `else` or
57
+ # `ensure` clause into it. Yields `stmt, module_function_on, named_call` for every statement except a
58
+ # bare call:
59
+ #
60
+ # - `module_function_on` says whether a bare call precedes the statement. The toggle never switches
61
+ # off, although a later bare `public`, `private` or `protected` resets it in Ruby.
62
+ # - `named_call` is true for a named call (`module_function :a` or `module_function def x`), which
63
+ # {.each_singleton_copy} resolves; the caller records nothing else for it.
64
+ #
65
+ # A call nested in control flow or a block is not seen.
66
+ def each_singleton_sibling(statements)
67
+ module_function_on = false
68
+ statements.each do |stmt|
69
+ if stmt.is_a?(Prism::CallNode) && call?(stmt)
70
+ if bare?(stmt)
71
+ module_function_on = true
72
+ else
73
+ yield stmt, module_function_on, true
74
+ end
75
+ next
76
+ end
77
+ yield stmt, module_function_on, false
78
+ end
79
+ end
80
+
81
+ # The defs a named call copies onto the module's singleton, for the singleton def-node table. Yields
82
+ # `name, def_node` for each Symbol or String argument that names a receiverless def among the body's
83
+ # direct `statements`. The last def of that name wins wherever it sits. A def written after the call
84
+ # is therefore taken, although Ruby copies the one in effect at the call (#1550), and a name defined
85
+ # only after the call resolves, although Ruby raises `NameError` there. A `module_function def x`
86
+ # argument names nothing here.
87
+ def each_singleton_copy(call, statements)
88
+ defs_by_name = statements.each_with_object({}) do |stmt, acc|
89
+ acc[stmt.name] = stmt if stmt.is_a?(Prism::DefNode) && stmt.receiver.nil?
90
+ end
91
+ call.arguments&.arguments&.each do |arg|
92
+ name = symbol_argument_name(arg)
93
+ def_node = name && defs_by_name[name]
94
+ yield name, def_node if def_node
95
+ end
96
+ end
97
+
98
+ # The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets` the start of
99
+ # every bare call; each def that starts after one is a module function
100
+ # ({.deferred_module_function?}). Appends to `ranges` one row per named call, in the table's
101
+ # `[start, end, name, kind, owner]` shape:
102
+ #
103
+ # - a `:name` argument gives a `:singleton` row over the call itself, because the copy happens at
104
+ # the call and not at the def;
105
+ # - a `module_function def x` argument gives a `:both` row over the def, or a `:singleton` row for
106
+ # `def self.x`.
107
+ #
108
+ # The prescan looks through control flow and blocks, because a call that ran there still flips the
109
+ # later defs. It stops at a nested class, module, `class <<` or def body, where the call targets
110
+ # another module. It skips an `END` body, which runs after every def, and a `*_eval` / `*_exec`
111
+ # block, whose self is the receiver's module; that block gets its own prescan as a body. Nothing
112
+ # resets it.
113
+ #
114
+ # `qualified_prefix` names the owner, and `in_singleton_class` says whether the body is a
115
+ # `class <<` body; both feed `ScopeIndexer.def_singleton?`.
116
+ def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges)
117
+ return unless node.is_a?(Prism::Node)
118
+ return if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
119
+ node.is_a?(Prism::SingletonClassNode) || node.is_a?(Prism::DefNode) ||
120
+ node.is_a?(Prism::PostExecutionNode)
121
+
122
+ if node.is_a?(Prism::CallNode)
123
+ return prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges) if call?(node)
124
+ if ScopeIndexer.receiver_eval_call?(node)
125
+ return prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
126
+ end
127
+ end
128
+
129
+ node.rigor_each_child do |child|
130
+ prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges)
131
+ end
132
+ end
133
+
134
+ # Whether the deferred-ranges reading makes a def starting at `start` a module function: a bare call
135
+ # starts before it, at any depth the prescan entered.
136
+ def deferred_module_function?(offsets, start)
137
+ offsets.any? { |offset| offset < start }
138
+ end
139
+
140
+ # The extends table's reading. A bare call makes the module extend itself, so every instance def the
141
+ # module has also answers on its singleton. That holds for defs before and after the call, and
142
+ # whatever a later `public` or `private` says. The over-approximation is deliberate (#526): the
143
+ # extra names only silence `call.undefined-method` on calls that raise. The walk reaches a call in
144
+ # control flow, and one in a `class_eval`-family block under that block's receiver. It does not reach
145
+ # one in an ordinary block, an `END` body or a `class << self` body.
146
+ def extends_self?(call)
147
+ call?(call) && bare?(call)
148
+ end
149
+
150
+ # sig-gen's reading, over one statement list. Yields every statement except a bare call, with
151
+ # whether a bare call came before it in this list or `active` was already true on entry. The
152
+ # generator passes the enclosing list's answer into an `if`, a block or a `begin`, but not into a
153
+ # nested class, module or `class << self`. A bare call in a nested list covers only that list's
154
+ # later statements. Nothing resets it, and a named call is not a directive at all.
155
+ def each_sig_gen_statement(statements, active)
156
+ statements.each do |stmt|
157
+ if sig_gen_directive?(stmt)
158
+ active = true
159
+ next
160
+ end
161
+ yield stmt, active
162
+ end
163
+ end
164
+
165
+ # A bare receiverless call, as sig-gen's statement-level directive.
166
+ def sig_gen_directive?(node)
167
+ node.is_a?(Prism::CallNode) && call?(node) && bare?(node)
168
+ end
169
+
170
+ # Whether sig-gen renders a def as `def self?.name`: an instance-side def under an active directive,
171
+ # in a class or a module alike. `kind` is `:singleton` for `def self.x` and a `class << self` body.
172
+ def sig_gen_module_function?(active, kind)
173
+ active && kind == :instance
174
+ end
175
+
176
+ # The prescan's call arm.
177
+ def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
178
+ if bare?(node)
179
+ offsets << node.location.start_offset
180
+ else
181
+ owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
182
+ node.arguments&.arguments&.each do |arg|
183
+ if arg.is_a?(Prism::DefNode)
184
+ kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
185
+ ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
186
+ elsif (name = symbol_argument_name(arg))
187
+ ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
188
+ else
189
+ prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
190
+ end
191
+ end
192
+ end
193
+ return unless node.block
194
+
195
+ prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges)
196
+ end
197
+
198
+ # The prescan's eval arm: only the call's receiver and arguments keep this module's context.
199
+ def prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
200
+ prescan_deferred(node.receiver, qualified_prefix, in_singleton_class, offsets, ranges) if node.receiver
201
+ node.arguments&.arguments&.each do |arg|
202
+ prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
203
+ end
204
+ end
205
+
206
+ # The Symbol a `:name` or `"name"` literal argument names, or nil.
207
+ def symbol_argument_name(arg)
208
+ Source::Literals.symbol_or_string(arg)
209
+ end
210
+
211
+ private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name
212
+ end
213
+ end
214
+ end
@@ -108,31 +108,36 @@ module Rigor
108
108
  # `index_targets` maps each `Prism::IndexTargetNode` to the value it stores; {#apply_to}
109
109
  # binds nothing for it.
110
110
  Result = Data.define(:types, :optimistic, :ivars, :optimistic_ivars, :index_targets) do
111
- def initialize(types:, optimistic:, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES,
112
- index_targets: NO_BINDINGS)
111
+ def initialize(types:, optimistic:, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES, index_targets: NO_BINDINGS)
113
112
  super
114
113
  end
115
114
 
116
- # Binds every name into `scope` and records the optimistic mark after the binding, since
117
- # `Scope#with_local` / `Scope#with_ivar` drop any mark the name carried before. `miss` is what
118
- # each marked slot answers on a miss (issue #1302): `nil` for every mark the binder makes itself —
119
- # a short array pads with `nil`, and a `nil` destructures to `nil` — so only a caller whose
120
- # right-hand side a miss can make something else passes another answer
121
- # ({OptimisticOrigin.destructuring_miss}).
115
+ # Binds every name into `scope` with its mark ({MultiTargetBinder.apply_bindings}).
122
116
  def apply_to(scope, miss: nil)
123
- bound = types.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
124
- bound = ivars.reduce(bound) { |acc, (name, type)| acc.with_ivar(name, type) }
125
- bound = optimistic.reduce(bound) do |acc, name|
126
- acc.with_optimistic_local(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
127
- end
128
- optimistic_ivars.reduce(bound) do |acc, name|
129
- acc.with_optimistic_ivar(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
130
- end
117
+ MultiTargetBinder.apply_bindings(scope, types, optimistic, ivars:, optimistic_ivars:, miss:)
131
118
  end
132
119
  end
133
120
 
134
121
  module_function
135
122
 
123
+ # The one place bindings and their optimistic marks are applied, shared by {Result#apply_to} and
124
+ # `BlockParameterBinder#bind_onto`. Every name is bound into the scope `bound` and the mark recorded
125
+ # after the binding, since `Scope#with_local` / `Scope#with_ivar` drop any mark the name carried
126
+ # before; the answer is the bound scope. `miss` is what each marked slot answers on a miss (issue
127
+ # #1302): `nil` for every mark the binder makes itself — a short array pads with `nil`, and a `nil`
128
+ # destructures to `nil` — so only a caller whose right-hand side a miss can make something else passes
129
+ # another answer ({OptimisticOrigin.destructuring_miss}). It loops with `each`, since `reduce`
130
+ # allocates even over the empty collections most of the four are
131
+ # ([#1504](https://github.com/rigortype/rigor/issues/1504)).
132
+ def apply_bindings(bound, types, optimistic, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES, miss: nil)
133
+ types.each { |name, type| bound = bound.with_local(name, type) }
134
+ ivars.each { |name, type| bound = bound.with_ivar(name, type).without_ivar_guard(name) }
135
+ origin = OptimisticOrigin::IMPLICITLY_RETURNS_NIL
136
+ optimistic.each { |name| bound = bound.with_optimistic_local(name, origin, miss: miss) }
137
+ optimistic_ivars.each { |name| bound = bound.with_optimistic_ivar(name, origin, miss: miss) }
138
+ bound
139
+ end
140
+
136
141
  # @param rhs_type — type of the right-hand side
137
142
  # @param scope — the scope the destructure is evaluated in, which answers the `to_ary`
138
143
  # question for a class outside the core list; without one only the core list wraps.