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
@@ -142,7 +142,7 @@ module Rigor
142
142
  buffer_name = decl.location&.buffer&.name
143
143
  return nil if buffer_name.nil?
144
144
 
145
- File.expand_path(buffer_name.to_s)
145
+ File.absolute_path(buffer_name.to_s)
146
146
  rescue StandardError
147
147
  nil
148
148
  end
@@ -57,7 +57,10 @@ module Rigor
57
57
  inline_generic_class: "sig.skipped.inline-generic-class",
58
58
  # ADR-112 WD4 — the member is declared inline and in `sig/`, and the two disagree. A refusal, not a
59
59
  # skip: `--write` and `--check` exit 1 until a person reconciles them or passes `--overwrite`.
60
- inline_differs: "sig.skipped.inline-differs"
60
+ inline_differs: "sig.skipped.inline-differs",
61
+ # #1676 — a plain top-level `def` defines a private `Object` method and has no class to write a
62
+ # declaration under, so it is reported rather than silently dropped. Whether to emit it stays open.
63
+ top_level_def: "sig.skipped.top-level-def"
61
64
  }.freeze
62
65
  end
63
66
  end
@@ -182,8 +182,8 @@ module Rigor
182
182
  def relative_path(path)
183
183
  return nil if path.nil?
184
184
 
185
- expanded = File.expand_path(path.to_s)
186
- root = "#{File.expand_path(Dir.pwd)}#{File::SEPARATOR}"
185
+ expanded = File.absolute_path(path.to_s)
186
+ root = "#{File.absolute_path(Dir.pwd)}#{File::SEPARATOR}"
187
187
  expanded.start_with?(root) ? expanded.delete_prefix(root) : nil
188
188
  end
189
189
 
@@ -13,6 +13,7 @@ require_relative "../source/literals"
13
13
  require_relative "../source/node_children"
14
14
  require_relative "../inference/def_return_typer"
15
15
  require_relative "../inference/scope_indexer"
16
+ require_relative "../inference/module_function_state"
16
17
  require_relative "../inference/rbs_type_translator"
17
18
  require_relative "alias_index"
18
19
  require_relative "meta_class_shape"
@@ -99,6 +100,9 @@ module Rigor
99
100
  @class_shells = Set.new
100
101
  @class_superclasses = {}
101
102
  @meta_layouts = {}
103
+ # Issue #1763 — the walked file's tree, and its refine-call query, built only when a `refine` call is walked.
104
+ @walk_root = nil
105
+ @in_effect_refinements = nil
102
106
  # Whole-run, NOT per-file: a rendering defect is reported once at the end of the run.
103
107
  @unrenderable = []
104
108
  # Issue #735 — whole-run too: `{ class name => the superclass token that resolves nowhere }`.
@@ -464,7 +468,7 @@ module Rigor
464
468
  @class_superclasses = {}
465
469
  @meta_layouts = collect_meta_layouts(scope_index)
466
470
  register_meta_classes
467
- defs = collect_method_definitions(parse_result.value)
471
+ defs = collect_method_definitions(parse_result.value, scope_index)
468
472
  # Candidate construction freezes the per-file maps above (see {#build_candidate}), so every registration
469
473
  # pass has to be finished before the first `build_candidate` call. Meta members lead the RETURNED order —
470
474
  # a value class's members and constructors read first, ahead of the methods its block body defines.
@@ -474,7 +478,7 @@ module Rigor
474
478
  # `rigor sig-gen` run. The `check` path recovers each *file* this way (worker_session.rb); sig-gen
475
479
  # recovers per-def so the rest of the file's candidates still emit.
476
480
 
477
- classify_def(path, def_node, class_name, kind, scope_index)
481
+ classify_collected_def(path, def_node, class_name, kind, scope_index)
478
482
  rescue StandardError
479
483
  nil
480
484
  end
@@ -485,8 +489,9 @@ module Rigor
485
489
 
486
490
  # Walks the AST collecting `(def_node, class_name, kind)` tuples for every `def` Rigor can re-type. Slice 1
487
491
  # covered instance `def foo` methods inside a nameable `class` / `module` body. Slice 4 extends this to
