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.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- 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)
|
data/lib/rigor/lens/document.rb
CHANGED
|
@@ -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
|
-
|
|
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" =>
|
|
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
|
|
data/lib/rigor/lens/run.rb
CHANGED
|
@@ -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
|