rigortype 0.4.7 → 0.4.8

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/docs/handbook/01-getting-started.md +1 -1
  8. data/docs/handbook/11-sig-gen.md +35 -0
  9. data/docs/install.md +20 -0
  10. data/docs/manual/01-installation.md +67 -26
  11. data/docs/manual/02-cli-reference.md +307 -91
  12. data/docs/manual/03-configuration.md +41 -3
  13. data/docs/manual/04-diagnostics.md +5 -4
  14. data/docs/manual/08-skills.md +11 -0
  15. data/docs/manual/09-editor-integration.md +8 -0
  16. data/docs/manual/14-rails-quickstart.md +1 -1
  17. data/docs/manual/19-effect-labels.md +3 -4
  18. data/docs/manual/plugins/rigor-actionpack.md +15 -6
  19. data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
  20. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  21. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  22. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  23. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
  24. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
  25. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
  26. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  27. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
  28. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  29. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  30. data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
  31. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  32. data/lib/rigor/analysis/check_rules.rb +598 -99
  33. data/lib/rigor/analysis/crash_signature.rb +43 -1
  34. data/lib/rigor/analysis/dependency_recorder.rb +233 -76
  35. data/lib/rigor/analysis/incremental.rb +11 -1
  36. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
  37. data/lib/rigor/analysis/incremental_session.rb +279 -37
  38. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  39. data/lib/rigor/analysis/rule_catalog.rb +37 -1
  40. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  41. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  42. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
  43. data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
  44. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
  45. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
  46. data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
  47. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  48. data/lib/rigor/analysis/runner.rb +222 -39
  49. data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
  50. data/lib/rigor/analysis/worker_session.rb +42 -41
  51. data/lib/rigor/bleeding_edge.rb +19 -4
  52. data/lib/rigor/cache/file_digest.rb +39 -31
  53. data/lib/rigor/cache/incremental_snapshot.rb +66 -7
  54. data/lib/rigor/cache/rbs_descriptor.rb +37 -2
  55. data/lib/rigor/cache/rbs_environment.rb +5 -1
  56. data/lib/rigor/cli/baseline_command.rb +15 -2
  57. data/lib/rigor/cli/check_command.rb +4 -2
  58. data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
  59. data/lib/rigor/cli/lens_command.rb +17 -2
  60. data/lib/rigor/cli/sig_gen_command.rb +8 -0
  61. data/lib/rigor/cli/skill_describe.rb +1 -0
  62. data/lib/rigor/cli.rb +5 -2
  63. data/lib/rigor/config_audit.rb +32 -18
  64. data/lib/rigor/configuration/severity_profile.rb +9 -0
  65. data/lib/rigor/configuration.rb +32 -5
  66. data/lib/rigor/effects/collector.rb +53 -21
  67. data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
  68. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  69. data/lib/rigor/environment/default_libraries.rb +17 -0
  70. data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
  71. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  72. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  73. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  74. data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
  75. data/lib/rigor/environment/rbs_loader.rb +312 -63
  76. data/lib/rigor/environment.rb +131 -35
  77. data/lib/rigor/inference/acceptance.rb +50 -15
  78. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  79. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  80. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  81. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  82. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  83. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  84. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  85. data/lib/rigor/inference/block_run_count.rb +9 -2
  86. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  87. data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
  88. data/lib/rigor/inference/captured_locals.rb +2 -0
  89. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  90. data/lib/rigor/inference/clause_widening.rb +1 -1
  91. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  92. data/lib/rigor/inference/construction_escape.rb +56 -15
  93. data/lib/rigor/inference/declaration_walk.rb +4 -0
  94. data/lib/rigor/inference/def_node_resolver.rb +32 -14
  95. data/lib/rigor/inference/def_return_typer.rb +1 -1
  96. data/lib/rigor/inference/definer_resolution.rb +15 -7
  97. data/lib/rigor/inference/definers_index.rb +72 -16
  98. data/lib/rigor/inference/error_info.rb +16 -0
  99. data/lib/rigor/inference/expression_typer.rb +1459 -287
  100. data/lib/rigor/inference/flow_tracer.rb +31 -14
  101. data/lib/rigor/inference/fork_map.rb +3 -0
  102. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  103. data/lib/rigor/inference/global_write_census.rb +8 -6
  104. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  105. data/lib/rigor/inference/indexed_narrowing.rb +57 -8
  106. data/lib/rigor/inference/ivar_writer_census.rb +106 -11
  107. data/lib/rigor/inference/key_presence_guard.rb +51 -25
  108. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  109. data/lib/rigor/inference/last_line.rb +5 -2
  110. data/lib/rigor/inference/loop_value_record.rb +63 -15
  111. data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
  112. data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
  113. data/lib/rigor/inference/match_rebinding.rb +21 -8
  114. data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
  115. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  116. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  117. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  118. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  119. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  120. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  121. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
  122. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
  123. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
  124. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
  125. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  126. data/lib/rigor/inference/method_dispatcher.rb +66 -18
  127. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  128. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  129. data/lib/rigor/inference/module_function_state.rb +13 -2
  130. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  131. data/lib/rigor/inference/mutation_widening.rb +44 -4
  132. data/lib/rigor/inference/narrowing.rb +201 -27
  133. data/lib/rigor/inference/operand_effects.rb +43 -2
  134. data/lib/rigor/inference/optimistic_origin.rb +133 -8
  135. data/lib/rigor/inference/origin_lookup.rb +1 -1
  136. data/lib/rigor/inference/precision_scanner.rb +1 -1
  137. data/lib/rigor/inference/project_method_ownership.rb +28 -10
  138. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  139. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  140. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  141. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  142. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  143. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  144. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
  145. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
  146. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  147. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  148. data/lib/rigor/inference/remover_holder_scan.rb +76 -22
  149. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  150. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
  151. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
  152. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  153. data/lib/rigor/inference/scope_indexer.rb +2079 -183
  154. data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
  155. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  156. data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
  157. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  158. data/lib/rigor/inference/statement_evaluator.rb +970 -250
  159. data/lib/rigor/inference/string_mutation.rb +14 -0
  160. data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
  161. data/lib/rigor/inference/toplevel_def_census.rb +14 -6
  162. data/lib/rigor/inference/unknown_store_widening.rb +1 -1
  163. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  164. data/lib/rigor/inference/void_tail_summary.rb +29 -10
  165. data/lib/rigor/lens/claims/comparison.rb +78 -0
  166. data/lib/rigor/lens/claims/request.rb +131 -0
  167. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  168. data/lib/rigor/lens/claims.rb +208 -0
  169. data/lib/rigor/lens/contract_reader.rb +5 -0
  170. data/lib/rigor/lens/document.rb +1 -1
  171. data/lib/rigor/lens/file_reader.rb +21 -8
  172. data/lib/rigor/lens/run.rb +8 -0
  173. data/lib/rigor/lens.rb +1 -1
  174. data/lib/rigor/library_probe.rb +68 -0
  175. data/lib/rigor/memo_audit.rb +282 -0
  176. data/lib/rigor/plugin/base.rb +15 -2
  177. data/lib/rigor/plugin/io_boundary.rb +25 -3
  178. data/lib/rigor/plugin/manifest.rb +7 -3
  179. data/lib/rigor/plugin/registry.rb +13 -2
  180. data/lib/rigor/plugin/trust_policy.rb +46 -0
  181. data/lib/rigor/protection/mutation_cache.rb +4 -1
  182. data/lib/rigor/reflection/constant_ancestors.rb +79 -22
  183. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  184. data/lib/rigor/reflection/declarations.rb +58 -0
  185. data/lib/rigor/reflection/project_method.rb +177 -0
  186. data/lib/rigor/reflection.rb +2 -0
  187. data/lib/rigor/rooted_glob.rb +18 -1
  188. data/lib/rigor/run_scoped_slots.rb +20 -20
  189. data/lib/rigor/scope/discovery_index.rb +149 -2
  190. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  191. data/lib/rigor/scope/resolution_chain.rb +900 -55
  192. data/lib/rigor/scope.rb +307 -34
  193. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  194. data/lib/rigor/sig_gen/classification.rb +14 -1
  195. data/lib/rigor/sig_gen/generator.rb +222 -13
  196. data/lib/rigor/sig_gen/method_candidate.rb +14 -0
  197. data/lib/rigor/sig_gen/observation_collector.rb +11 -2
  198. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  199. data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
  200. data/lib/rigor/sig_gen/writer.rb +21 -0
  201. data/lib/rigor/sig_gen.rb +1 -0
  202. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  203. data/lib/rigor/source/node_children.rb +13 -0
  204. data/lib/rigor/type/combinator.rb +164 -8
  205. data/lib/rigor/type/interface.rb +60 -0
  206. data/lib/rigor/type.rb +1 -0
  207. data/lib/rigor/type_of_memo/serving.rb +99 -0
  208. data/lib/rigor/type_of_memo/stats.rb +43 -0
  209. data/lib/rigor/type_of_memo.rb +337 -0
  210. data/lib/rigor/typing_state.rb +1094 -0
  211. data/lib/rigor/version.rb +1 -1
  212. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
  213. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
  214. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  215. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  216. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  217. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  218. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  219. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
  220. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
  221. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  222. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
  223. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  224. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
  225. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
  226. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
  227. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  228. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  229. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
  230. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  231. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
  232. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
  233. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
  234. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
  235. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
  236. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  237. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  238. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
  239. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  240. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
  241. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  242. data/sig/rigor/analysis/fact_store.rbs +2 -3
  243. data/sig/rigor/cache/descriptor.rbs +14 -0
  244. data/sig/rigor/environment.rbs +4 -1
  245. data/sig/rigor/plugin/base.rbs +53 -19
  246. data/sig/rigor/plugin/interfaces.rbs +51 -0
  247. data/sig/rigor/plugin/io_boundary.rbs +2 -3
  248. data/sig/rigor/plugin/node_context.rbs +21 -0
  249. data/sig/rigor/reflection.rbs +37 -0
  250. data/sig/rigor/scope.rbs +22 -3
  251. data/sig/rigor/trinary.rbs +1 -0
  252. data/sig/rigor/type.rbs +9 -3
  253. data/sig/rigor.rbs +3 -2
  254. data/skills/rigor-ask/SKILL.md +1 -0
  255. data/skills/rigor-project-init/references/01-detect.md +1 -0
  256. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
  257. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  258. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  259. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  260. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  261. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  262. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  263. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  264. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  265. data/skills/rigor-type-oracle/SKILL.md +26 -1
  266. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  267. metadata +48 -2
  268. data/lib/rigor/inference/memo_audit.rb +0 -55
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "claims/request"
4
+ require_relative "claims/type_reader"
5
+ require_relative "claims/comparison"
6
+
7
+ module Rigor
8
+ module Lens
9
+ # Read-only documentation coverage against the contracts retained by this lens run.
10
+ class Claims
11
+ REASONS = %w[no_such_declaration unsupported_declaration declaration_mismatch unsupported_subject
12
+ no_authored_contract unsupported_contract_source extrbs_contract refined_contract
13
+ unsupported_overloads no_such_parameter unsupported_parameter_kind unmatched_parameter
14
+ unsupported_grammar_feature unresolved_name ambiguous_name dynamic_type unsupported_type
15
+ unproven].freeze
16
+ POSITIONALS = %w[required optional rest post].freeze
17
+
18
+ def initialize(request:, outcome:, run:)
19
+ @request = request
20
+ @outcome = outcome
21
+ @run = run
22
+ end
23
+
24
+ def call
25
+ @request.verify_sources!(@outcome.targets)
26
+ { "schema" => "lens-claims/v0", "revision" => 1, "request_digest" => @request.digest,
27
+ "grammar" => "rbs", "capabilities" => %w[return required_param required_keyword],
28
+ "results" => @request.claims.map { |claim| result(claim) } }
29
+ end
30
+
31
+ private
32
+
33
+ def result(claim)
34
+ answer = { "id" => claim.fetch("id"), "verdict" => "unknown", "reason" => "unproven",
35
+ "relation" => nil, "direction" => nil, "site" => nil, "member" => nil,
36
+ "slot" => nil, "contract" => nil, "evidence" => [] }
37
+ target = @outcome.targets.find { |entry| entry.absolute == @request.paths.fetch(claim.fetch("file")) }
38
+ return answer.merge("verdict" => "unavailable", "reason" => target.status) unless target.status == "ok"
39
+
40
+ parsed, grammar_reason = TypeReader.parse(claim.fetch("claim"))
41
+ if grammar_reason == "syntax_error"
42
+ return answer.merge("verdict" => "invalid_claim",
43
+ "reason" => "syntax_error")
44
+ end
45
+
46
+ site, member, reason = bind(claim.fetch("declaration"), target.reader)
47
+ return answer.merge("reason" => reason) if reason
48
+
49
+ answer.merge!("site" => site.data.fetch("key"), "member" => member.slice("owner", "scope", "name"))
50
+ check_slot(claim, parsed, grammar_reason, site, member, target, answer)
51
+ end
52
+
53
+ def bind(declaration, reader)
54
+ sites = reader.sites.select { |site| same_span?(site.data.fetch("span"), declaration.fetch("span")) }
55
+ if sites.empty?
56
+ omission = reader.omissions.any? { |entry| same_span?(entry.fetch("span"), declaration.fetch("span")) }
57
+ return [nil, nil, omission ? "unsupported_declaration" : "no_such_declaration"]
58
+ end
59
+ return [nil, nil, "unsupported_declaration"] unless sites.one? && sites.first.data["kind"] == "method"
60
+
61
+ site = sites.first
62
+ member = @outcome.member_rows.find do |row|
63
+ row.values_at("owner", "scope", "name") == declaration.values_at("owner", "scope", "name") &&
64
+ row.fetch("sites").include?(site.data.fetch("key")) && declaration["kind"] == "method"
65
+ end
66
+ member ? [site, member, nil] : [nil, nil, "declaration_mismatch"]
67
+ end
68
+
69
+ def same_span?(left, right)
70
+ left.values_at("start_offset", "end_offset") == right.values_at("start_offset", "end_offset")
71
+ end
72
+
73
+ def check_slot(claim, parsed, grammar_reason, site, member, target, answer)
74
+ tag = claim.fetch("tag")
75
+ return answer.merge("reason" => "unsupported_subject") unless supported_subject?(tag)
76
+
77
+ raw = @run.raw_contracts[member.values_at("owner", "scope", "name")]
78
+ overloads = member.fetch("overloads")
79
+ reasons = member_reasons(site, member) + [grammar_reason].compact
80
+ slot, index, subject_reason = subject(tag, site, overloads.one? ? overloads.first : nil)
81
+ reasons << subject_reason if subject_reason
82
+ if slot
83
+ read_slot(slot, index, tag, overloads.first, raw, answer, reasons)
84
+ type = authored_type(raw, tag, index)
85
+ compare_types(parsed, type, site, target, answer, reasons) if parsed && type
86
+ end
87
+ reason = REASONS.find { |candidate| reasons.include?(candidate) }
88
+ reason ? answer.merge("verdict" => "unknown", "reason" => reason, "evidence" => []) : answer
89
+ end
90
+
91
+ def supported_subject?(tag)
92
+ %w[param return].include?(tag.fetch("name")) && (tag["name"] == "return" || tag["subject"])
93
+ end
94
+
95
+ def authored_type(raw, tag, index)
96
+ contract_type(raw.types.first.type, tag, index) if raw&.types&.one?
97
+ end
98
+
99
+ def member_reasons(site, member)
100
+ reasons = []
101
+ overloads = member.fetch("overloads")
102
+ reasons << "no_authored_contract" if member["basis"] != "contract"
103
+ reasons << "unsupported_overloads" unless overloads.one? && overloads.first["params"]
104
+ reasons << "extrbs_contract" if site.contract_comments.any? do |comment|
105
+ comment.location.slice.match?(/\A#\s*@extrbs\b/)
106
+ end
107
+ reasons
108
+ end
109
+
110
+ def read_slot(slot, index, tag, overload, raw, answer, reasons)
111
+ answer.merge!("relation" => "documentation_coverage", "slot" => slot,
112
+ "direction" => tag["name"] == "param" ? "claim_within_contract" : "contract_within_claim")
113
+ contract = tag["name"] == "return" ? overload["return"] : overload.fetch("params")[index]["type"]
114
+ inspect_contract(contract, raw, tag, index, answer, reasons)
115
+ end
116
+
117
+ def inspect_contract(contract, raw, tag, index, answer, reasons)
118
+ if contract.nil? || contract["state"] == "unavailable" || contract["provenance"] == "inferred"
119
+ reasons << "no_authored_contract"
120
+ return
121
+ end
122
+ provenance = contract["provenance"]
123
+ reasons << "unsupported_contract_source" if provenance == "library" || provenance&.start_with?("plugin:")
124
+ answer["contract"] = contract.slice("type", "provenance", "path")
125
+ reasons << "refined_contract" if refined?(raw, tag, index)
126
+ end
127
+
128
+ def refined?(raw, tag, index)
129
+ return false if raw.nil?
130
+ return raw.annotations.any? { |text| text.start_with?("rigor:v1:return:") } if tag["name"] == "return"
131
+
132
+ raw.types.any? do |method_type|
133
+ function = method_type.type
134
+ param = function_params(function)[index]
135
+ if function.respond_to?(:required_keywords)
136
+ keyword = function.required_keywords.find do |_key, value|
137
+ value.equal?(param)
138
+ end
139
+ end
140
+ name = keyword ? keyword.first : param&.name
141
+ name && raw.annotations.any? { |text| text.match?(/\Arigor:v1:param:\s*#{Regexp.escape(name.to_s)}\s/) }
142
+ end
143
+ end
144
+
145
+ def subject(tag, site, overload)
146
+ return [{ "kind" => "return" }, nil, nil] if tag["name"] == "return" && overload
147
+ return [nil, nil, nil] if tag["name"] == "return"
148
+
149
+ params = site.data["params"] || []
150
+ param = params.find { |entry| entry["name"] == tag["subject"] }
151
+ return [nil, nil, "no_such_parameter"] unless param
152
+ return [nil, nil, "unsupported_parameter_kind"] unless %w[required keyreq].include?(param["kind"])
153
+ return [nil, nil, nil] unless overload && overload["params"]
154
+
155
+ declared = overload.fetch("params")
156
+ index = parameter_index(param, params, declared)
157
+ return [nil, nil, "unmatched_parameter"] unless index
158
+
159
+ [{ "kind" => "param", "name" => param["name"], "param_kind" => param["kind"], "index" => index }, index, nil]
160
+ end
161
+
162
+ def parameter_index(param, params, declared)
163
+ if param["kind"] == "keyreq"
164
+ return declared.index { |entry| entry["kind"] == "keyreq" && entry["name"] == param["name"] }
165
+ end
166
+
167
+ source_positions = params.select { |entry| POSITIONALS.include?(entry["kind"]) }
168
+ contract_positions = declared.select { |entry| POSITIONALS.include?(entry["kind"]) }
169
+ return unless source_positions.map { |entry| entry["kind"] } == contract_positions.map { |entry| entry["kind"] }
170
+
171
+ source_positions.index(param)
172
+ end
173
+
174
+ def contract_type(function, tag, index)
175
+ tag["name"] == "return" ? function.return_type : function_params(function)[index]&.type
176
+ end
177
+
178
+ def function_params(function)
179
+ return [] unless function.respond_to?(:required_positionals)
180
+
181
+ function.required_positionals + function.optional_positionals + [function.rest_positionals].compact +
182
+ function.trailing_positionals + function.required_keywords.values + function.optional_keywords.values +
183
+ [function.rest_keywords].compact
184
+ end
185
+
186
+ def compare_types(parsed, contract, site, target, answer, reasons)
187
+ scope = @run.claim_contexts.fetch(target.absolute)
188
+ nesting = site.data.fetch("lexical_nesting")
189
+ reader = TypeReader.new(scope: scope, nesting: nesting, singleton_nesting: site.data["singleton_nesting"],
190
+ owner: site.data["owner"])
191
+ claim_arms = reader.read(parsed, claim: true)
192
+ contract_arms = reader.read(contract)
193
+ reasons.concat(reader.reasons.to_a)
194
+ return unless reasons.empty?
195
+
196
+ left, right = if answer["direction"] == "claim_within_contract"
197
+ [claim_arms, contract_arms]
198
+ else
199
+ [contract_arms, claim_arms]
200
+ end
201
+ comparator = Comparison.new(scope.environment.rbs_loader,
202
+ relative_conflict: nesting == [] && site.data["owner"] == "Object")
203
+ verdict, reason, evidence = comparator.call(left, right)
204
+ answer.merge!("verdict" => verdict, "reason" => reason, "evidence" => evidence)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -16,9 +16,13 @@ module Rigor
16
16
  # `overloads` hold `{rbs, params, block, return}`; a return the contract leaves to inference is nil there,
17
17
  # with `return_inferred` set. `buffers` are the declaring buffer names.
18
18
  Contract = Data.define(:overloads, :provenance, :path, :return_inferred, :buffers)
19
+ RawContract = Data.define(:types, :annotations)
20
+
21
+ attr_reader :raw_contracts
19
22
 
20
23
  def initialize(provenance)
21
24
  @provenance = provenance
25
+ @raw_contracts = {}
22
26
  end
23
27
 
24
28
  # The member's contract, or nil when it has none of its own. `name` is the Ruby method name.
@@ -28,6 +32,7 @@ module Rigor
28
32
  return nil if defs.empty?
29
33
 
30
34
  annotations = annotations_of(method, defs)
35
+ @raw_contracts[[owner, scope.to_s, name]] = RawContract.new(types: defs.map(&:type), annotations: annotations)
31
36
  return nil if annotations.include?(INFERRED_SIGNATURE)
32
37
 
33
38
  contract(defs, annotations)
@@ -24,7 +24,7 @@ module Rigor
24
24
  def to_h
25
25
  { "schema" => SCHEMA, "revision" => REVISION, "producer" => producer, "context" => context,
26
26
  "capabilities" => CAPABILITIES, "files" => @outcome.targets.map { |target| file(target) },
27
- "members" => @outcome.member_rows }
27
+ "members" => @outcome.member_rows, "claims" => nil }
28
28
  end
29
29
 
30
30
  private
@@ -26,7 +26,10 @@ module Rigor
26
26
  # `kind` is :module, :class or :singleton. `module_function` is :off, :on (a bare `module_function` written
27
27
  # directly in a module body covers the later direct defs) or :unknown (the reading Ruby gives and the one
28
28
  # `check`'s tables give could differ, so the later defs are omissions).
29
- Body = Struct.new(:current, :uncertain, :detached, :kind, :module_function)
29
+ # `lexical` is the named part of the body's Ruby cref chain, which a builder block does not extend (Ruby
30
+ # `class_eval`s the block on the new class, but its constants resolve in the scope that wrote it) and a
31
+ # `class << self` body extends only with its anonymous singleton class, left out here.
32
+ Body = Struct.new(:current, :uncertain, :detached, :kind, :module_function, :lexical)
30
33
 
31
34
  ATTRIBUTE_CALLS = { attr_reader: "reader", attr_writer: "writer", attr_accessor: "accessor" }.freeze
32
35
  OMITTED_CALLS = {
@@ -92,7 +95,7 @@ module Rigor
92
95
  end
93
96
 
94
97
  def detached
95
- @bodies.push(Body.new("public", false, true, :detached, :off))
98
+ @bodies.push(Body.new("public", false, true, :detached, :off, @bodies.last&.lexical))
96
99
  yield
97
100
  ensure
98
101
  @bodies.pop
@@ -124,14 +127,14 @@ module Rigor
124
127
  return detached { walk(node.body, nesting, singleton: singleton, in_block: true) } if node.body
125
128
  end
126
129
 
127
- walk_body(node.body, nesting, singleton: true, kind: :singleton)
130
+ walk_body(node.body, nesting, singleton: true, kind: :singleton, lexical: lexical_nesting(nesting))
128
131
  end
129
132
 
130
133
  # A class body's statements, tracking the running visibility and `module_function`.
131
- def walk_body(body, nesting, singleton:, kind:)
134
+ def walk_body(body, nesting, singleton:, kind:, lexical: nesting)
132
135
  return if body.nil?
133
136
 
134
- @bodies.push(Body.new("public", false, false, kind, :off))
137
+ @bodies.push(Body.new("public", false, false, kind, :off, lexical))
135
138
  statements = body.is_a?(Prism::StatementsNode) ? body.body : [body]
136
139
  statements.each { |child| walk_body_statement(child, nesting, singleton) }
137
140
  ensure
@@ -425,7 +428,7 @@ module Rigor
425
428
  "block" => nil, "body_return" => nil }, contracts: trailing ? :trailing : false)
426
429
  end
427
430
  block = node.value.block
428
- walk_body(block.body, nesting + [name], singleton: false, kind: :builder) if block&.body
431
+ walk_body(block.body, nesting + [name], singleton: false, kind: :builder, lexical: nesting) if block&.body
429
432
  end
430
433
 
431
434
  # Ruby forces a field reader named `initialize_copy` (say) private, but not its `Struct` writer: a `Data`
@@ -539,7 +542,9 @@ module Rigor
539
542
  data = { "kind" => "method", "name" => node.name.to_s, "owner" => owner, "scope" => scope.to_s,
540
543
  "attribute" => nil, "members" => [node.name.to_s], "module_function" => module_function,
541
544
  "visibility" => module_function ? "private" : visibility(node, nesting, scope),
542
- "params" => shape_params, "block" => shape_block, "body_return" => returns }
545
+ "params" => shape_params, "block" => shape_block, "body_return" => returns,
546
+ "lexical_nesting" => lexical_nesting(nesting),
547
+ "singleton_nesting" => scope == :singleton && node.receiver.nil? }
543
548
  return data unless module_function