488
- # singleton-side methods via `def self.foo` and `class << self; def foo; end`; top-level / DSL-block defs
489
- # still degrade silently (no nameable receiver).
492
+ # singleton-side methods via `def self.foo` and `class << self; def foo; end`. A plain top-level `def` is
493
+ # collected with a nil class and reported as skipped (#1676); DSL-block defs still degrade silently (no
494
+ # nameable receiver).
490
495
  #
491
496
  # ADR-14 gap-#3 follow-up tracks two extra pieces during the same walk so the Writer can emit kind-correct
492
497
  # RBS without guessing:
@@ -497,8 +502,12 @@ module Rigor
497
502
  # - `@module_function_methods` records `(class_name, method_name)` pairs where a `module_function` (no
498
503
  # args) call preceded the `def` inside a module body. The renderer emits `def self?.name` for these, the
499
504
  # RBS spelling that matches the dual instance + singleton dispatch the runtime produces.
500
- def collect_method_definitions(root)
505
+ def collect_method_definitions(root, scope_index)
506
+ @declared_types = scope_index[root].declared_types
507
+ @walk_root = root
508
+ @in_effect_refinements = nil
501
509
  out = []
510
+ @block_depth = 0
502
511
  walk_defs(root, [], false, false, out)
503
512
  out
504
513
  end
@@ -528,9 +537,22 @@ module Rigor
528
537
  return
529
538
  end
530
539
 
540
+ walk_def_children(node, prefix, in_singleton_class, module_function_active, out)
541
+ end
542
+
543
+ # #1676 — a block body is a DSL scope, not the file's top level, so a `def` inside one is not reported
544
+ # as a top-level def. Neither is a `def` in `class << obj` (a non-`self` singleton body reaches here,
545
+ # the `self` one returns above): it defines a singleton method on `obj`, not a private `Object` method.
546
+ def walk_def_children(node, prefix, in_singleton_class, module_function_active, out)
547
+ return if refine_block_call?(node)
548
+
549
+ in_block = node.is_a?(Prism::BlockNode) || node.is_a?(Prism::LambdaNode) ||
550
+ node.is_a?(Prism::SingletonClassNode)
551
+ @block_depth += 1 if in_block
531
552
  node.rigor_each_child do |child|
532
553
  walk_defs(child, prefix, in_singleton_class, module_function_active, out)
533
554
  end
555
+ @block_depth -= 1 if in_block
534
556
  end
535
557
 
536
558
  # [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
@@ -542,6 +564,11 @@ module Rigor
542
564
  # `prefix` stays the nesting the header is WRITTEN in for {#record_superclass}: `::` re-anchors the
543
565
  # declaration, not the superclass expression beside it, which Ruby still evaluates in the enclosing cref.
544
566
  def descend_into_namespace?(node, prefix, out)
567
+ # Issue #1518 — a `class self::X` / `module self::X` the indexer declined (`self` is opaque, as under
568
+ # `REGISTRY.first.class_eval`) names no class, so its body must not be written out under the lexical
569
+ # `X`. The indexer's identity table is the single source of that decision.
570
+ return true if declined_self_header?(node)
571
+
545
572
  child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
546
573
  return false unless child_prefix
547
574
 
@@ -552,6 +579,26 @@ module Rigor
552
579
  true
553
580
  end
554
581
 
582
+ # Issue #1763 — a `def` (or an `attr_*`) in a `refine X do … end` block defines a method of the refinement
583
+ # module, not of the namespace the block is written in, and RBS has no declaration for a refinement, so both
584
+ # walks skip the whole block: writing `def shout: () -> String` under the enclosing module declared a method
585
+ # that module does not have. Statement and value position alike, since the walks descend every child. The
586
+ # skip is silent, as for a top-level `def`: a `sig.skipped.*` row would name the enclosing class, the very
587
+ # misattribution this avoids. A `refine` call the engine reads as a class's own method (`Class` undefines
588
+ # `refine`, #1689) walks as any other block, as the engine treats it.
589
+ def refine_block_call?(node)
590
+ return false unless node.is_a?(Prism::CallNode) && Inference::ScopeIndexer.refine_call?(node)
591
+
592
+ @in_effect_refinements ||= Inference::InEffectRefinements.new(@walk_root)
593
+ !@in_effect_refinements.class_body_refine?(node)
594
+ end
595
+
596
+ def declined_self_header?(node)
597
+ path = node.constant_path
598
+ path.is_a?(Prism::ConstantPathNode) && path.parent.is_a?(Prism::SelfNode) &&
599
+ !@declared_types.key?(path)
600
+ end
601
+
555
602
  # ADR-14: a generated subclass declaration MUST carry its superclass, or the sidecar `sig/` misrepresents
