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
|
@@ -344,19 +344,96 @@ module Rigor
|
|
|
344
344
|
when Rigor::Type::Singleton
|
|
345
345
|
chain_lookup(self_type.class_name, method_name, anchor_kind: :singleton, mixin_kind: :extend)
|
|
346
346
|
when Rigor::Type::Nominal
|
|
347
|
-
chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
347
|
+
chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
|
|
348
|
+
struct_prop_lookup(self_type.class_name, method_name, scope)
|
|
348
349
|
end
|
|
349
350
|
rescue StandardError
|
|
350
351
|
nil
|
|
351
352
|
end
|
|
352
353
|
|
|
354
|
+
# Issue #2541 — after the sig catalog (the class and its mixins) declines an instance-side call, a
|
|
355
|
+
# `prop` / `const` accessor the class or a superclass declares under a `T::Struct`-family root.
|
|
356
|
+
# The accessor's type is translated here rather than at harvest: a relative constant in it resolves the
|
|
357
|
+
# way Ruby's lexical lookup would from the declaring class body — under each enclosing namespace,
|
|
358
|
+
# innermost first, then at the top level — against the classes the walk declared and the RBS
|
|
359
|
+
# environment. A constant that resolves nowhere reads `Dynamic[top]`, never a guessed class.
|
|
360
|
+
def struct_prop_lookup(class_name, method_name, scope)
|
|
361
|
+
signature = @catalog.lookup_struct_prop(class_name: class_name, method_name: method_name)
|
|
362
|
+
return signature if signature.nil?
|
|
363
|
+
|
|
364
|
+
source, nesting = @catalog.struct_prop_type(signature)
|
|
365
|
+
return signature if source.nil?
|
|
366
|
+
|
|
367
|
+
signature.with(return_type: struct_prop_type(source, nesting, signature.class_name, scope))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def struct_prop_type(source, nesting, class_name, scope)
|
|
371
|
+
@struct_prop_types ||= {}
|
|
372
|
+
@struct_prop_types[[source, nesting, class_name, scope.nil?]] ||= begin
|
|
373
|
+
node = Prism.parse(source).value.statements&.body&.first
|
|
374
|
+
resolve = ->(name) { resolve_struct_constant(name, nesting, class_name, scope) }
|
|
375
|
+
TypeTranslator.translate(node, resolve)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Ruby's lookup for a relative constant in the class body: the lexical nesting, innermost first; then
|
|
380
|
+
# the class's ancestors; then the top level. The nesting step answers a class it finds, and declines
|
|
381
|
+
# (nil, so the constant reads `Dynamic[top]`) when it meets a constant that is not a declared class.
|
|
382
|
+
# Ancestry is not modelled precisely: a class or constant of that name under any ancestor declines.
|
|
383
|
+
# The top level answers only when the struct's ancestry is closed — no class on its walked superclass
|
|
384
|
+
# chain `include`s or `prepend`s anything, whose constants could shadow it — and no other walked
|
|
385
|
+
# `*::<name>` exists.
|
|
386
|
+
def resolve_struct_constant(name, nesting, class_name, scope)
|
|
387
|
+
environment = scope&.environment
|
|
388
|
+
return name.delete_prefix("::") if name.start_with?("::")
|
|
389
|
+
|
|
390
|
+
nesting.size.downto(1) do |depth|
|
|
391
|
+
candidate = "#{nesting.first(depth).join('::')}::#{name}"
|
|
392
|
+
return candidate if struct_constant_class?(candidate, environment)
|
|
393
|
+
return nil if struct_constant_bound?(candidate, scope)
|
|
394
|
+
end
|
|
395
|
+
@catalog.constant_ancestry(class_name).each do |ancestor|
|
|
396
|
+
candidate = "#{ancestor}::#{name}"
|
|
397
|
+
return nil if struct_constant_class?(candidate, environment) || struct_constant_bound?(candidate, scope)
|
|
398
|
+
end
|
|
399
|
+
return nil if struct_constant_elsewhere?(name)
|
|
400
|
+
return nil if @catalog.mixes_in_ancestry?(class_name)
|
|
401
|
+
|
|
402
|
+
struct_constant_class?(name, environment) ? name : nil
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def struct_constant_class?(name, environment)
|
|
406
|
+
@catalog.namespace?(name) || !environment&.nominal_for_name(name).nil?
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# A walked constant write, or any constant the engine types at the rooted path `::<name>` — an RBS
|
|
410
|
+
# constant declaration included. The probe goes through the plugin-facing `Scope#type_of` on a parsed
|
|
411
|
+
# constant-path node; a name nothing declares types as `Dynamic[top]` and is not bound.
|
|
412
|
+
def struct_constant_bound?(name, scope)
|
|
413
|
+
return true if @catalog.constant?(name)
|
|
414
|
+
return false if scope.nil?
|
|
415
|
+
|
|
416
|
+
node = Prism.parse("::#{name}").value.statements&.body&.first
|
|
417
|
+
type = scope.type_of(node)
|
|
418
|
+
!(type.nil? || type.is_a?(Rigor::Type::Dynamic))
|
|
419
|
+
rescue StandardError
|
|
420
|
+
true
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def struct_constant_elsewhere?(name)
|
|
424
|
+
@struct_constant_names ||= @catalog.declared_constant_names
|
|
425
|
+
suffix = "::#{name}"
|
|
426
|
+
@struct_constant_names.any? { |declared| declared.end_with?(suffix) }
|
|
427
|
+
end
|
|
428
|
+
|
|
353
429
|
def instance_chain_lookup(receiver_node, method_name, scope)
|
|
354
430
|
return nil if scope.nil?
|
|
355
431
|
|
|
356
432
|
receiver_type = scope.type_of(receiver_node)
|
|
357
433
|
return nil unless receiver_type.is_a?(Rigor::Type::Nominal)
|
|
358
434
|
|
|
359
|
-
chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
435
|
+
chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
|
|
436
|
+
struct_prop_lookup(receiver_type.class_name, method_name, scope)
|
|
360
437
|
rescue StandardError
|
|
361
438
|
# `scope.type_of` can raise on unrecognised synthetic nodes; degrade to "no contribution" rather
|
|
362
439
|
# than bubbling the failure into the dispatcher.
|
|
@@ -548,7 +625,7 @@ module Rigor
|
|
|
548
625
|
catalog
|
|
549
626
|
end
|
|
550
627
|
|
|
551
|
-
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
|
|
628
|
+
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path, struct_catalog: catalog)
|
|
552
629
|
# ADR-88 WD2 — store Marshal-clean `{kind:, line:, column:}` tuples, not the `ParseError` (it holds a
|
|
553
630
|
# live Prism node the producer bundle could not serialise). `diagnostics_for_file` rebuilds the
|
|
554
631
|
# diagnostic from the tuple, at the same 1-based line / `start_column + 1` position `from_node` gave.
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
require "rigor/
|
|
5
|
+
require "rigor/reflection"
|
|
6
6
|
|
|
7
7
|
require_relative "serializer_index"
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
11
|
class Typelizer < Rigor::Plugin::Base
|
|
12
|
-
# A
|
|
13
|
-
# one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
|
|
12
|
+
# A collector over one file, built on {Rigor::Reflection.class_declarations_in}: the classes it declares, and
|
|
13
|
+
# whether each one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
|
|
14
14
|
#
|
|
15
15
|
# A `module` that includes the DSL is deliberately not recorded: typelizer registers the module's own
|
|
16
16
|
# name and `target_serializers` then calls `.descendants` on it, which a plain Module lacks. A class that
|
|
@@ -22,8 +22,6 @@ module Rigor
|
|
|
22
22
|
# `included do include Typelizer::DSL end` and a `def` that includes it credit nothing to the lexical
|
|
23
23
|
# class. A `class << self` body is a different node and is not entered either.
|
|
24
24
|
class SerializerCollector
|
|
25
|
-
include Rigor::Inference::DeclarationWalk::Collector
|
|
26
|
-
|
|
27
25
|
DSL_MODULE = "Typelizer::DSL"
|
|
28
26
|
DSL_CALLS = %i[include extend].freeze
|
|
29
27
|
|
|
@@ -33,17 +31,10 @@ module Rigor
|
|
|
33
31
|
@dsl = {}
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
unless name.empty?
|
|
41
|
-
superclass = superclass_name(node.superclass)
|
|
42
|
-
@entries[name] = superclass if superclass || !@entries.key?(name)
|
|
43
|
-
@nestings[name] = context.nesting || []
|
|
44
|
-
@dsl[name] = true if dsl_in?(node.body) && !body_context.singleton_cref
|
|
45
|
-
end
|
|
46
|
-
Rigor::Inference::DeclarationWalk::DESCEND
|
|
34
|
+
# Walks `root` (a parsed file's `Prism::Node`) and records what it finds; answers the collector itself.
|
|
35
|
+
def collect(root)
|
|
36
|
+
Rigor::Reflection.class_declarations_in(root).each { |declaration| record(declaration) }
|
|
37
|
+
self
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
# The classes this file declares, as {SerializerIndex::ClassEntry} values.
|
|
@@ -58,6 +49,15 @@ module Rigor
|
|
|
58
49
|
|
|
59
50
|
private
|
|
60
51
|
|
|
52
|
+
def record(declaration)
|
|
53
|
+
name = declaration.name
|
|
54
|
+
node = declaration.node
|
|
55
|
+
superclass = superclass_name(node.superclass)
|
|
56
|
+
@entries[name] = superclass if superclass || !@entries.key?(name)
|
|
57
|
+
@nestings[name] = declaration.nesting
|
|
58
|
+
@dsl[name] = true if dsl_in?(node.body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
61
|
# Whether a statement reachable without entering a def, block, lambda or nested declaration is a
|
|
62
62
|
# receiverless `include` / `extend` of the DSL module.
|
|
63
63
|
def dsl_in?(node)
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
require "rigor/inference/declaration_walk"
|
|
6
|
-
|
|
7
5
|
require_relative "serializer_collector"
|
|
8
6
|
require_relative "serializer_index"
|
|
9
7
|
|
|
@@ -30,10 +28,7 @@ module Rigor
|
|
|
30
28
|
parsed = Prism.parse(contents)
|
|
31
29
|
next unless parsed.success?
|
|
32
30
|
|
|
33
|
-
collector = SerializerCollector.new
|
|
34
|
-
Rigor::Inference::DeclarationWalk.run(
|
|
35
|
-
parsed.value, [collector], Rigor::Inference::DeclarationWalk::Context.root(nesting: [])
|
|
36
|
-
)
|
|
31
|
+
collector = SerializerCollector.new.collect(parsed.value)
|
|
37
32
|
in_dirs = dir_files.include?(path)
|
|
38
33
|
entries.concat(collector.class_entries.map { |entry| entry.with(in_dirs: in_dirs) })
|
|
39
34
|
end
|
|
@@ -2,9 +2,8 @@ module Rigor
|
|
|
2
2
|
module Analysis
|
|
3
3
|
module CheckRules
|
|
4
4
|
def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
|
|
5
|
-
def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root) -> Hash[Symbol, untyped]
|
|
6
|
-
|
|
7
|
-
def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> untyped
|
|
5
|
+
def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root, ?plugin_api_surface: bool) -> Hash[Symbol, untyped]
|
|
6
|
+
def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver
|
|
8
7
|
def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
|
|
9
8
|
def self?.filter_suppressed: (Array[Diagnostic] diagnostics, comments: untyped, disabled_rules: Array[String]) -> Array[Diagnostic]
|
|
10
9
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Rigor::Cache::Descriptor::PluginEntry
|
|
2
|
+
def initialize: (id: untyped, version: untyped, ?config_hash: untyped) -> void
|
|
3
|
+
def to_h: () -> Hash[String, untyped]
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Rigor::Cache::Descriptor
|
|
7
|
+
def initialize: (?files: untyped, ?gems: untyped, ?plugins: untyped, ?configs: untyped, ?dependencies: untyped, ?globs: untyped) -> void
|
|
8
|
+
def self.compose: (*untyped) -> Rigor::Cache::Descriptor
|
|
9
|
+
def cache_key_for: (producer_id: untyped, ?params: untyped) -> String
|
|
10
|
+
def to_canonical_bytes: () -> String
|
|
11
|
+
def to_canonical_hash: () -> Hash[String, untyped]
|
|
12
|
+
def ==: (untyped) -> (false | true)
|
|
13
|
+
def hash: () -> Integer
|
|
14
|
+
end
|
data/sig/rigor/environment.rbs
CHANGED
|
@@ -19,7 +19,9 @@ module Rigor
|
|
|
19
19
|
def hkt_scan_failure: () -> [String, String, String?, Symbol]?
|
|
20
20
|
|
|
21
21
|
def self.default: () -> Environment
|
|
22
|
-
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> Environment
|
|
22
|
+
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?, ?eager: bool) -> Environment
|
|
23
|
+
# authored: the return is the `signature_paths:` argument's type (authored intent) or the default ["sig"]; sig-gen types the `||` fallback untyped.
|
|
24
|
+
def self.project_signature_root_candidates: (Array[String | _ToPath]? signature_paths) -> Array[String | _ToPath]
|
|
23
25
|
# authored: the return is the `signature_paths:` argument's type (authored intent) or the default [Pathname]; sig-gen types the `||` fallback untyped.
|
|
24
26
|
def self.project_signature_roots: (Array[String | _ToPath]? signature_paths, ?root: String) -> Array[String | _ToPath]
|
|
25
27
|
|
|
@@ -36,6 +38,7 @@ module Rigor
|
|
|
36
38
|
def rbs_module?: (String | Symbol name) -> bool
|
|
37
39
|
def singleton_extended_modules: (String | Symbol name) -> Array[String]
|
|
38
40
|
def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
|
|
41
|
+
def prepare_for_typing!: () -> Environment
|
|
39
42
|
def reflection: () -> untyped?
|
|
40
43
|
def missing_rbs_gem_owner: (String | Symbol root_constant_name) -> String?
|
|
41
44
|
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -4,9 +4,20 @@
|
|
|
4
4
|
# declared surface and give plugin subclasses (once they enter a check
|
|
5
5
|
# target) method resolution for the DSL + hook surface.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# ADR-128 WD2 — every hook position takes a plugin-facing interface
|
|
8
|
+
# (`sig/rigor/plugin/interfaces.rbs`), a concrete Prism node class, a
|
|
9
|
+
# `Rigor::Type::t` or a core type, instead of `untyped`. A hook's `scope`
|
|
10
|
+
# is non-nil where every engine call site passes one: the per-file hooks
|
|
11
|
+
# receive the seeded `Scope.empty(...)`, `MethodDispatcher#try_plugin_contribution`
|
|
12
|
+
# returns before consulting a plugin when its scope is nil, and
|
|
13
|
+
# `StatementEvaluator#apply_plugin_assertions` has already read its own. The
|
|
14
|
+
# existing `scope.nil?` guards in plugin source stay valid either way.
|
|
15
|
+
#
|
|
16
|
+
# Collaborators that have no RBS yet (FlowContribution) stay `untyped`;
|
|
17
|
+
# the precision ceiling is the sparse `sig/` tree. A parameter row stays
|
|
18
|
+
# lenient even where its type IS declared — `cache_for`'s `?descriptor:` and
|
|
19
|
+
# `IoBoundary#replay` both take a `Cache::Descriptor` and both read `untyped`
|
|
20
|
+
# — by ADR-107 clause 2, not for want of a declaration.
|
|
10
21
|
class Rigor::Plugin::Base
|
|
11
22
|
# --- class-level declaration DSLs ---------------------------------
|
|
12
23
|
|
|
@@ -17,16 +28,32 @@ class Rigor::Plugin::Base
|
|
|
17
28
|
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped, ?generation_cap: untyped) { (untyped params) -> untyped } -> Symbol
|
|
18
29
|
def self.producers: () -> Hash[Symbol, untyped]
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
# One overload per node class the bundled plugins name, so the block's `node` is that class; `Prism::Node` is the
|
|
32
|
+
# fallback for any other (RBS cannot write `singleton(N)` over a type variable). The block runs `instance_exec`'d
|
|
33
|
+
# on the plugin: `file_context` is whatever the `node_file_context` block returned (plugin-defined), and a rule
|
|
34
|
+
# may answer one diagnostic, an array of them, or nil.
|
|
35
|
+
def self.node_rule: (singleton(::Prism::CallNode) node_type) { (::Prism::CallNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::CallNode)
|
|
36
|
+
| (singleton(::Prism::ClassNode) node_type) { (::Prism::ClassNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ClassNode)
|
|
37
|
+
| (singleton(::Prism::ProgramNode) node_type) { (::Prism::ProgramNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ProgramNode)
|
|
38
|
+
| (singleton(::Prism::Node) node_type) { (::Prism::Node node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::Node)
|
|
21
39
|
def self.node_rules: () -> Array[untyped]
|
|
22
40
|
|
|
23
|
-
def self.node_file_context: () { (
|
|
24
|
-
def self.node_file_context_block: () -> (^(
|
|
41
|
+
def self.node_file_context: () { (::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped } -> void
|
|
42
|
+
def self.node_file_context_block: () -> (^(::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped)?
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
# A rule with a static `methods:` Array only ever runs its block on a `CallNode`: the name gate
|
|
45
|
+
# (`Registry#build_name_gates`, `Base#dynamic_return_rule_applies?`) matches on the call's name, which
|
|
46
|
+
# none of the other nodes in the union below has, so the gate declines them (#2681, #2764). Every other
|
|
47
|
+
# declaration shape is offered the whole union. A callable `methods:` also only ever sees a `CallNode`
|
|
48
|
+
# at runtime, since the same gate declines the nameless nodes, but RBS cannot tell a callable from an
|
|
49
|
+
# Array argument by overload alone, so it keeps the union, which is sound.
|
|
50
|
+
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, methods: Array[untyped], ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
|
|
51
|
+
| (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
|
|
27
52
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
53
|
|
|
29
|
-
|
|
54
|
+
# The block answers the facts that hold after the call returns normally, or nil. The element is a
|
|
55
|
+
# `Rigor::FlowContribution::Fact`, which has no RBS declaration yet, so it stays `untyped`.
|
|
56
|
+
def self.narrowing_facts: (methods: Array[untyped]) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Array[untyped]? } -> nil
|
|
30
57
|
# The engine-facing reader for the declared `narrowing_facts` rules. Named
|
|
31
58
|
# `type_specifiers` before ADR-80; that spelling was removed in 0.3.0.
|
|
32
59
|
def self.narrowing_facts_rules: () -> Array[untyped]
|
|
@@ -35,15 +62,16 @@ class Rigor::Plugin::Base
|
|
|
35
62
|
|
|
36
63
|
# --- instance surface --------------------------------------------
|
|
37
64
|
|
|
38
|
-
attr_reader services:
|
|
39
|
-
|
|
65
|
+
attr_reader services: Rigor::Plugin::_Services
|
|
66
|
+
# Schema-driven per plugin, so the values stay `untyped` (ADR-128 WD2).
|
|
67
|
+
attr_reader config: Hash[String, untyped]
|
|
40
68
|
|
|
41
|
-
def initialize: (services:
|
|
69
|
+
def initialize: (services: Rigor::Plugin::_Services, ?config: Hash[String, untyped]) -> void
|
|
42
70
|
|
|
43
71
|
# Overridable lifecycle / extension hooks (default no-ops).
|
|
44
|
-
def init: (
|
|
45
|
-
def prepare: (
|
|
46
|
-
def diagnostics_for_file: (path:
|
|
72
|
+
def init: (Rigor::Plugin::_Services services) -> nil
|
|
73
|
+
def prepare: (Rigor::Plugin::_Services services) -> nil
|
|
74
|
+
def diagnostics_for_file: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[Rigor::Analysis::Diagnostic]
|
|
47
75
|
# authored: hook-contract widening — the element type comes from a plugin gem sig-gen never
|
|
48
76
|
# sees, so no engine work would let it answer; the declared `Array[Rigor::Plugin::TemplateUnit]`
|
|
49
77
|
# over a bare-`[]` default body is the author's contract.
|
|
@@ -54,12 +82,19 @@ class Rigor::Plugin::Base
|
|
|
54
82
|
def declared_members: (untyped class_name) -> Array[untyped]
|
|
55
83
|
|
|
56
84
|
# Engine-executed dispatch over the declared DSLs.
|
|
57
|
-
def node_rule_diagnostics: (path:
|
|
85
|
+
def node_rule_diagnostics: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[untyped]
|
|
58
86
|
# sig-gen gap: #1007 — the true `return result if result` branch (base.rb:594) flows through an
|
|
59
87
|
# `instance_exec`'d, plugin-supplied Proc that sig-gen's inference does not fold into the return
|
|
60
88
|
# join, so it proposes the literal fallback `nil` instead of the honest contract.
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
# `call_node` is a `CallNode` except where the engine dispatches a compound write or a block argument. The
|
|
90
|
+
# compound writes (`x.attr op= v`, `x.attr ||= v`, `x.attr &&= v`, and the `x[i]` forms) reach a rule through
|
|
91
|
+
# `ExpressionTyper#attribute_compound_read_type`, `#call_operator_write_type_for` and `#index_read_dispatch`;
|
|
92
|
+
# the `&:symbol` block arguments through `ExpressionTyper#symbol_block_return_type` and `#apply_hash_block`.
|
|
93
|
+
# A rule the name gate does not screen out (a `receivers:`-only rule, for one) is offered those nodes, so the
|
|
94
|
+
# `dynamic_return` block union above is the full set. A rule that reads a `CallNode`-only member (`receiver`,
|
|
95
|
+
# `name`, `arguments`) must guard with `call_node.is_a?(::Prism::CallNode)` first (#2717).
|
|
96
|
+
def dynamic_return_type: (call_node: ::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode, scope: Rigor::Plugin::_Scope, receiver_type: Rigor::Type::t) -> Rigor::Type::t?
|
|
97
|
+
def narrowing_facts_for: (call_node: ::Prism::CallNode, scope: Rigor::Plugin::_Scope) -> Array[untyped]
|
|
63
98
|
|
|
64
99
|
# Authoring helpers.
|
|
65
100
|
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> Rigor::Analysis::Diagnostic
|
|
@@ -84,6 +119,5 @@ class Rigor::Plugin::Base
|
|
|
84
119
|
def effect_ancestry: () -> Array[Rigor::Plugin::EffectAncestry]
|
|
85
120
|
def io_boundary: () -> Rigor::Plugin::IoBoundary
|
|
86
121
|
def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> Proc
|
|
87
|
-
|
|
88
|
-
def plugin_entry: () -> untyped
|
|
122
|
+
def plugin_entry: () -> Rigor::Cache::Descriptor::PluginEntry
|
|
89
123
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# ADR-128 — the plugin-facing structural interfaces: what a hook may call on the engine objects it receives.
|
|
2
|
+
#
|
|
3
|
+
# The criterion (ADR-128): a method is public plugin API only if it is declared on `Plugin::Base`, on one of the
|
|
4
|
+
# interfaces below, or in a namespace `docs/internal-spec/public-api.md` lists as public. Being reachable at
|
|
5
|
+
# runtime does not make a method public. `spec/rigor/plugin_boundary_spec.rb` gates the bundled plugins against
|
|
6
|
+
# these member lists, so adding a member here is an API promise, and `public_api_drift_spec.rb` pins the sets.
|
|
7
|
+
#
|
|
8
|
+
# Each interface is backed by a concrete engine class (the `BACKING` table in `spec/support/plugin_boundary.rb`).
|
|
9
|
+
# Engine signatures keep those concrete classes (ADR-128 WD5); an interface appears only at a plugin-facing
|
|
10
|
+
# position, in `base.rbs`.
|
|
11
|
+
module Rigor
|
|
12
|
+
module Plugin
|
|
13
|
+
# The scope a hook receives. Backed by `Rigor::Scope`, which has far more members (about 200); only these are
|
|
14
|
+
# promised. `environment` is non-nil, matching `Scope#environment`.
|
|
15
|
+
interface _Scope
|
|
16
|
+
def type_of: (::Prism::Node node) -> Rigor::Type::t
|
|
17
|
+
def self_type: () -> Rigor::Type::t?
|
|
18
|
+
def local: (String | Symbol name) -> Rigor::Type::t?
|
|
19
|
+
def source_path: () -> String?
|
|
20
|
+
def with_local: (String | Symbol name, Rigor::Type::t type) -> Rigor::Plugin::_Scope
|
|
21
|
+
def environment: () -> Rigor::Plugin::_Environment
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The project's RBS and class-registry handle, backed by `Rigor::Environment`.
|
|
25
|
+
interface _Environment
|
|
26
|
+
def nominal_for_name: (String | Symbol name) -> Rigor::Type::Nominal?
|
|
27
|
+
def rbs_module?: (String | Symbol name) -> bool
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The services container `Plugin::Base#init` / `#prepare` receive, backed by `Rigor::Plugin::Services`.
|
|
31
|
+
# `reflection` and `type` are public namespaces (`Rigor::Reflection`, `Rigor::Type::Combinator`) that a plugin
|
|
32
|
+
# also reaches by constant, so they are typed as those modules rather than narrowed to an interface.
|
|
33
|
+
interface _Services
|
|
34
|
+
def fact_store: () -> Rigor::Plugin::_FactStore
|
|
35
|
+
def configuration: () -> Rigor::Plugin::_Configuration
|
|
36
|
+
def reflection: () -> singleton(Rigor::Reflection)
|
|
37
|
+
def type: () -> singleton(Rigor::Type::Combinator)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The per-run cross-plugin fact store, backed by `Rigor::Plugin::FactStore`.
|
|
41
|
+
interface _FactStore
|
|
42
|
+
def publish: (plugin_id: String | Symbol, name: Symbol, value: untyped) -> nil
|
|
43
|
+
def read: (plugin_id: String | Symbol, name: Symbol) -> untyped
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The project configuration, backed by `Rigor::Configuration`.
|
|
47
|
+
interface _Configuration
|
|
48
|
+
def paths: () -> Array[String]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -14,8 +14,7 @@ class Rigor::Plugin::IoBoundary
|
|
|
14
14
|
# whose parameter is undeclared.
|
|
15
15
|
def refusal_summary: () -> { count: Integer, first_path: String?, nearest_root: String? }?
|
|
16
16
|
def open_url: (untyped url) -> String
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
def cache_descriptor: () -> untyped
|
|
17
|
+
# The returned descriptor is frozen.
|
|
18
|
+
def cache_descriptor: () -> Rigor::Cache::Descriptor
|
|
20
19
|
def replay: (untyped descriptor) -> nil
|
|
21
20
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Rigor
|
|
2
|
+
module Plugin
|
|
3
|
+
# ADR-37 slice 1d — the lexical context of a node, the fifth argument of a `node_rule` block.
|
|
4
|
+
class NodeContext
|
|
5
|
+
EMPTY: nil
|
|
6
|
+
|
|
7
|
+
attr_reader ancestors: Array[::Prism::Node]
|
|
8
|
+
|
|
9
|
+
def initialize: (Array[::Prism::Node] ancestors) -> void
|
|
10
|
+
# The three finders return the innermost ancestor of a given kind: a `DefNode`, a `ClassNode` or
|
|
11
|
+
# `ModuleNode`, and a `CallNode` respectively. `enclosing_def` is sig-gen's proposal (#2719, #2914); the other
|
|
12
|
+
# two blocks (`||`, `&&`) are not a single class test, so their bodies still infer `Prism::Node?`. A narrower
|
|
13
|
+
# declaration would be an unmarked sig-gen divergence (ADR-107).
|
|
14
|
+
# Known Steep 2.0.0 limitation (surfaced by `library "prism"`, #2718): it does not narrow `rfind` through an
|
|
15
|
+
# `is_a?` block, so it warns that the body is `Prism::Node?`; the declaration keeps the precise shape.
|
|
16
|
+
def enclosing_def: () -> (Prism::DefNode | nil)
|
|
17
|
+
def enclosing_module: () -> ::Prism::Node?
|
|
18
|
+
def enclosing_block: (Symbol method_name) -> ::Prism::Node?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/sig/rigor/reflection.rbs
CHANGED
|
@@ -19,5 +19,42 @@ module Rigor
|
|
|
19
19
|
def self?.class_type_param_names: (String | Symbol class_name, ?scope: Scope) -> Array[Symbol]
|
|
20
20
|
def self?.discovered_class?: (String | Symbol class_name, ?scope: Scope) -> bool
|
|
21
21
|
def self?.discovered_method?: (String | Symbol class_name, String | Symbol method_name, ?kind: Symbol, ?scope: Scope) -> bool
|
|
22
|
+
def self?.project_method_defined: (String | Symbol? class_name, String | Symbol method_name, kind: :instance | :singleton, source: :def | :discovered | :either, scope: Scope) -> Trinary
|
|
23
|
+
def self?.rbs_method_defined: (String | Symbol? class_name, String | Symbol method_name, kind: :instance | :singleton, scope: Scope) -> Trinary
|
|
24
|
+
def self?.ancestry_reaches: (String | Symbol? class_name, Array[String | Symbol] ancestor_names, kind: :instance | :singleton, scope: Scope) -> Trinary
|
|
25
|
+
# sig-gen gap: #2302 — the collector's `@classes = []` is grown by `<<`, so sig-gen reads the array as `Array[untyped]`.
|
|
26
|
+
def self?.class_declarations_in: (Prism::Node root) ?{ (DeclaredCall call) -> void } -> Array[ClassDeclaration]
|
|
27
|
+
|
|
28
|
+
# One `class` header of a parsed file, as `class_declarations_in` reports it.
|
|
29
|
+
class ClassDeclaration < Data
|
|
30
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so sig-gen proposes `untyped`; the shape is hand-written.
|
|
31
|
+
attr_reader name: String
|
|
32
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so sig-gen proposes `untyped`; the shape is hand-written.
|
|
33
|
+
attr_reader nesting: Array[String]
|
|
34
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so sig-gen proposes `untyped`; the shape is hand-written.
|
|
35
|
+
attr_reader node: Prism::ClassNode
|
|
36
|
+
|
|
37
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so the constructor's keywords are `untyped` in sig-gen.
|
|
38
|
+
def self.new: (name: String, nesting: Array[String], node: Prism::ClassNode) -> instance
|
|
39
|
+
| (String name, Array[String] nesting, Prism::ClassNode node) -> instance
|
|
40
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so the constructor's keywords are `untyped` in sig-gen.
|
|
41
|
+
def self.[]: (name: String, nesting: Array[String], node: Prism::ClassNode) -> instance
|
|
42
|
+
| (String name, Array[String] nesting, Prism::ClassNode node) -> instance
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# One call `class_declarations_in` yields: the `Prism::CallNode` and the class or module it lexically sits in.
|
|
46
|
+
class DeclaredCall < Data
|
|
47
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so sig-gen proposes `untyped`; the shape is hand-written.
|
|
48
|
+
attr_reader node: Prism::CallNode
|
|
49
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so sig-gen proposes `untyped`; the shape is hand-written.
|
|
50
|
+
attr_reader owner: String?
|
|
51
|
+
|
|
52
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so the constructor's keywords are `untyped` in sig-gen.
|
|
53
|
+
def self.new: (node: Prism::CallNode, owner: String?) -> instance
|
|
54
|
+
| (Prism::CallNode node, String? owner) -> instance
|
|
55
|
+
# sig-gen gap: #1150 — `Data.define` members carry no type, so the constructor's keywords are `untyped` in sig-gen.
|
|
56
|
+
def self.[]: (node: Prism::CallNode, owner: String?) -> instance
|
|
57
|
+
| (Prism::CallNode node, String? owner) -> instance
|
|
58
|
+
end
|
|
22
59
|
end
|
|
23
60
|
end
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -30,7 +30,7 @@ module Rigor
|
|
|
30
30
|
attr_reader struct_fold_safe_locals: Set[Symbol]
|
|
31
31
|
attr_reader opaque_block_self: bool
|
|
32
32
|
# Issue #1759 — `:refinement` (REFINEMENT_BLOCK_SELF) directly in a `refine X do … end` block body.
|
|
33
|
-
attr_reader block_self_unknown: bool | :refinement
|
|
33
|
+
attr_reader block_self_unknown: bool | :refinement | :preserved
|
|
34
34
|
attr_reader singleton_class_body: bool
|
|
35
35
|
attr_reader lexical_nesting: Array[String]?
|
|
36
36
|
# Issue #1429 — a guard's narrowing of a constant reference, keyed by its spelling.
|
|
@@ -100,6 +100,12 @@ module Rigor
|
|
|
100
100
|
def run_generation: () -> Object?
|
|
101
101
|
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
102
102
|
def record_optimistic_origin: (untyped node, Symbol cause) -> Scope
|
|
103
|
+
# sig-gen gap: #2840 — the reader returns through a sentinel `equal?` guard over `Hash#fetch`; sig-gen emits nothing.
|
|
104
|
+
def dynamic_origin: (untyped node) -> Symbol?
|
|
105
|
+
# sig-gen gap: #2840 — the reader returns through a sentinel `equal?` guard over `Hash#fetch`; sig-gen emits nothing.
|
|
106
|
+
def optimistic_origin: (untyped node) -> Symbol?
|
|
107
|
+
# sig-gen gap: #2840 — the reader returns through a sentinel `equal?` guard over `Hash#fetch`; sig-gen emits nothing.
|
|
108
|
+
def void_origin: (untyped node) -> Inference::VoidOrigin?
|
|
103
109
|
|
|
104
110
|
def record_void_origin: (untyped node, Inference::VoidOrigin origin) -> Scope
|
|
105
111
|
|
|
@@ -219,12 +225,16 @@ module Rigor
|
|
|
219
225
|
|
|
220
226
|
def self.empty: (?environment: Environment, ?source_path: String?) -> Scope
|
|
221
227
|
|
|
222
|
-
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?, ?opaque_block_self: bool, ?block_self_unknown: bool | :refinement, ?singleton_class_body: bool, ?dynamic_origins: Hash[untyped, Symbol], ?indexed_narrowings: Hash[IndexedKey, Type::t], ?method_chain_narrowings: Hash[ChainKey, Type::t], ?declaration_sourced: Set[[Symbol, Symbol] | [Symbol, Symbol, Symbol]], ?published_constant_sourced: Set[[Symbol, Symbol]], ?struct_fold_safe_locals: Set[Symbol], ?lexical_nesting: Array[String]?, ?local_origins: Hash[Symbol, Symbol], ?ivar_origins: Hash[Symbol, Symbol], ?void_origins: Hash[untyped, Inference::VoidOrigin], ?plugin_typed_calls: Hash[untyped, bool], ?optimistic_origins: Hash[untyped, Symbol], ?optimistic_locals: Hash[Symbol, Symbol | Inference::OptimisticOrigin::BoundMark], ?optimistic_ivars: Hash[Symbol, Symbol | Inference::OptimisticOrigin::BoundMark], ?construction_facts: Hash[Symbol, Set[Symbol]], ?repeated_or_writes: Hash[untyped, bool], ?match_frame: untyped, ?constant_narrowings: Hash[String, Type::t], ?guard_records: Hash[[Symbol, Symbol | String], Type::t], ?bot_guard_classes: Hash[[Symbol, Symbol | String], Array[String]], ?declared_refinements: Array[String | Symbol]) -> void
|
|
228
|
+
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?, ?opaque_block_self: bool, ?block_self_unknown: bool | :refinement | :preserved, ?singleton_class_body: bool, ?dynamic_origins: Hash[untyped, Symbol], ?indexed_narrowings: Hash[IndexedKey, Type::t], ?method_chain_narrowings: Hash[ChainKey, Type::t], ?declaration_sourced: Set[[Symbol, Symbol] | [Symbol, Symbol, Symbol]], ?published_constant_sourced: Set[[Symbol, Symbol]], ?struct_fold_safe_locals: Set[Symbol], ?lexical_nesting: Array[String]?, ?local_origins: Hash[Symbol, Symbol], ?ivar_origins: Hash[Symbol, Symbol], ?void_origins: Hash[untyped, Inference::VoidOrigin], ?plugin_typed_calls: Hash[untyped, bool], ?optimistic_origins: Hash[untyped, Symbol], ?optimistic_locals: Hash[Symbol, Symbol | Inference::OptimisticOrigin::BoundMark], ?optimistic_ivars: Hash[Symbol, Symbol | Inference::OptimisticOrigin::BoundMark], ?construction_facts: Hash[Symbol, Set[Symbol]], ?repeated_or_writes: Hash[untyped, bool], ?match_frame: untyped, ?constant_narrowings: Hash[String, Type::t], ?guard_records: Hash[[Symbol, Symbol | String], Type::t], ?bot_guard_classes: Hash[[Symbol, Symbol | String], Array[String]], ?declared_refinements: Array[String | Symbol]) -> void
|
|
223
229
|
def with_source_path: (String? path) -> Scope
|
|
224
230
|
def with_struct_fold_safe: (Set[Symbol] locals) -> Scope
|
|
225
231
|
def with_lexical_nesting: (Array[String]? chain) -> Scope
|
|
226
232
|
def struct_fold_safe?: (String | Symbol name) -> bool
|
|
233
|
+
def entering_block_without_gradual_marks: (Prism::Node block_node) -> Scope
|
|
227
234
|
def entering_opaque_block: () -> Scope
|
|
235
|
+
PRESERVED_BLOCK_SELF: :preserved
|
|
236
|
+
def entering_self_preserving_block: () -> Scope
|
|
237
|
+
def walked_block_self_unknown?: () -> bool
|
|
228
238
|
def block_self_unknown?: () -> bool
|
|
229
239
|
REFINEMENT_BLOCK_SELF: :refinement
|
|
230
240
|
def refinement_block_self?: () -> bool
|
|
@@ -297,6 +307,9 @@ module Rigor
|
|
|
297
307
|
def with_inferred_param_mark: (String | Symbol name) -> Scope
|
|
298
308
|
def without_inferred_param_mark: (String | Symbol name) -> Scope
|
|
299
309
|
def inferred_param?: (String | Symbol name) -> bool
|
|
310
|
+
def with_gradual_narrowing_mark: (String | Symbol name) -> Scope
|
|
311
|
+
def gradual_narrowing?: (String | Symbol name) -> bool
|
|
312
|
+
def gradual_narrowing_names: () -> Array[Symbol]
|
|
300
313
|
def forget_match_globals: () -> Scope
|
|
301
314
|
def untyped_match_globals: () -> Scope
|
|
302
315
|
def match_globals_bound?: () -> bool
|
|
@@ -321,8 +334,13 @@ module Rigor
|
|
|
321
334
|
def def_node_slot: (untyped class_name, untyped method_name, ?Symbol kind) -> untyped
|
|
322
335
|
def user_def_for: (String | Symbol class_name, String | Symbol method_name) -> ::Prism::DefNode?
|
|
323
336
|
def user_def_through_ancestors: (String | Symbol class_name, String | Symbol method_name, ?name_memo: Hash[untyped, untyped]) -> ([::Prism::DefNode, String] | [nil, nil])
|
|
337
|
+
# Returns `[opaque_flag, recorder_keys]`. Steep 2.0.0 cannot verify a tuple through the
|
|
338
|
+
# `Array#freeze` on the returned literal and reports a MethodBodyTypeMismatch warning; the
|
|
339
|
+
# declaration keeps the precise shape because `rigor sig-gen` proves it.
|
|
324
340
|
def subclassing_opacity: () -> [false | true, Array[untyped]]
|
|
325
|
-
|
|
341
|
+
# authored: each owner is a frozen `[class_name, open_to_override]` pair. sig-gen types the lazily built
|
|
342
|
+
# `(owners ||= []) <<` array as `Array[untyped]`; the body keeps it to avoid `Hash#filter_map`'s per-entry pair.
|
|
343
|
+
def user_instance_def_owners: (untyped) -> Array[Array[untyped]]
|
|
326
344
|
def discovered_method_through_ancestors?: (String | Symbol? class_name, String | Symbol method_name, Symbol kind, ?name_memo: Hash[untyped, untyped]) -> bool
|
|
327
345
|
def external_ancestor_name_candidates: (String | Symbol class_name, ?name_memo: Hash[untyped, untyped]) -> Array[Array[String]]
|
|
328
346
|
def enqueue_ancestors: (String current, Array[String] queue, Hash[untyped, untyped] name_memo, ?mixins: bool) -> void
|
|
@@ -357,6 +375,7 @@ module Rigor
|
|
|
357
375
|
def data_member_layout: (String | Symbol class_name) -> Array[Symbol]?
|
|
358
376
|
def struct_member_layout: (String | Symbol class_name) -> { members: Array[Symbol], keyword_init: bool }?
|
|
359
377
|
def includes_of: (String | Symbol class_name) -> Array[String]
|
|
378
|
+
def prepends_of: (String | Symbol class_name) -> Array[String]
|
|
360
379
|
# sig-gen gap: #2302 — `names = []` grown by `concat` reads `Array[untyped]`, so the element type is
|
|
361
380
|
# hand-written, matching {#includes_of}.
|
|
362
381
|
def singleton_extends_of: (String | Symbol class_name) -> Array[String]
|
data/sig/rigor/trinary.rbs
CHANGED
data/sig/rigor/type.rbs
CHANGED
|
@@ -56,8 +56,8 @@ module Rigor
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
class Dynamic
|
|
59
|
-
attr_reader static_facet: Type::t
|
|
60
|
-
def initialize: (Type::t static_facet) -> void
|
|
59
|
+
attr_reader static_facet: Type::t | Interface
|
|
60
|
+
def initialize: (Type::t | Interface static_facet) -> void
|
|
61
61
|
def describe: (?Symbol verbosity) -> String
|
|
62
62
|
def erase_to_rbs: () -> "untyped"
|
|
63
63
|
def top: () -> Trinary
|
|
@@ -169,6 +169,11 @@ module Rigor
|
|
|
169
169
|
def inspect: () -> String
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
+
# ADR-130 — a `Rigor::Plugin` interface, carried only as a `Dynamic`'s facet (`Dynamic[Rigor::Plugin::_Scope]`), so
|
|
173
|
+
# it is not a member of `Type::t`. Declared for that name only; its members are read from the source.
|
|
174
|
+
class Interface
|
|
175
|
+
end
|
|
176
|
+
|
|
172
177
|
class Union
|
|
173
178
|
attr_reader members: Array[Type::t]
|
|
174
179
|
def initialize: (Array[Type::t] members) -> void
|
|
@@ -446,7 +451,8 @@ module Rigor
|
|
|
446
451
|
def self?.top: () -> Top
|
|
447
452
|
def self?.bot: () -> Bot
|
|
448
453
|
def self?.untyped: () -> Dynamic
|
|
449
|
-
def self?.
|
|
454
|
+
def self?.untyped?: (Type::t? type) -> bool
|
|
455
|
+
def self?.dynamic: (Type::t | Interface static_facet) -> Dynamic
|
|
450
456
|
def self?.nominal_of: (Module | String class_name_or_object, ?type_args: Array[Type::t]) -> Nominal
|
|
451
457
|
def self?.singleton_of: (Module | String class_name_or_object) -> Singleton
|
|
452
458
|
def self?.constant_of: (untyped value) -> Constant
|
data/sig/rigor.rbs
CHANGED
|
@@ -165,8 +165,9 @@ module Rigor
|
|
|
165
165
|
# sig-gen gap: #1008 — `result` is populated by `collect_return_summaries` mutating it by
|
|
166
166
|
# reference from a sibling method; sig-gen folds only the `{}` seed and the empty-memo early
|
|
167
167
|
# return, so it proposes the seed shape instead of the honest, usually-non-empty contract.
|
|
168
|
-
# Keys are `[path, "Class#method"]` pairs; values are `{ keys:, returns:, effects: }` bundles
|
|
169
|
-
def
|
|
168
|
+
# Keys are `[path, "Class#method"]` pairs; values are `{ keys:, returns:, effects: }` bundles, or
|
|
169
|
+
# nil for a def whose keys the run cannot list. nil for a pool run, which harvests none (#3160).
|
|
170
|
+
def return_summaries: () -> Hash[[String, String], Hash[Symbol, untyped]?]?
|
|
170
171
|
# ADR-67 — `[class_name, method_name, kind] => { param_name => Type }`.
|
|
171
172
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
172
173
|
def collect_param_inference_table: (untyped files) -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|