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
|
@@ -80,6 +80,20 @@ module Rigor
|
|
|
80
80
|
type.is_a?(Type::Constant) && type.value.is_a?(String)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# A predicate refinement over String (`decimal-int-string`, `lowercase-string`) under a String mutator → its
|
|
84
|
+
# `String` base. Any in-place mutation can falsify the predicate (`s.clear` leaves a `decimal-int-string`
|
|
85
|
+
# empty, `s << "A"` a `lowercase-string` mixed-case), so none keeps it. `nil` for every other input.
|
|
86
|
+
def widen_refined(refined, method_name)
|
|
87
|
+
return nil unless refined?(refined)
|
|
88
|
+
return nil unless MUTATORS.include?(method_name)
|
|
89
|
+
|
|
90
|
+
refined.base
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def refined?(type)
|
|
94
|
+
type.is_a?(Type::Refined) && type.base.is_a?(Type::Nominal) && type.base.class_name == "String"
|
|
95
|
+
end
|
|
96
|
+
|
|
83
97
|
# Whether `method_name`, called with `arg_types` (the call's positional argument types, possibly none), can leave
|
|
84
98
|
# a non-empty receiver empty, and so retract a `non-empty-string` refinement. Every mutator outside
|
|
85
99
|
# {EMPTY_PRESERVING} can, except a {TRANSLATORS} call whose replacement is provably non-empty.
|
|
@@ -323,21 +323,29 @@ module Rigor
|
|
|
323
323
|
# - `class_methods do ... end` blocks are NOT yet handled —
|
|
324
324
|
# singleton-level emission is out of scope per the slice-3
|
|
325
325
|
# floor framing.
|
|
326
|
+
#
|
|
327
|
+
# Issue #3005 — the `included do` blocks of every body of a module some body extends with Concern are read: a
|
|
328
|
+
# reopening that does not repeat the `extend` runs its block on a module that is already a concern (loaded
|
|
329
|
+
# first, its `included do` raises, so no running program has the includer without the calls). A module no
|
|
330
|
+
# body extends with Concern contributes nothing.
|
|
326
331
|
CONCERN_NAME = "ActiveSupport::Concern"
|
|
327
332
|
|
|
328
333
|
def build_concern_index(asts)
|
|
329
|
-
|
|
334
|
+
bodies = Hash.new { |hash, name| hash[name] = [] }
|
|
330
335
|
asts.each_value do |ast|
|
|
331
336
|
next if ast.nil?
|
|
332
337
|
|
|
333
338
|
walk_module_decls(ast, []) do |module_name, body|
|
|
334
|
-
|
|
335
|
-
next unless concern_module_body?(body)
|
|
336
|
-
|
|
337
|
-
deferred_calls = collect_included_do_calls(body)
|
|
338
|
-
index[module_name] = deferred_calls.freeze if deferred_calls.any?
|
|
339
|
+
bodies[module_name] << body unless module_name.nil? || body.nil?
|
|
339
340
|
end
|
|
340
341
|
end
|
|
342
|
+
index = {}
|
|
343
|
+
bodies.each do |module_name, list|
|
|
344
|
+
next unless list.any? { |body| concern_module_body?(body) }
|
|
345
|
+
|
|
346
|
+
deferred_calls = list.flat_map { |body| collect_included_do_calls(body) }
|
|
347
|
+
index[module_name] = deferred_calls.freeze if deferred_calls.any?
|
|
348
|
+
end
|
|
341
349
|
index.freeze
|
|
342
350
|
end
|
|
343
351
|
|
|
@@ -359,18 +367,34 @@ module Rigor
|
|
|
359
367
|
end
|
|
360
368
|
|
|
361
369
|
# Recognises a module body that begins with (or contains at top level) an `extend ActiveSupport::Concern`
|
|
362
|
-
# statement.
|
|
370
|
+
# statement. Issue #3005 — `self.extend` and a receiverless or `self.` `send` / `public_send` /
|
|
371
|
+
# `__send__(:extend, …)` are the same call.
|
|
363
372
|
def concern_module_body?(body)
|
|
364
373
|
return false unless body.respond_to?(:body)
|
|
365
374
|
|
|
366
375
|
body.body.any? do |stmt|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
args = stmt.arguments&.arguments || []
|
|
370
|
-
args.any? { |arg| const_name_string(arg) == CONCERN_NAME }
|
|
376
|
+
args = concern_extend_arguments(stmt)
|
|
377
|
+
args&.any? { |arg| const_name_string(arg) == CONCERN_NAME }
|
|
371
378
|
end
|
|
372
379
|
end
|
|
373
380
|
|
|
381
|
+
CONCERN_SEND_CALLS = %i[send public_send __send__].freeze
|
|
382
|
+
private_constant :CONCERN_SEND_CALLS
|
|
383
|
+
|
|
384
|
+
# The modules an `extend` statement on the module's own `self` names, or nil for any other statement.
|
|
385
|
+
def concern_extend_arguments(stmt)
|
|
386
|
+
return nil unless stmt.is_a?(Prism::CallNode)
|
|
387
|
+
return nil unless stmt.receiver.nil? || stmt.receiver.is_a?(Prism::SelfNode)
|
|
388
|
+
|
|
389
|
+
args = stmt.arguments&.arguments || []
|
|
390
|
+
return args if stmt.name == :extend
|
|
391
|
+
return nil unless CONCERN_SEND_CALLS.include?(stmt.name)
|
|
392
|
+
|
|
393
|
+
name = args.first
|
|
394
|
+
sent = name.unescaped if name.is_a?(Prism::SymbolNode) || name.is_a?(Prism::StringNode)
|
|
395
|
+
sent == "extend" ? args.drop(1) : nil
|
|
396
|
+
end
|
|
397
|
+
|
|
374
398
|
def collect_included_do_calls(body)
|
|
375
399
|
body.body.flat_map do |stmt|
|
|
376
400
|
included_do = stmt.is_a?(Prism::CallNode) && stmt.receiver.nil? && stmt.name == :included
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
4
|
-
require_relative "memo_audit"
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
require_relative "../memo_audit"
|
|
5
5
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Inference
|
|
@@ -23,8 +23,14 @@ module Rigor
|
|
|
23
23
|
# is bypassed while one is active, as the callee floor's other memos are.
|
|
24
24
|
module ToplevelDefCensus
|
|
25
25
|
OBJECT_OWNERS = %w[Object Kernel BasicObject].freeze
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# ADR-129 WD1 — a run-scoped Memo, per discovery index and name: the answer is a function of both, and
|
|
27
|
+
# `MemoAudit` checks it on a hit.
|
|
28
|
+
RELIABLE = TypingState.memo(
|
|
29
|
+
:toplevel_def_reliable,
|
|
30
|
+
run_scoped: true, audit: "top-level def census",
|
|
31
|
+
reason: "keyed by discovery index, then name; the environment is the run's; audited by MemoAudit"
|
|
32
|
+
)
|
|
33
|
+
private_constant :OBJECT_OWNERS, :RELIABLE
|
|
28
34
|
|
|
29
35
|
module_function
|
|
30
36
|
|
|
@@ -32,9 +38,11 @@ module Rigor
|
|
|
32
38
|
def reliable?(scope, name)
|
|
33
39
|
return compute?(scope, name) if Analysis::DependencyRecorder.active? || MemoAudit.suspended?
|
|
34
40
|
|
|
35
|
-
table =
|
|
41
|
+
table = TypingState.current.weak_key_map(RELIABLE)
|
|
36
42
|
by_name = (table[scope.discovery] ||= {})
|
|
37
|
-
|
|
43
|
+
unless by_name.key?(name)
|
|
44
|
+
return by_name[name] = MemoAudit.key_audit("top-level def census") { compute?(scope, name) }
|
|
45
|
+
end
|
|
38
46
|
|
|
39
47
|
MemoAudit.check("top-level def census", by_name[name], name) { compute?(scope, name) }
|
|
40
48
|
end
|
|
@@ -165,7 +165,7 @@ module Rigor
|
|
|
165
165
|
arg.members.any? { |member| !member.is_a?(Type::Constant) && !gradual_top?(member) }
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
def gradual_top?(member) =
|
|
168
|
+
def gradual_top?(member) = Type::Combinator.untyped?(member)
|
|
169
169
|
|
|
170
170
|
# `Dynamic[top] | A | B` as `Dynamic[A | B]` for the class members; a literal member (a value-pinned key) and any
|
|
171
171
|
# other element stay as they are.
|
|
@@ -57,7 +57,7 @@ module Rigor
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def candidate?(entry_type, joined_type)
|
|
60
|
-
|
|
60
|
+
Type::Combinator.untyped?(entry_type) &&
|
|
61
61
|
joined_type.is_a?(Type::Union) && joined_type.members.any?(Type::Dynamic)
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -5,7 +5,8 @@ require "prism"
|
|
|
5
5
|
require_relative "void_origin"
|
|
6
6
|
require_relative "../reflection"
|
|
7
7
|
require_relative "../source/node_children"
|
|
8
|
-
require_relative "../
|
|
8
|
+
require_relative "../typing_state"
|
|
9
|
+
require_relative "../memo_audit"
|
|
9
10
|
|
|
10
11
|
module Rigor
|
|
11
12
|
module Inference
|
|
@@ -54,8 +55,8 @@ module Rigor
|
|
|
54
55
|
# cycle (nil). Memoising is therefore always sound, and the cycle guard only ever rejects a node that
|
|
55
56
|
# is still mid-computation on the current chain (never yet memoised).
|
|
56
57
|
class VoidTailSummary
|
|
57
|
-
# Run-scoped memo, the ADR-84 `return_memo_bucket` house pattern: a single retained slot
|
|
58
|
-
#
|
|
58
|
+
# Run-scoped memo, the ADR-84 `return_memo_bucket` house pattern: a single retained slot in
|
|
59
|
+
# the thread's {TypingState} keyed by the run-generation token's identity, so a re-run in one process (LSP,
|
|
59
60
|
# ADR-62 warm loop) lands in a fresh bucket and can neither serve stale entries nor accumulate
|
|
60
61
|
# them. Thread-local ⇒ each fork-pool worker (a separate process) and each Ractor-pool worker (its
|
|
61
62
|
# own Ractor-local `Thread.current`) builds its own bucket; the bucket hash is created and mutated
|
|
@@ -63,8 +64,15 @@ module Rigor
|
|
|
63
64
|
# graceful-degradation branch is needed. `def_node` identity is stable per run (the ADR-85
|
|
64
65
|
# {DefNodeResolver} yields one node object per `(path, node_id)`), the same identity the ADR-84
|
|
65
66
|
# return memo relies on.
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
#
|
|
68
|
+
# ADR-129 WD1 — a run-scoped Memo keyed on the token's identity: the bucket holds summaries that are pure
|
|
69
|
+
# functions of the def node they key on within one run.
|
|
70
|
+
MEMO = TypingState.memo(
|
|
71
|
+
:void_tail_summary,
|
|
72
|
+
run_scoped: true, audit: ["void tail origin", "void tail walk"],
|
|
73
|
+
reason: "a bucket of per-def-node summaries, keyed on the run-generation token's identity"
|
|
74
|
+
)
|
|
75
|
+
private_constant :MEMO
|
|
68
76
|
|
|
69
77
|
def initialize(scope)
|
|
70
78
|
@scope = scope
|
|
@@ -82,7 +90,11 @@ module Rigor
|
|
|
82
90
|
return nil if def_node.nil?
|
|
83
91
|
|
|
84
92
|
memo = memo_bucket
|
|
85
|
-
|
|
93
|
+
if memo.key?(def_node)
|
|
94
|
+
return MemoAudit.check("void tail origin", memo[def_node], def_node) do
|
|
95
|
+
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
86
98
|
|
|
87
99
|
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
88
100
|
end
|
|
@@ -98,10 +110,14 @@ module Rigor
|
|
|
98
110
|
return nil if visited.key?(def_node)
|
|
99
111
|
|
|
100
112
|
memo = memo_bucket
|
|
101
|
-
|
|
113
|
+
if memo.key?(def_node)
|
|
114
|
+
return MemoAudit.check("void tail walk", memo[def_node], def_node) do
|
|
115
|
+
walk(def_node, owner, kind, {}.compare_by_identity)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
102
118
|
|
|
103
119
|
visited[def_node] = true
|
|
104
|
-
memo[def_node] = compute(def_node, owner, kind, visited)
|
|
120
|
+
memo[def_node] = MemoAudit.key_audit("void tail walk") { compute(def_node, owner, kind, visited) }
|
|
105
121
|
end
|
|
106
122
|
|
|
107
123
|
def compute(def_node, owner, kind, visited)
|
|
@@ -206,11 +222,14 @@ module Rigor
|
|
|
206
222
|
end
|
|
207
223
|
|
|
208
224
|
def memo_bucket
|
|
225
|
+
return {}.compare_by_identity if MemoAudit.enabled? && MemoAudit.suspended?
|
|
226
|
+
|
|
209
227
|
generation = scope.run_generation || scope.discovered_def_nodes
|
|
210
|
-
|
|
228
|
+
state = TypingState.current
|
|
229
|
+
slot = state[MEMO]
|
|
211
230
|
unless slot && slot[0].equal?(generation)
|
|
212
231
|
slot = [generation, {}.compare_by_identity]
|
|
213
|
-
|
|
232
|
+
state[MEMO] = slot
|
|
214
233
|
end
|
|
215
234
|
slot[1]
|
|
216
235
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../environment/member_consistency"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Lens
|
|
7
|
+
class Claims
|
|
8
|
+
# The deliberately small proof fragment of lens-claims.md. Unknown is not a failed subtype check.
|
|
9
|
+
class Comparison
|
|
10
|
+
def initialize(loader, relative_conflict:)
|
|
11
|
+
@loader = loader
|
|
12
|
+
@proof = Environment::MemberConsistency::RbsProof.new(loader)
|
|
13
|
+
@relative_conflict = relative_conflict
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(sub, sup)
|
|
17
|
+
included = sub.map do |arm|
|
|
18
|
+
match = sup.filter_map do |other|
|
|
19
|
+
(rule = inclusion(arm, other)) && evidence(rule, arm, other.display)
|
|
20
|
+
end.first
|
|
21
|
+
match
|
|
22
|
+
end
|
|
23
|
+
return ["compatible", "proven_inclusion", included] if included.none?(nil)
|
|
24
|
+
|
|
25
|
+
sub.each do |arm|
|
|
26
|
+
rules = sup.map { |other| disjoint(arm, other) }
|
|
27
|
+
next if rules.any?(nil)
|
|
28
|
+
|
|
29
|
+
return ["conflict", "proven_disjoint_arm", [evidence(rules.first, arm, sup.map(&:display).join(" | "))]]
|
|
30
|
+
end
|
|
31
|
+
["unknown", "unproven", []]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def evidence(rule, arm, against)
|
|
37
|
+
{ "rule" => rule, "arm" => arm.display, "against" => against }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inclusion(sub, sup)
|
|
41
|
+
return "equal" if sub.identity == sup.identity
|
|
42
|
+
return nil unless sup.kind == :class && sup.arguments.empty?
|
|
43
|
+
return nil unless @loader.class_type_param_names(sup.value).empty?
|
|
44
|
+
|
|
45
|
+
name = sub.kind == :literal ? sub.value.class.name : sub.value
|
|
46
|
+
unreliable = Environment::MemberConsistency::RbsProof::UNRELIABLE_ANCESTRY
|
|
47
|
+
return nil if unreliable.include?(name) || unreliable.include?(sup.value)
|
|
48
|
+
return nil unless @loader.ancestor_names_for(name).include?(sup.value)
|
|
49
|
+
|
|
50
|
+
sub.kind == :literal ? "literal_class_ancestor" : "class_ancestor"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def disjoint(left, right)
|
|
54
|
+
return nil if !@relative_conflict && (left.relative || right.relative)
|
|
55
|
+
return "distinct_literals" if left.kind == :literal && right.kind == :literal && !left.value.eql?(right.value)
|
|
56
|
+
|
|
57
|
+
disjoint_classes(left, right)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def disjoint_classes(left, right)
|
|
61
|
+
left_name = left.kind == :literal ? left.value.class.name : left.value
|
|
62
|
+
right_name = right.kind == :literal ? right.value.class.name : right.value
|
|
63
|
+
left_ancestors = @proof.class_ancestors(left_name)
|
|
64
|
+
right_ancestors = @proof.class_ancestors(right_name)
|
|
65
|
+
return nil if left_ancestors.nil? || right_ancestors.nil?
|
|
66
|
+
|
|
67
|
+
if left.kind == :literal
|
|
68
|
+
"literal_outside_class" unless left_ancestors.include?(right_name)
|
|
69
|
+
elsif right.kind == :literal
|
|
70
|
+
"literal_outside_class" unless right_ancestors.include?(left_name)
|
|
71
|
+
elsif !left_ancestors.include?(right_name) && !right_ancestors.include?(left_name)
|
|
72
|
+
"unrelated_classes"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Lens
|
|
7
|
+
class Claims
|
|
8
|
+
class IntegrityError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Validates transport and source identity before the incremental session is started.
|
|
11
|
+
class Request
|
|
12
|
+
attr_reader :claims, :digest, :paths
|
|
13
|
+
|
|
14
|
+
def self.read(path, root:, arguments:)
|
|
15
|
+
bytes = File.binread(path).force_encoding(Encoding::UTF_8)
|
|
16
|
+
raise IntegrityError, "claims request is not UTF-8" unless bytes.valid_encoding?
|
|
17
|
+
|
|
18
|
+
new(JSON.parse(bytes), digest: Lens.digest(bytes), root: root, arguments: arguments)
|
|
19
|
+
rescue SystemCallError, JSON::ParserError => e
|
|
20
|
+
raise IntegrityError, "cannot read claims request: #{e.message}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(data, digest:, root:, arguments:)
|
|
24
|
+
@digest = digest
|
|
25
|
+
@root = root
|
|
26
|
+
@arguments = arguments.map { |argument| File.expand_path(argument, root) }
|
|
27
|
+
validate_header(data)
|
|
28
|
+
@claims = data.fetch("claims")
|
|
29
|
+
@files = data.fetch("files")
|
|
30
|
+
@paths = {}
|
|
31
|
+
@readable_paths = Set.new
|
|
32
|
+
@preflight = true
|
|
33
|
+
@files.each { |file| validate_file(file) }
|
|
34
|
+
ids = []
|
|
35
|
+
@claims.each do |claim|
|
|
36
|
+
validate_claim(claim)
|
|
37
|
+
ids << claim.fetch("id")
|
|
38
|
+
end
|
|
39
|
+
require_value(ids.uniq.size == ids.size, "duplicate claim id")
|
|
40
|
+
verify_sources!
|
|
41
|
+
@preflight = false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def verify_sources!(targets = [])
|
|
45
|
+
@files.each do |file|
|
|
46
|
+
absolute = @paths.fetch(file.fetch("path"))
|
|
47
|
+
bytes = readable(absolute)
|
|
48
|
+
target = targets.find { |entry| entry.absolute == absolute }
|
|
49
|
+
require_value(bytes || (!@readable_paths.include?(absolute) && !target&.reader),
|
|
50
|
+
"source became unreadable during analysis: #{file['path']}")
|
|
51
|
+
@readable_paths << absolute if @preflight && bytes
|
|
52
|
+
next unless bytes
|
|
53
|
+
|
|
54
|
+
require_value(Lens.digest(bytes) == file.fetch("digest"), "stale claims digest: #{file['path']}")
|
|
55
|
+
if target&.reader
|
|
56
|
+
require_value(target.reader.digest == file.fetch("digest"),
|
|
57
|
+
"source changed during analysis: #{file['path']}")
|
|
58
|
+
end
|
|
59
|
+
verify_spans(file, bytes.bytesize)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def verify_spans(file, size)
|
|
66
|
+
@claims.select { |claim| claim.fetch("file") == file.fetch("path") }.each do |claim|
|
|
67
|
+
%w[declaration tag].each do |field|
|
|
68
|
+
require_value(claim.fetch(field).fetch("span").fetch("end_offset") <= size,
|
|
69
|
+
"#{field} span outside #{file['path']}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_header(data)
|
|
75
|
+
require_value(data.is_a?(Hash), "claims request must be an object")
|
|
76
|
+
require_value(data["schema"] == "lens-claims-request/v0", "unsupported claims schema")
|
|
77
|
+
require_value(data["revision"].is_a?(Integer) && data["revision"] == 1, "unsupported claims revision")
|
|
78
|
+
require_value(data["grammar"] == "rbs", "unsupported claims grammar")
|
|
79
|
+
%w[files claims].each { |field| require_value(data[field].is_a?(Array), "#{field} must be an array") }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def validate_file(file)
|
|
83
|
+
strings(file, %w[path digest])
|
|
84
|
+
path = file.fetch("path")
|
|
85
|
+
absolute = File.expand_path(path, @root)
|
|
86
|
+
relative = Pathname(absolute).relative_path_from(Pathname(@root)).to_s
|
|
87
|
+
require_value(path == relative, "file path must be root-relative: #{path}")
|
|
88
|
+
require_value(@arguments.include?(absolute), "claims file is not a target: #{path}")
|
|
89
|
+
require_value(!@paths.key?(path), "duplicate claims file: #{path}")
|
|
90
|
+
require_value(file.fetch("digest").match?(/\Axxh3-64:[0-9a-f]{16}\z/), "invalid claims file digest")
|
|
91
|
+
@paths[path] = absolute
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def validate_claim(claim)
|
|
95
|
+
strings(claim, %w[id file claim])
|
|
96
|
+
require_value(@paths.key?(claim.fetch("file")), "claim file is missing from files")
|
|
97
|
+
declaration = claim["declaration"]
|
|
98
|
+
strings(declaration, %w[kind owner scope name])
|
|
99
|
+
tag = claim["tag"]
|
|
100
|
+
strings(tag, %w[name])
|
|
101
|
+
require_value(tag.key?("subject") && (tag["subject"].nil? || tag["subject"].is_a?(String)),
|
|
102
|
+
"tag subject must be a string or null")
|
|
103
|
+
[declaration, tag].each { |entry| validate_span(entry["span"]) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_span(span)
|
|
107
|
+
require_value(span.is_a?(Hash), "span must be an object")
|
|
108
|
+
start = span["start_offset"]
|
|
109
|
+
finish = span["end_offset"]
|
|
110
|
+
require_value(start.is_a?(Integer) && finish.is_a?(Integer) && start >= 0 && finish >= start,
|
|
111
|
+
"invalid byte span")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def strings(object, fields)
|
|
115
|
+
require_value(object.is_a?(Hash), "claims entry must be an object")
|
|
116
|
+
fields.each { |field| require_value(object[field].is_a?(String), "#{field} must be a string") }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def require_value(condition, message)
|
|
120
|
+
raise IntegrityError, message unless condition
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def readable(path)
|
|
124
|
+
File.binread(path) if File.file?(path)
|
|
125
|
+
rescue SystemCallError
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
require_relative "../../rbs_extended"
|
|
5
|
+
require_relative "../../scope/resolution_chain"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Lens
|
|
9
|
+
class Claims
|
|
10
|
+
# Normalizes RBS ASTs without erasing unsupported forms or using display text as identity.
|
|
11
|
+
class TypeReader
|
|
12
|
+
Atom = Data.define(:kind, :value, :arguments, :relative) do
|
|
13
|
+
def identity
|
|
14
|
+
[kind, value, arguments.map { |arms| arms.to_set(&:identity) }]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def display
|
|
18
|
+
return value.inspect if kind == :literal
|
|
19
|
+
|
|
20
|
+
suffix = arguments.empty? ? "" : "[#{arguments.map { |arms| arms.map(&:display).join(' | ') }.join(', ')}]"
|
|
21
|
+
"::#{value}#{suffix}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :reasons
|
|
26
|
+
|
|
27
|
+
def self.parse(text)
|
|
28
|
+
# An empty, blank or comment-only text parses to nil rather than raising.
|
|
29
|
+
type = ::RBS::Parser.parse_type(text, require_eof: true)
|
|
30
|
+
type ? [type, nil] : [nil, "syntax_error"]
|
|
31
|
+
rescue ::RBS::ParsingError
|
|
32
|
+
extended = RbsExtended.parse_return_type_override("rigor:v1:return: #{text}")
|
|
33
|
+
[nil, extended ? "unsupported_grammar_feature" : "syntax_error"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# `owner` is the member's owner; an RBS declaration of the member sits in its namespace, which a builder
|
|
37
|
+
# block's lexical nesting does not name.
|
|
38
|
+
def initialize(scope:, nesting:, singleton_nesting: false, owner: nil)
|
|
39
|
+
@scope = scope
|
|
40
|
+
@loader = scope.environment.rbs_loader
|
|
41
|
+
@resolver = @loader.type_name_resolver
|
|
42
|
+
@nesting = nesting
|
|
43
|
+
@singleton_nesting = singleton_nesting
|
|
44
|
+
@context = rbs_context(Array(nesting))
|
|
45
|
+
@owner_context = owner && owner != "Object" ? rbs_context(owner_namespaces(owner)) : nil
|
|
46
|
+
@reasons = Set.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read(type, claim: false, aliases: Set.new)
|
|
50
|
+
case type
|
|
51
|
+
when ::RBS::Types::Bases::Any
|
|
52
|
+
@reasons << "dynamic_type"
|
|
53
|
+
[]
|
|
54
|
+
when ::RBS::Types::Bases::Bool then [literal(true), literal(false)]
|
|
55
|
+
when ::RBS::Types::Bases::Nil then [literal(nil)]
|
|
56
|
+
when ::RBS::Types::Literal then [literal(type.literal)]
|
|
57
|
+
when ::RBS::Types::Union then type.types.flat_map { |part| read(part, claim: claim, aliases: aliases) }
|
|
58
|
+
when ::RBS::Types::Optional then read(type.type, claim: claim, aliases: aliases) + [literal(nil)]
|
|
59
|
+
when ::RBS::Types::Alias then read_alias(type, claim, aliases)
|
|
60
|
+
when ::RBS::Types::ClassInstance then read_class(type, claim, aliases)
|
|
61
|
+
else
|
|
62
|
+
@reasons << "unsupported_type"
|
|
63
|
+
type.each_type { |part| read(part, claim: claim, aliases: aliases) }
|
|
64
|
+
[]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def rbs_context(names)
|
|
71
|
+
names.reduce(nil) do |outer, name|
|
|
72
|
+
type_name = ::RBS::TypeName.parse("::#{name}")
|
|
73
|
+
@loader.class_known?(name) ? [outer, type_name] : outer
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def owner_namespaces(owner)
|
|
78
|
+
segments = owner.split("::")
|
|
79
|
+
segments.each_index.map { |index| segments[0..index].join("::") }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def literal(value, relative: false)
|
|
83
|
+
Atom.new(kind: :literal, value: value, arguments: [], relative: relative)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def resolve(name, claim)
|
|
87
|
+
resolved = @resolver&.resolve(name, context: claim ? @context : nil)
|
|
88
|
+
if resolved.nil?
|
|
89
|
+
@reasons << "unresolved_name"
|
|
90
|
+
elsif claim && !name.absolute? && name.class?
|
|
91
|
+
compare_ruby_name(name, resolved)
|
|
92
|
+
end
|
|
93
|
+
resolved
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def compare_ruby_name(name, resolved)
|
|
97
|
+
# An unlocated alias body has no known cref. A singleton class's anonymous cref is not a
|
|
98
|
+
# named namespace either; its constant table and ancestry cannot be read as the owner's.
|
|
99
|
+
if @nesting.nil? || @singleton_nesting
|
|
100
|
+
@reasons << "ambiguous_name"
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
ruby = ruby_name(name.to_s)
|
|
105
|
+
@reasons << "ambiguous_name" if ruby && ruby != resolved.to_s.delete_prefix("::")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def read_class(type, claim, aliases)
|
|
109
|
+
name = resolve(type.name, claim)
|
|
110
|
+
arguments = type.args.map { |arg| read(arg, claim: claim, aliases: aliases) }
|
|
111
|
+
return [] unless name
|
|
112
|
+
|
|
113
|
+
key = name.to_s.delete_prefix("::")
|
|
114
|
+
if !@loader.class_known?(key) || arguments.size != @loader.class_type_param_names(key).size
|
|
115
|
+
@reasons << "unsupported_type"
|
|
116
|
+
end
|
|
117
|
+
singles = { "TrueClass" => true, "FalseClass" => false, "NilClass" => nil }
|
|
118
|
+
if arguments.empty? && singles.key?(key)
|
|
119
|
+
return [literal(singles.fetch(key), relative: claim && !type.name.absolute?)]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
[Atom.new(kind: :class, value: key, arguments: arguments, relative: claim && !type.name.absolute?)]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def read_alias(type, claim, aliases)
|
|
126
|
+
name = resolve(type.name, claim)
|
|
127
|
+
type.args.each { |arg| read(arg, claim: claim, aliases: aliases) }
|
|
128
|
+
return [] unless name
|
|
129
|
+
|
|
130
|
+
relative = claim && !type.name.absolute?
|
|
131
|
+
return [] if relative && !alias_name_settled?(type.name, name)
|
|
132
|
+
|
|
133
|
+
entry = @loader.type_alias_declaration(name)
|
|
134
|
+
if entry.nil? || !entry.type_params.empty? || !type.args.empty? || aliases.include?(name)
|
|
135
|
+
@reasons << "unsupported_type"
|
|
136
|
+
return []
|
|
137
|
+
end
|
|
138
|
+
# The resolved declaration binds names in the alias's namespace, not the claim's lexical scope. Arms
|
|
139
|
+
# reached through a relatively written name stay relative, so the conflict guard still applies.
|
|
140
|
+
arms = read(entry.type, aliases: aliases | [name])
|
|
141
|
+
relative ? arms.map { |arm| arm.with(relative: true) } : arms
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# A type alias has no Ruby meaning, so the only reading to cross-check is the RBS one: the claim's lexical
|
|
145
|
+
# context and the member's RBS namespace must resolve a relative alias name alike. An unlocated body or a
|
|
146
|
+
# singleton-class body has no context to read it in.
|
|
147
|
+
def alias_name_settled?(written, resolved)
|
|
148
|
+
if @nesting.nil? || @singleton_nesting
|
|
149
|
+
@reasons << "ambiguous_name"
|
|
150
|
+
return false
|
|
151
|
+
end
|
|
152
|
+
return true if @owner_context.nil? || @owner_context == @context
|
|
153
|
+
|
|
154
|
+
declared = @resolver&.resolve(written, context: @owner_context)
|
|
155
|
+
return true if declared == resolved
|
|
156
|
+
|
|
157
|
+
@reasons << "ambiguous_name"
|
|
158
|
+
false
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Project constants only: a missing entry falls back to RBS, which also knows libraries and gems.
|
|
162
|
+
# Resolve the first segment before the rest, so a Ruby-only namespace cannot fall through to core.
|
|
163
|
+
def ruby_name(written)
|
|
164
|
+
head, *tail = written.split("::")
|
|
165
|
+
found = project_constant_in(@nesting.reverse, head)
|
|
166
|
+
found ||= ancestor_constant(head)
|
|
167
|
+
found = head if found.nil? && (@nesting.empty? || @loader.rbs_module?(@nesting.last)) && project_name?(head)
|
|
168
|
+
found && ([found] + tail).join("::")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def project_constant_in(bases, name)
|
|
172
|
+
bases.each do |base|
|
|
173
|
+
candidate = base == "Object" ? name : "#{base}::#{name}"
|
|
174
|
+
return candidate if project_name?(candidate)
|
|
175
|
+
end
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def project_name?(name)
|
|
180
|
+
@scope.declared_class?(name) || @scope.bound_constant_names(name).include?(name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def ancestor_constant(name)
|
|
184
|
+
chain = Scope::ResolutionChain.for(@scope, @nesting.last || "Object", :instance, :constants)
|
|
185
|
+
found = chain_constant(chain, name)
|
|
186
|
+
settled = chain.settle(@scope, found) { |retro| chain_constant(retro, name) }
|
|
187
|
+
if chain.truncated? || chain.wildcard_mixin? || settled != :chain || opaque_ancestry?(chain)
|
|
188
|
+
@reasons << "ambiguous_name"
|
|
189
|
+
return nil
|
|
190
|
+
end
|
|
191
|
+
found
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# A superclass the tables cannot name (`Class.new(Base)`, `< pick`, a builder class's `Struct`/`Data`) or an
|
|
195
|
+
# external ancestor no RBS declares (`< SomeAlias`): the constants it holds are not on the chain.
|
|
196
|
+
def opaque_ancestry?(chain)
|
|
197
|
+
return true if chain.opaque_tail?(@scope)
|
|
198
|
+
|
|
199
|
+
chain.entries.any? { |entry| entry.external? && entry.candidates.none? { |name| @loader.class_known?(name) } }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def chain_constant(chain, name)
|
|
203
|
+
chain.search(@scope) do |entry|
|
|
204
|
+
bases = entry.external? ? external_ancestors(entry) : [entry.name]
|
|
205
|
+
project_constant_in(bases, name)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def external_ancestors(entry)
|
|
210
|
+
name = entry.candidates.find { |candidate| @loader.class_known?(candidate) }
|
|
211
|
+
name ? @loader.ancestor_names_for(name) : []
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|