556
603
  # the class (inherited members vanish → receiver dispatch degrades to `Dynamic`) and, worse, a nested
557
604
  # reference to an inherited type re-declares the class as a bare namespace on the RBS side and can
@@ -648,7 +695,8 @@ module Rigor
648
695
  return nil unless node.value.is_a?(Prism::CallNode)
649
696
  return nil unless @meta_layouts.key?((prefix + [node.name.to_s]).join("::"))
650
697
 
651
- node.value.block&.body
698
+ block = node.value.block
699
+ block.body if block.is_a?(Prism::BlockNode)
652
700
  end
653
701
 
654
702
  # Module / class bodies are walked through the `walk_statements` path so `module_function` (no-args)
@@ -662,29 +710,28 @@ module Rigor
662
710
  walk_defs(namespace_node.body, prefix, false, false, out)
663
711
  end
664
712
 
713
+ # The directive's reach is {Inference::ModuleFunctionState.each_sig_gen_statement}.
665
714
  def walk_statements(stmts_node, prefix, in_singleton_class, module_function_active, out)
666
- stmts_node.body.each do |stmt|
667
- if module_function_directive?(stmt)
668
- module_function_active = true
669
- next
670
- end
671
- walk_defs(stmt, prefix, in_singleton_class, module_function_active, out)
715
+ Inference::ModuleFunctionState.each_sig_gen_statement(stmts_node.body,
716
+ module_function_active) do |stmt, active|
717
+ walk_defs(stmt, prefix, in_singleton_class, active, out)
672
718
  end
673
719
  end
674
720
 
675
- def module_function_directive?(node)
676
- return false unless node.is_a?(Prism::CallNode)
677
- return false unless node.name == :module_function && node.receiver.nil?
678
-
679
- (node.arguments&.arguments || []).empty?
680
- end
681
-
682
721
  def collect_def_node(node, prefix, in_singleton_class, module_function_active, out)
683
- return if prefix.empty?
722
+ if prefix.empty?
723
+ # #1676 — a plain top-level `def` defines a private `Object` method. It is surfaced as a skipped
724
+ # candidate (no class to write it under) rather than dropped; `def self.x` and block-body defs stay
725
+ # unreported.
726
+ out << [node, nil, :instance] if node.receiver.nil? && !in_singleton_class && @block_depth.zero?
727
+ return
728
+ end
684
729
 
685
730
  kind = node.receiver.is_a?(Prism::SelfNode) || in_singleton_class ? :singleton : :instance
686
731
  class_name = prefix.join("::")
687
- @module_function_methods << [class_name, node.name] if module_function_active && kind == :instance
732
+ if Inference::ModuleFunctionState.sig_gen_module_function?(module_function_active, kind)
733
+ @module_function_methods << [class_name, node.name]
734
+ end
688
735
  out << [node, class_name, kind]
689
736
  end
690
737
 
@@ -909,6 +956,13 @@ module Rigor
909
956
  end
910
957
  end
911
958
 
959
+ # #1676 — a top-level `def` is collected with no class; it is reported, never typed.
960
+ def classify_collected_def(path, def_node, class_name, kind, scope_index)
961
+ return skipped(path, def_node, class_name, kind, :top_level_def) if class_name.nil?
962
+
963
+ classify_def(path, def_node, class_name, kind, scope_index)
964
+ end
965
+
912
966
  def classify_def(path, def_node, class_name, kind, scope_index)