544
549
 
545
550
  # Internal to the member pass: the singleton copy is public until `private_class_method` says otherwise,
@@ -549,6 +554,12 @@ module Rigor
549
554
  data.merge("singleton_visibility" => "public", "singleton_body_return" => copy)
550
555
  end
551
556
 
557
+ # The Ruby cref chain a def written here reads its constants through: the enclosing body's, which differs
558
+ # from the owner nesting under a builder block.
559
+ def lexical_nesting(nesting)
560
+ @bodies.last&.lexical || nesting
561
+ end
562
+
552
563
  # Whether the instance-scope return may differ from the singleton copy's: the body reads `self`, instance or
553
564
  # class state, or `super` (nested defs excluded, blocks included, which over-declines safely), or the return
554
565
  # names the owner's type, which is where a `self` that flowed through `itself`, `tap` or another module
@@ -628,13 +639,15 @@ module Rigor
628
639
  def alias_site(names, owner)
629
640
  new_name, old_name = names
630
641
  last, copy = alias_original(owner, old_name)
642
+ copied = copy&.data || {}
631
643
  visibility = FORCED_PRIVATE.include?(new_name) ? "private" : last&.data&.fetch("visibility")
632
644
  # `initialize` reads `void` by its name, which neither side of an alias across it keeps.