913
967
  return nil if visibility_excludes?(def_node, class_name, kind, scope_index)
914
968
  return nil if initialize_excludes?(def_node, kind)
@@ -1706,6 +1760,8 @@ module Rigor
1706
1760
  return
1707
1761
  end
1708
1762
  when Prism::CallNode
1763
+ return if refine_block_call?(node)
1764
+
1709
1765
  collect_attr_call(node, prefix, in_singleton_class, ctx)
1710
1766
  end
1711
1767
 
@@ -11,8 +11,8 @@ module Rigor
11
11
  # applicable to that classification.
12
12
  #
13
13
  # - `path` — the source `.rb` file the def came from.
14
- # - `class_name` — qualified receiver class name (e.g. `"Foo::Bar"`). `nil` for top-level / DSL-block defs
15
- # the MVP skips.
14
+ # - `class_name` — qualified receiver class name (e.g. `"Foo::Bar"`). `nil` for a top-level `def`, which is only
15
+ # ever `skipped` (`sig.skipped.top-level-def`, #1676).
16
16
  # - `method_name` — the def's `Symbol` name.
17
17
  # - `kind` — `:instance` or `:singleton`.
18
18
  # - `inferred_return` — `Rigor::Type` instance (or `nil` when the inference pass disqualified the def).
@@ -135,6 +135,18 @@ module Rigor
135
135
  "body, not the override the subclass actually runs.",
136
136
  next_step: "Generate signatures for the overriding subclass too — widen the `sig-gen` path set to " \
137
137
  "cover it — and the parent's declaration becomes safe to emit."
138
+ ),
139
+ "sig.skipped.top-level-def" => Entry.new(
140
+ id: "sig.skipped.top-level-def",
141
+ summary: "A top-level `def` defines a private `Object` method, and sig-gen writes no signature for it.",
142
+ explanation: "Not a gap in inference. A `def` outside any `class` or `module` has no class to " \
143
+ "declare the member under, and Ruby makes it a private method on `Object`. sig-gen " \
144
+ "reports it instead of dropping it silently, so a file that defines only top-level " \
145
+ "methods is not mistaken for one that was never read. Whether to emit it as a private " \
146
+ "`Object` method is undecided (#1676); `def self.x` and `def` inside a block body are " \
147
+ "not reported.",
148
+ next_step: "Nothing is required. To type the method, move it into a module (`module_function` or " \
149
+ "`extend self` keeps the call sites), or write the `Object` declaration by hand in `sig/`."
138
150
  )
139
151
  }.freeze
140
152
 
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "symbol_name"
5
+
6
+ module Rigor
7
+ module Source
8
+ # The `[new_name, old_name]` pair a method-alias statement names, read off its syntax alone: the `alias` keyword
9
+ # (`alias to_m to_modint`, `alias :to_m :to_modint`) and an implicit-self `alias_method :to_m, :to_modint` call
10
+ # whose two arguments are Symbol or String literals. A computed name (`alias_method name, :x`, an interpolated
11
+ # symbol) is runtime data and reads as nil, so no walker files an alias for it.
12
+ #
13
+ # The one reader the per-file discovery walk ({Inference::ScopeIndexer}) and the `pre_eval:` pre-pass
14
+ # ({Inference::ProjectPatchedScanner}) share, so a patch file's aliases mean the same thing in both.
15
+ module AliasNames
16
+ module_function
17
+
18
+ # The pair for an `alias` keyword whose two names are plain symbols (a bare identifier parses as one), or nil.
19
+ def keyword_names(alias_node)
20
+ new_name = symbol_name(alias_node.new_name)
21
+ old_name = symbol_name(alias_node.old_name)
22
+ return nil if new_name.nil? || old_name.nil?
23
+
24
+ [new_name, old_name]
25
+ end
26
+
27
+ # The pair for an implicit-self `alias_method` call with two literal Symbol / String arguments, or nil.
28
+ def alias_method_call_names(call_node)
29
+ return nil unless call_node.name == :alias_method && call_node.receiver.nil?
30
+
31
+ args = call_node.arguments&.arguments
32
+ return nil unless args && args.size == 2
33
+
34
+ new_name = literal_name(args[0])
35
+ old_name = literal_name(args[1])
36
+ return nil if new_name.nil? || old_name.nil?
37
+
38
+ [new_name, old_name]
39
+ end
40
+
41
+ def symbol_name(node)
42
+ node.is_a?(Prism::SymbolNode) ? SymbolName.of(node.unescaped) : nil
43
+ end
44
+
45
+ def literal_name(node)
46
+ return nil unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
47
+
48
+ SymbolName.of(node.unescaped)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "prism"
4
+ require_relative "symbol_name"
4
5
 
5
6
  module Rigor
6
7
  module Source
@@ -34,7 +35,7 @@ module Rigor
34
35
  def symbol_or_string(node)
35
36
  return nil unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
36
37
 
37
- node.unescaped.to_sym
38
+ SymbolName.of(node.unescaped)
38
39
  end
39
40
 
40
41
  # The String a literal `Prism::SymbolNode` / `Prism::StringNode` names, or `nil` for any other node
@@ -53,7 +54,7 @@ module Rigor
53
54
  def symbol(node)
54
55
  return nil unless node.is_a?(Prism::SymbolNode)
55
56
 
56
- node.unescaped.to_sym
57
+ SymbolName.of(node.unescaped)
57
58
  end
58
59
 
59
60
  # The String a literal `Prism::SymbolNode` names, or `nil` for any other node (including a
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Source
5
+ # The Symbol a name's text denotes, or `nil` when no Symbol can hold its bytes.
6
+ #
7
+ # `"\xff".to_sym` raises EncodingError, and a String literal may carry any bytes (a Symbol literal with invalid
8
+ # bytes is a syntax error, so no analysed file has one). Every reader of a literal name goes through here, so such
9
+ # a name reads as `nil` — the answer a computed name already gets — instead of aborting the file's analysis (#1821).
10
+ # `valid_encoding?` is the test, not `ascii_only?`: a name that is valid in its own encoding (a binary or Shift_JIS
11
+ # source) is a real Symbol.
12
+ module SymbolName
13
+ module_function
14
+
15
+ def of(text)
16
+ text.to_sym if text&.valid_encoding?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -832,7 +832,8 @@ module Rigor
832
832
  # instead of several readings of it. The normative relation is
833
833
  # docs/type-specification/normalization.md § "Member absorption"; {absorbed_by?} is that list.
834
834
  #
835
- # Only a `FloatRange`, `Tuple` or `HashShape` member can be dropped, so the O(n²) scan is
835
+ # Only a `FloatRange`, `Tuple`, `HashShape` or element-carrying `Enumerator::ArithmeticSequence` member
836
+ # can be dropped (the last also joins with its kind, {join_sequence_elements}), so the O(n²) scan is
836
837
  # skipped for every union that carries none of them — this runs on the hot path of every join.
837
838
  #
838
839
  # The `!absorbed_by?(other, member)` re-check costs one predicate call per pair that absorbed
@@ -841,6 +842,7 @@ module Rigor
841
842
  def absorb_contained_members(members)
842
843
  return members unless members.any? { |m| absorbable_member?(m) }
843
844
 
845
+ members = join_sequence_elements(members)
844
846
  members.reject do |member|
845
847
  absorbable_member?(member) &&
846
848
  members.any? do |other|
@@ -850,7 +852,36 @@ module Rigor
850
852
  end
851
853
 
852
854
  def absorbable_member?(type)