633
645
  returns = copy && ![new_name, old_name].include?("initialize") ? copy.data["body_return"] : nil
634
646
  { "kind" => "method", "name" => new_name, "owner" => owner, "scope" => "instance", "attribute" => nil,
635
647
  "members" => [new_name], "module_function" => false, "visibility" => visibility,
636
- "params" => copy&.data&.fetch("params"), "block" => copy&.data&.fetch("block"),
648
+ "params" => copied["params"], "block" => copied["block"],
637
649
  "body_return" => returns || Slot.unavailable("not_computed"),
650
+ "lexical_nesting" => copied["lexical_nesting"], "singleton_nesting" => copied["singleton_nesting"],
638
651
  "alias_of" => old_name, "aliased_node" => copy && aliased_node(copy) }
639
652
  end
640
653
 
@@ -23,6 +23,12 @@ module Rigor
23
23
  Target = Data.define(:argument, :path, :absolute, :status, :reason, :reader, :diagnostics, :inputs)
24
24
  Outcome = Data.define(:targets, :member_rows, :plugins, :warm)
25
25
 
26
+ attr_reader :claim_contexts
27
+
28
+ def raw_contracts
29
+ @contract_reader&.raw_contracts || {}
30
+ end
31
+
26
32
  # @param root — the project root (absolute); every path in the answer is relative to it