853
- type.is_a?(FloatRange) || type.is_a?(Tuple) || type.is_a?(HashShape)
855
+ type.is_a?(FloatRange) || type.is_a?(Tuple) || type.is_a?(HashShape) || element_sequence?(type)
856
+ end
857
+
858
+ ARITHMETIC_SEQUENCE = "Enumerator::ArithmeticSequence"
859
+ private_constant :ARITHMETIC_SEQUENCE
860
+
861
+ # Issue #1794 — the `Enumerator::ArithmeticSequence[E]` block-less `Integer#step` returns, whose element is
862
+ # a Rigor-private type argument (`Nominal::RIGOR_PRIVATE_TYPE_ARGS`).
863
+ def element_sequence?(type)
864
+ type.is_a?(Nominal) && type.class_name == ARITHMETIC_SEQUENCE && type.type_args.size == 1
865
+ end
866
+
867
+ def plain_sequence?(type)
868
+ type.is_a?(Nominal) && type.class_name == ARITHMETIC_SEQUENCE && type.type_args.empty?
869
+ end
870
+
871
+ # Two element-carrying sequences join into one over the union of their elements, so a join of
872
+ # `1.step(10, 2)` and `1.step(10, 3)` stays one sequence whose block reads `Integer`, the answer each arm
873
+ # gives. (A plain sequence absorbing one with an element is the {absorbed_by?} clause.)
874
+ def join_sequence_elements(members)
875
+ sequences = members.select { |member| element_sequence?(member) }
876
+ return members if sequences.size < 2
877
+
878
+ joined = nominal_of(ARITHMETIC_SEQUENCE, type_args: [union(*sequences.map { |seq| seq.type_args.first })])
879
+ first = sequences.first
880
+ members.filter_map do |member|
881
+ if member.equal?(first) then joined
882
+ elsif !sequences.include?(member) then member
883
+ end
884
+ end
854
885
  end
855
886
 
856
887
  # `narrower`'s inhabitants are a subset of `wider`'s under a rule the union already applies to
@@ -864,6 +895,7 @@ module Rigor
864
895
  return true if listed_in_union?(narrower, wider)
865
896
 
866
897
  case narrower
898
+ when Nominal then element_sequence?(narrower) && plain_sequence?(wider)
867
899
  when FloatRange then float_range_absorbed_by?(narrower, wider)
868
900
  when Tuple then wider.is_a?(Tuple) && tuple_absorbed_by?(narrower, wider)
869
901
  when HashShape then wider.is_a?(HashShape) && hash_shape_absorbed_by?(narrower, wider)
@@ -991,13 +1023,37 @@ module Rigor
991
1023
  end
992
1024
  end
993
1025
 
1026
+ # The order a new union or intersection keeps: by each member's `describe(:short)`, which is
1027
+ # observable in messages and in cache fingerprints, so it must not move. Two members, the common
1028
+ # case, compare directly instead of paying `sort_by`'s work arrays; `sort_by` keeps an equal pair in
1029
+ # place as well ([#1505](https://github.com/rigortype/rigor/issues/1505)).
994
1030
  def sort_members(members)
995
- members.sort_by { |m| m.describe(:short) }
1031
+ if members.size == 2
1032
+ first, second = members
1033
+ return sort_key(second) < sort_key(first) ? [second, first] : members
1034
+ end
1035
+
1036
+ members.sort_by { |member| sort_key(member) }
1037
+ end
1038
+
1039
+ # A member's `describe(:short)`, rendered at most once per type instance between garbage collections.
1040
+ # No type carrier has a method that changes it once built (a built type is frozen; one `Marshal.load`
1041
+ # restores is not, but nothing mutates it either), so a memoised key cannot go stale. The memo is weak
1042
+ # in both key and value: it keeps neither the type nor the string alive, and an entry whose string
1043
+ # nothing else holds is dropped at the next GC. It is keyed by identity on purpose: `Constant[0.0]` and
1044
+ # `Constant[-0.0]` are equal and hash alike, but render differently. A non-main Ractor may not read the
1045
+ # module's ivar, so a pool worker renders the key every time, as every run did before.
1046
+ def sort_key(member)
1047
+ return member.describe(:short) unless Ractor.main?
1048
+
1049
+ @sort_keys[member] ||= member.describe(:short)
996
1050
  end
997
1051
  end
998
1052
 
999
1053
  # Eager-allocated at load time; see `untyped` method comment above.
1000
1054
  @untyped = Dynamic.new(Top.instance)
1055
+ # The main Ractor's {sort_key} memo, allocated here for the same reason.
1056
+ @sort_keys = ObjectSpace::WeakMap.new
1001
1057
 
1002
1058
  NIL_CONSTANT = Constant.new(nil)
1003
1059
  TRUE_CONSTANT = Constant.new(true)
@@ -11,7 +11,7 @@ module Rigor
11
11
  # bounds, never sentinels, because Ruby orders them. The range may exclude its end (`0.0...1.0`)
12
12
  # and never its beginning, because Ruby's literal has no exclusive begin.
13
13
  #
14
- # No bounded range contains NaN: `(a..b).cover?(Float::NAN)` is false for every `a` and `b`, so
14
+ # No bounded range contains NaN (`#covers?` answers false for it on every Ruby), so
15
15
  # the range from `-Float::INFINITY` to `Float::INFINITY` is "every Float except NaN"
16
16
  # (`non-nan-float`), not `Float`. The whole of `Float` (`nil..nil` in Ruby, which does cover NaN)
17
17
  # is never a carrier; the payload builder normalises it to `Nominal[Float]`.
@@ -58,6 +58,9 @@ module Rigor
58
58
  # displays as does.
59
59
  def covers?(value)
60
60
  return false unless value.is_a?(Float)
61
+ # NaN is in no bounded range, so answer false here: `Range#cover?(Float::NAN)` raises
62
+ # ArgumentError on ruby-head (4.1), where Ruby 4.0 returned false.
63
+ return false if value.nan?
61
64
 
62
65
  Range.new(min, max, @exclude_end).cover?(value)
63
66
  end
@@ -49,9 +49,14 @@ module Rigor
49
49
  "#{rendered_name}[#{rendered}]"
50
50
  end
51
51
 
52
+ # Classes RBS declares without a type parameter, on which Rigor carries one of its own: the element type of
53
+ # the `Enumerator::ArithmeticSequence` that `Integer#step` returns (issue #1794). RBS has no slot for it, so
54
+ # the argument erases away.
55
+ RIGOR_PRIVATE_TYPE_ARGS = %w[Enumerator::ArithmeticSequence].freeze
56
+
52
57
  def erase_to_rbs
53
58
  return "untyped" if AnonymousClassName.match?(class_name)
54
- return class_name if type_args.empty?
59
+ return class_name if type_args.empty? || RIGOR_PRIVATE_TYPE_ARGS.include?(class_name)
55
60
 
56
61
  rendered = type_args.map(&:erase_to_rbs).join(", ")
57
62
  "#{class_name}[#{rendered}]"
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.2"
5
5
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rigor/plugin"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ # A pure RBS-bundle plugin for ac-library-rb, the Ruby port of the AtCoder Library. It emits no diagnostic and
8
+ # declares no producer: the manifest's `signature_paths: ["sig"]` adds the bundled `AcLibraryRb` signatures to the
9
+ # RBS environment.
10
+ #
11
+ # Inference cannot write these. The containers are generic over values the caller picks (a segment tree's monoid,
12
+ # a priority queue's elements), and their methods read parameters no call site in the library constrains, so
13
+ # `rigor sig-gen` over the gem's `lib_lock/` leaves 95 of its methods untyped. The signatures were written against
14
+ # that output and the gem's documentation, and `rigor check` over `lib_lock/` holds the method bodies to them.
15
+ class AcLibraryRb < Base
16
+ manifest(
17
+ id: "ac-library-rb",
18
+ target_gems: ["ac-library-rb"],
19
+ version: "0.1.0",
20
+ description: "RBS bundle for ac-library-rb (AtCoder Library): Segtree, LazySegtree, DSU, ModInt, flows " \
21
+ "and the math and string algorithms.",
22
+ signature_paths: ["sig"]
23
+ )
24
+ end
25
+ end
26
+ end
27
+
28
+ Rigor::Plugin.register(Rigor::Plugin::AcLibraryRb)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rigor/plugin/ac_library_rb"
@@ -0,0 +1,7 @@
1
+ module AcLibraryRb
2
+ class Convolution
3
+ def initialize: (?Integer mod, ?Integer? primitive_root) -> void
4
+
5
+ def convolution: (Array[Integer] a, Array[Integer] b) -> Array[Integer]
6
+ end
7
+ end