27
33
  def initialize(configuration:, root:, arguments:, workers:)
28
34
  @configuration = configuration
@@ -80,6 +86,7 @@ module Rigor
80
86
  @contracts = {}
81
87
  @defaulted_blocks = {}
82
88
  @site_paths = {}
89
+ @claim_contexts = {}
83
90
  end
84
91
 
85
92
  # A file requested again, in any spelling, is answered once: the later entries read `duplicate`, naming the
@@ -134,6 +141,7 @@ module Rigor
134
141
  end
135
142
  end
136
143
  @captured[absolute] = reader
144
+ @claim_contexts[absolute] = scope
137
145
  end
138
146
 
139
147
  # The constant's type as `check` reads it ({Reflection.constant_type_for}): the value the project's source
data/lib/rigor/lens.rb CHANGED
@@ -10,7 +10,7 @@ module Rigor
10
10
  # Bumped on every breaking change to the `lens/v0` document (docs/internal-spec/lens.md § Status).
11
11
  REVISION = 1
12
12
  CAPABILITIES = %w[class module method attribute constant module_function builder alias body_return
13
- diagnostics inputs].freeze
13
+ diagnostics inputs claims].freeze
14
14
 
15
15
  module_function
16
16
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "environment/required_features"
4
+
5
+ module Rigor
6
+ # Issue #2961 — answers what `RBS::EnvironmentLoader#has_library?(version: nil)` answers, for the
7
+ # {ConfigAudit} library warnings. The CLI has not loaded `rbs` when that audit runs, and loading it costs
8
+ # ~90 ms on the warm boot-slim path, so without a loaded `rbs` this probes the filesystem instead: a gem's
9
+ # `sig/` directory, or a release-versioned directory under the bundled rbs gem's `stdlib/<name>/`
10
+ # (`Repository::GemRBS#load!`). A probe that cannot place the rbs gem answers true: it never warns when unsure.
11
+ module LibraryProbe
12
+ module_function
13
+
14
+ def predicate
15
+ if defined?(::RBS::EnvironmentLoader)
16
+ return ->(lib) { ::RBS::EnvironmentLoader.new.has_library?(library: lib, version: nil) }
17
+ end
18
+
19
+ stdlib = stdlib_root
20
+ return ->(_lib) { true } if stdlib.nil?
21
+
22
+ ->(lib) { gem_sig_dir?(lib) || stdlib_library?(stdlib, lib) }
23
+ end
24
+
25
+ # The names of `library_names` the environment loads: a default library, a vendored feature token, or a name
26
+ # RBS has. The loader drops any other name (`RbsLoader.library_loader` keeps only `has_library?` ones), so it
27
+ # covers nothing. The predicate is built only when a name needs it: building it can construct an RBS loader.
28
+ # Callers have `Environment` loaded, which defines `DEFAULT_LIBRARIES`.
29
+ def loadable(library_names)
30
+ has = nil
31
+ library_names.select do |name|
32
+ Environment::DEFAULT_LIBRARIES.include?(name) || Environment::RequiredFeatures.feature_of_token(name) ||
33
+ (has ||= predicate).call(name)
34
+ end
35
+ end
36
+
37
+ # The `libraries:` names worth auditing: a vendored feature name (`prime`) becomes a vendored feature token
38
+ # that the loader never asks `has_library?` about, so it is not an RBS library name to check.
39
+ def auditable(libraries)
40
+ Array(libraries).map(&:to_s) - Environment::RequiredFeatures::VENDORED_DIRS.keys
41
+ end
42
+
43
+ def stdlib_root
44
+ dir = File.join(Gem::Specification.find_by_name("rbs").gem_dir, "stdlib")
45
+ File.directory?(dir) ? dir : nil
46
+ rescue Gem::LoadError
47
+ nil
48
+ end
49
+
50
+ def gem_sig_dir?(name)
51
+ File.directory?(File.join(Gem::Specification.find_by_name(name).gem_dir, "sig"))
52
+ rescue Gem::MissingSpecError
53
+ false
54
+ rescue Gem::LoadError
55
+ true
56
+ end
57
+
58
+ def stdlib_library?(stdlib, name)
59
+ return false if name.include?("/") || name.include?("\0")
60
+
61
+ Dir.children(File.join(stdlib, name)).any? do |child|
62
+ Gem::Version.correct?(child) && !Gem::Version.create(child).prerelease?
63
+ end
64
+ rescue SystemCallError
65
+ false
66
+ end
67
+ end
68
+ end