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
|
@@ -286,8 +286,8 @@ module Rigor
|
|
|
286
286
|
# project class, which says nothing about who wrote `==`.
|
|
287
287
|
def project_defines?(class_name, operator, scope)
|
|
288
288
|
OPERATOR_OWNER_CHAINS.fetch([class_name, operator]).any? do |name|
|
|
289
|
-
!
|
|
290
|
-
|
|
289
|
+
!Rigor::Reflection.project_method_defined(name, operator, kind: :instance, source: :either,
|
|
290
|
+
scope: scope).no?
|
|
291
291
|
end
|
|
292
292
|
end
|
|
293
293
|
|
|
@@ -390,8 +390,8 @@ module Rigor
|
|
|
390
390
|
# `def`, an included module, or a `define_method`.
|
|
391
391
|
def project_defines_on_chain?(class_name, method_name, scope)
|
|
392
392
|
CORE_ANCESTOR_CHAINS.fetch(class_name, [class_name]).any? do |name|
|
|
393
|
-
!
|
|
394
|
-
|
|
393
|
+
!Rigor::Reflection.project_method_defined(name, method_name, kind: :instance, source: :either,
|
|
394
|
+
scope: scope).no?
|
|
395
395
|
end
|
|
396
396
|
end
|
|
397
397
|
|
|
@@ -15,11 +15,32 @@ module Rigor
|
|
|
15
15
|
# Hash on every `mixins_for` query.
|
|
16
16
|
EMPTY_MIXINS = { include: [].freeze, extend: [].freeze }.freeze
|
|
17
17
|
|
|
18
|
+
# Issue #2541 — the sorbet-runtime struct roots whose `prop` / `const` calls define accessors
|
|
19
|
+
# (`T::Props::ClassMethods#prop` → `T::Props::Decorator#add_prop_definition` defines the reader,
|
|
20
|
+
# and the writer for `prop` only; `const` is `prop(..., immutable: true)`). `T::ImmutableStruct`
|
|
21
|
+
# overrides `prop` to raise, so a writer is never answered under it.
|
|
22
|
+
STRUCT_ROOTS = %w[T::Struct T::InexactStruct T::ImmutableStruct].freeze
|
|
23
|
+
IMMUTABLE_STRUCT_ROOT = "T::ImmutableStruct"
|
|
24
|
+
|
|
18
25
|
def initialize
|
|
19
26
|
@entries = {}
|
|
20
27
|
# ADR-11 slice 8 — per-class mixin declarations collected by `CatalogWalker`. Lookup-time chain
|
|
21
28
|
# traversal lifts sigs declared on a mixed-in module to call sites on the host class.
|
|
22
29
|
@mixins = {}
|
|
30
|
+
# Issue #2541 — `prop` / `const` accessors recorded per declaring class, the lexical superclass of
|
|
31
|
+
# every `class X < Y` the walker saw, and the instance methods each class body `def`s itself. A
|
|
32
|
+
# struct accessor answers only when the declaring class reaches a {STRUCT_ROOTS} root through
|
|
33
|
+
# `@superclasses`, and a class's own `def` of the same name shadows it (Ruby: the later `def`
|
|
34
|
+
# replaces the generated accessor; a subclass `def` overrides it).
|
|
35
|
+
@struct_props = {}
|
|
36
|
+
@struct_prop_types = {}
|
|
37
|
+
@struct_nesting = {}
|
|
38
|
+
@namespaces = {}
|
|
39
|
+
@constants = {}
|
|
40
|
+
@struct_includes = {}
|
|
41
|
+
@mixes_in = {}
|
|
42
|
+
@superclasses = {}
|
|
43
|
+
@defined_methods = {}
|
|
23
44
|
@frozen_after_build = false
|
|
24
45
|
end
|
|
25
46
|
|
|
@@ -42,8 +63,153 @@ module Rigor
|
|
|
42
63
|
list << module_name unless list.include?(module_name)
|
|
43
64
|
end
|
|
44
65
|
|
|
66
|
+
# @param signature — a {MethodSignature} for a `prop` / `const` reader or a `prop` writer.
|
|
67
|
+
# @param certain — false for a call nested under a conditional or a block, which never displaces
|
|
68
|
+
# an accessor already recorded for the name; a certain one replaces whatever is there.
|
|
69
|
+
# @param type_source — the source text of the macro's type argument, re-translated at lookup with
|
|
70
|
+
# its constants resolved against `nesting` ({#struct_prop_type}); nil when the accessor is untyped.
|
|
71
|
+
# @param nesting — the declaring class's lexical nesting, outermost first, its own name last.
|
|
72
|
+
def record_struct_prop(signature, certain: true, type_source: nil, nesting: [])
|
|
73
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
74
|
+
|
|
75
|
+
props = (@struct_props[signature.class_name] ||= {})
|
|
76
|
+
return unless certain || !props.key?(signature.method_name)
|
|
77
|
+
|
|
78
|
+
props[signature.method_name] = signature
|
|
79
|
+
@struct_prop_types[[signature.class_name, signature.method_name]] = type_source
|
|
80
|
+
@struct_nesting[signature.class_name] ||= nesting.dup.freeze
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Issue #2541 — every class and module name the walk declared, so a struct accessor's relative
|
|
84
|
+
# constant resolves to a project class the RBS environment may not know.
|
|
85
|
+
def record_namespace(name)
|
|
86
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
87
|
+
|
|
88
|
+
@namespaces[name] = true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def namespace?(name)
|
|
92
|
+
@namespaces.key?(name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A constant the walk saw assigned (`Thing = Other`) rather than declared as a class or module.
|
|
96
|
+
def record_constant(name)
|
|
97
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
98
|
+
|
|
99
|
+
@constants[name] = true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def constant?(name)
|
|
103
|
+
@constants.key?(name)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def record_struct_include(class_name, module_name)
|
|
107
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
108
|
+
|
|
109
|
+
list = (@struct_includes[class_name] ||= [])
|
|
110
|
+
list << module_name unless list.include?(module_name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def record_mixes_in(class_name)
|
|
114
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
115
|
+
|
|
116
|
+
@mixes_in[class_name] = true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Issue #2541 — whether any class on `class_name`'s walked superclass chain, itself included,
|
|
120
|
+
# `include`s or `prepend`s a module.
|
|
121
|
+
def mixes_in_ancestry?(class_name)
|
|
122
|
+
chain = []
|
|
123
|
+
current = class_name.to_s.delete_prefix("::")
|
|
124
|
+
until current.nil? || chain.include?(current) || STRUCT_ROOTS.include?(current)
|
|
125
|
+
return true if @mixes_in.key?(current)
|
|
126
|
+
|
|
127
|
+
chain << current
|
|
128
|
+
current = resolve_superclass(current)
|
|
129
|
+
end
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Every walked class and module name and every assigned constant name, for the plugin's check that
|
|
134
|
+
# no other `*::<name>` could shadow a top-level constant.
|
|
135
|
+
def declared_constant_names
|
|
136
|
+
@namespaces.keys + @constants.keys
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Issue #2541 — the classes whose constants a struct body's relative constant could reach through
|
|
140
|
+
# ancestry: the class itself, its walked superclasses below the struct root, and the modules each of
|
|
141
|
+
# them `include`s (as written).
|
|
142
|
+
def constant_ancestry(class_name)
|
|
143
|
+
chain = []
|
|
144
|
+
current = class_name.to_s.delete_prefix("::")
|
|
145
|
+
until current.nil? || chain.include?(current) || STRUCT_ROOTS.include?(current)
|
|
146
|
+
chain << current
|
|
147
|
+
current = resolve_superclass(current)
|
|
148
|
+
end
|
|
149
|
+
chain + chain.flat_map { |name| @struct_includes.fetch(name, []).map { |mod| mod.delete_prefix("::") } }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return the type source and the nesting for the accessor `signature` names, or nil when it has no type
|
|
153
|
+
# source (a conditional or type-less macro).
|
|
154
|
+
def struct_prop_type(signature)
|
|
155
|
+
source = @struct_prop_types[[signature.class_name, signature.method_name]]
|
|
156
|
+
return nil if source.nil?
|
|
157
|
+
|
|
158
|
+
[source, @struct_nesting.fetch(signature.class_name, [])]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @param written — the superclass expression as written (`"T::Struct"`, `"Base"`, `"::Foo::Base"`).
|
|
162
|
+
# @param lexical_prefix — the enclosing namespace names, outermost first, that Ruby resolves a
|
|
163
|
+
# relative `written` against.
|
|
164
|
+
def record_superclass(class_name:, written:, lexical_prefix:)
|
|
165
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
166
|
+
|
|
167
|
+
@superclasses[class_name] ||= [written, lexical_prefix.dup.freeze].freeze
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def record_defined_method(class_name:, method_name:)
|
|
171
|
+
raise "Catalog already finalised" if @frozen_after_build
|
|
172
|
+
|
|
173
|
+
list = (@defined_methods[class_name] ||= [])
|
|
174
|
+
list << method_name unless list.include?(method_name)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Issue #2541 — the signature a `prop` / `const` accessor contributes to `class_name#method_name`,
|
|
178
|
+
# walking the lexical superclass chain the way Ruby's method lookup does. A class on the way that
|
|
179
|
+
# `def`s the name itself (or declares it with `attr_*`) stops the walk with nil, leaving the user's
|
|
180
|
+
# method to the sig lookup and the engine's own tiers. Returns nil unless the declaring class descends from a {STRUCT_ROOTS} root, so a
|
|
181
|
+
# project DSL that happens to be named `prop` is never read as Sorbet's.
|
|
182
|
+
def lookup_struct_prop(class_name:, method_name:)
|
|
183
|
+
return nil if @struct_props.empty?
|
|
184
|
+
|
|
185
|
+
current = class_name.to_s.delete_prefix("::")
|
|
186
|
+
visited = []
|
|
187
|
+
while current && !visited.include?(current)
|
|
188
|
+
visited << current
|
|
189
|
+
return nil if @defined_methods[current]&.include?(method_name)
|
|
190
|
+
|
|
191
|
+
signature = @struct_props.dig(current, method_name)
|
|
192
|
+
return struct_answer(signature, current) if signature
|
|
193
|
+
|
|
194
|
+
current = resolve_superclass(current)
|
|
195
|
+
end
|
|
196
|
+
nil
|
|
197
|
+
end
|
|
198
|
+
|
|
45
199
|
def freeze!
|
|
46
200
|
@frozen_after_build = true
|
|
201
|
+
@struct_props.each_value(&:freeze)
|
|
202
|
+
@struct_props.freeze
|
|
203
|
+
@struct_prop_types.freeze
|
|
204
|
+
@struct_nesting.freeze
|
|
205
|
+
@namespaces.freeze
|
|
206
|
+
@constants.freeze
|
|
207
|
+
@struct_includes.each_value(&:freeze)
|
|
208
|
+
@struct_includes.freeze
|
|
209
|
+
@mixes_in.freeze
|
|
210
|
+
@superclasses.freeze
|
|
211
|
+
@defined_methods.each_value(&:freeze)
|
|
212
|
+
@defined_methods.freeze
|
|
47
213
|
@entries.freeze
|
|
48
214
|
@mixins.each_value do |bucket|
|
|
49
215
|
bucket.each_value(&:freeze)
|
|
@@ -64,7 +230,7 @@ module Rigor
|
|
|
64
230
|
end
|
|
65
231
|
|
|
66
232
|
def empty?
|
|
67
|
-
@entries.empty?
|
|
233
|
+
@entries.empty? && @struct_props.empty?
|
|
68
234
|
end
|
|
69
235
|
|
|
70
236
|
# ADR-52 slice 4 — the distinct method names the catalog carries at least one signature for,
|
|
@@ -74,7 +240,9 @@ module Rigor
|
|
|
74
240
|
# Computed fresh per call — the plugin memoises the resolved set, and `freeze!` freezes the
|
|
75
241
|
# catalog itself so a lazy memo ivar here would raise.
|
|
76
242
|
def method_names
|
|
77
|
-
@entries.keys.map { |key| key[1] }
|
|
243
|
+
names = @entries.keys.map { |key| key[1] }
|
|
244
|
+
@struct_props.each_value { |props| names.concat(props.keys) }
|
|
245
|
+
names.uniq
|
|
78
246
|
end
|
|
79
247
|
|
|
80
248
|
def size
|
|
@@ -83,6 +251,45 @@ module Rigor
|
|
|
83
251
|
|
|
84
252
|
private
|
|
85
253
|
|
|
254
|
+
def struct_answer(signature, declaring_class)
|
|
255
|
+
root = struct_root(declaring_class)
|
|
256
|
+
return nil if root.nil?
|
|
257
|
+
return nil if root == IMMUTABLE_STRUCT_ROOT && signature.method_name.end_with?("=")
|
|
258
|
+
|
|
259
|
+
signature
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def struct_root(class_name)
|
|
263
|
+
current = class_name
|
|
264
|
+
visited = []
|
|
265
|
+
while current && !visited.include?(current)
|
|
266
|
+
return current if STRUCT_ROOTS.include?(current)
|
|
267
|
+
|
|
268
|
+
visited << current
|
|
269
|
+
current = resolve_superclass(current)
|
|
270
|
+
end
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Resolves the superclass expression recorded for `class_name` the way Ruby's lexical constant lookup
|
|
275
|
+
# would, as far as the walked sources can tell: a rooted `::Foo` is absolute; a relative `Foo` is tried
|
|
276
|
+
# under each enclosing namespace, innermost first, against the classes the walk saw, then as written.
|
|
277
|
+
def resolve_superclass(class_name)
|
|
278
|
+
written, prefix = @superclasses[class_name]
|
|
279
|
+
return nil if written.nil?
|
|
280
|
+
return written.delete_prefix("::") if written.start_with?("::")
|
|
281
|
+
|
|
282
|
+
prefix.size.downto(1) do |depth|
|
|
283
|
+
candidate = "#{prefix.first(depth).join('::')}::#{written}"
|
|
284
|
+
return candidate if known_class?(candidate)
|
|
285
|
+
end
|
|
286
|
+
written
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def known_class?(name)
|
|
290
|
+
@superclasses.key?(name) || @struct_props.key?(name)
|
|
291
|
+
end
|
|
292
|
+
|
|
86
293
|
def key_for(class_name, method_name, kind)
|
|
87
294
|
[class_name.to_s, method_name.to_sym, kind]
|
|
88
295
|
end
|
|
@@ -29,16 +29,20 @@ module Rigor
|
|
|
29
29
|
# @param root — the file's program node.
|
|
30
30
|
# @param catalog — mutable; signatures are recorded into it.
|
|
31
31
|
# @param path — file path used for diagnostic provenance.
|
|
32
|
+
# @param struct_catalog — where the `T::Struct`-family accessors, superclasses, and own `def`s are
|
|
33
|
+
# recorded (issue #2541). Defaults to `catalog`; the plugin passes the per-run catalog here even
|
|
34
|
+
# when `catalog` is the discard catalog of an unenforced-sigil file, because sorbet-runtime defines
|
|
35
|
+
# a struct's accessors whatever the file's `# typed:` level.
|
|
32
36
|
# @return errors observed during the walk; empty when the file is sig-clean.
|
|
33
|
-
def walk(root:, catalog:, path:)
|
|
34
|
-
state = State.new(catalog: catalog, path: path)
|
|
37
|
+
def walk(root:, catalog:, path:, struct_catalog: catalog)
|
|
38
|
+
state = State.new(catalog: catalog, path: path, struct_catalog: struct_catalog)
|
|
35
39
|
walk_node(root, state, lexical_path: [], in_singleton_class: false)
|
|
36
40
|
state.errors
|
|
37
41
|
end
|
|
38
42
|
|
|
39
|
-
State = Struct.new(:catalog, :path, :errors, keyword_init: true) do
|
|
40
|
-
def initialize(catalog:, path:)
|
|
41
|
-
super(catalog: catalog, path: path, errors: [])
|
|
43
|
+
State = Struct.new(:catalog, :path, :errors, :struct_catalog, keyword_init: true) do
|
|
44
|
+
def initialize(catalog:, path:, struct_catalog: catalog)
|
|
45
|
+
super(catalog: catalog, path: path, errors: [], struct_catalog: struct_catalog)
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def record_error(kind, node)
|
|
@@ -50,7 +54,9 @@ module Rigor
|
|
|
50
54
|
return unless node.is_a?(Prism::Node)
|
|
51
55
|
|
|
52
56
|
case node
|
|
53
|
-
when Prism::ClassNode
|
|
57
|
+
when Prism::ClassNode
|
|
58
|
+
descend_class(node, state, lexical_path)
|
|
59
|
+
when Prism::ModuleNode
|
|
54
60
|
descend_class_or_module(node, state, lexical_path)
|
|
55
61
|
when Prism::SingletonClassNode
|
|
56
62
|
descend_singleton_class(node, state, lexical_path)
|
|
@@ -59,6 +65,20 @@ module Rigor
|
|
|
59
65
|
when Prism::DefNode
|
|
60
66
|
# A `def` not preceded by a `sig` is fine; we just don't record anything for it. The
|
|
61
67
|
# interesting case is in `walk_statements`, which pairs sig+def.
|
|
68
|
+
when Prism::CallNode
|
|
69
|
+
# Issue #2541 — any `include` / `prepend` in a class (whatever its arguments) opens the class's
|
|
70
|
+
# ancestry, so a struct accessor's constant is no longer answered from the top level.
|
|
71
|
+
if node.receiver.nil? && %i[include prepend].include?(node.name) && !lexical_path.empty?
|
|
72
|
+
state.struct_catalog.record_mixes_in(lexical_path.join("::"))
|
|
73
|
+
end
|
|
74
|
+
node.rigor_each_child do |child|
|
|
75
|
+
walk_node(child, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
|
|
76
|
+
end
|
|
77
|
+
when Prism::ConstantWriteNode
|
|
78
|
+
# Issue #2541 — `Thing = Other` makes `Thing` a constant that is not a class declaration, so a
|
|
79
|
+
# struct accessor's type naming it is not read as any class.
|
|
80
|
+
state.struct_catalog.record_constant((lexical_path + [node.name.to_s]).join("::"))
|
|
81
|
+
walk_node(node.value, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
|
|
62
82
|
else
|
|
63
83
|
node.rigor_each_child do |child|
|
|
64
84
|
walk_node(child, state, lexical_path: lexical_path, in_singleton_class: in_singleton_class)
|
|
@@ -66,8 +86,25 @@ module Rigor
|
|
|
66
86
|
end
|
|
67
87
|
end
|
|
68
88
|
|
|
89
|
+
# Issue #2541 — a `class` records its superclass, and only its body's own top-level statements,
|
|
90
|
+
# which certainly run at class definition, may declare `prop` / `const` accessors.
|
|
91
|
+
def descend_class(node, state, lexical_path)
|
|
92
|
+
name = qualified_name_for(node.constant_path)
|
|
93
|
+
return descend_class_or_module(node, state, lexical_path) unless name
|
|
94
|
+
|
|
95
|
+
record_superclass(node, name, state, lexical_path)
|
|
96
|
+
state.struct_catalog.record_namespace((lexical_path + [name]).join("::"))
|
|
97
|
+
if node.body.is_a?(Prism::StatementsNode)
|
|
98
|
+
walk_statements(node.body, state, lexical_path: lexical_path + [name], in_singleton_class: false,
|
|
99
|
+
class_body: true)
|
|
100
|
+
else
|
|
101
|
+
descend_class_or_module(node, state, lexical_path)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
69
105
|
def descend_class_or_module(node, state, lexical_path)
|
|
70
106
|
name = qualified_name_for(node.constant_path)
|
|
107
|
+
state.struct_catalog.record_namespace((lexical_path + [name]).join("::")) if name
|
|
71
108
|
if name && node.body
|
|
72
109
|
child_prefix = lexical_path + [name]
|
|
73
110
|
walk_node(node.body, state, lexical_path: child_prefix, in_singleton_class: false)
|
|
@@ -88,10 +125,13 @@ module Rigor
|
|
|
88
125
|
# `sig` call, it remembers it and consumes the very next `def` / `def self.foo` as the target.
|
|
89
126
|
# Anything between a sig and its def (a comment is fine — comments aren't AST nodes — but a
|
|
90
127
|
# method call would be a problem) leaves the sig dangling.
|
|
91
|
-
def walk_statements(statements, state, lexical_path:, in_singleton_class:)
|
|
128
|
+
def walk_statements(statements, state, lexical_path:, in_singleton_class:, class_body: false)
|
|
92
129
|
pending_sig = nil
|
|
93
130
|
|
|
94
131
|
statements.body.each do |child|
|
|
132
|
+
record_defined_method(child, state, lexical_path) unless in_singleton_class
|
|
133
|
+
record_struct_prop_call(child, state, lexical_path, certain: class_body) unless in_singleton_class
|
|
134
|
+
|
|
95
135
|
if pending_sig && def_node?(child)
|
|
96
136
|
record_def_with_sig(child, pending_sig, state, lexical_path, in_singleton_class)
|
|
97
137
|
pending_sig = nil
|
|
@@ -153,9 +193,108 @@ module Rigor
|
|
|
153
193
|
state.catalog.record_mixin(
|
|
154
194
|
class_name: class_name, kind: kind, module_name: module_name
|
|
155
195
|
)
|
|
196
|
+
# Issue #2541 — an included module's constants shadow a top-level one for the class body.
|
|
197
|
+
state.struct_catalog.record_struct_include(class_name, module_name) if kind == :include
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Issue #2541 — `class X < Y`: the superclass as written plus the enclosing namespaces, so the
|
|
202
|
+
# catalog can follow `X`'s chain to a `T::Struct`-family root. `lexical_path` holds the enclosing
|
|
203
|
+
# names (a nested `class A::B` element counts as one namespace step, as Ruby's cref does).
|
|
204
|
+
def record_superclass(node, name, state, lexical_path)
|
|
205
|
+
written = qualified_name_for(node.superclass)
|
|
206
|
+
return if written.nil?
|
|
207
|
+
|
|
208
|
+
state.struct_catalog.record_superclass(
|
|
209
|
+
class_name: (lexical_path + [name]).join("::"), written: written, lexical_prefix: lexical_path
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# The instance methods a class body defines itself — `def foo`, `private def foo`, `attr_reader :foo` /
|
|
214
|
+
# `attr_writer` / `attr_accessor` — so a struct accessor of the same name yields to them.
|
|
215
|
+
def record_defined_method(node, state, lexical_path)
|
|
216
|
+
return if lexical_path.empty?
|
|
217
|
+
|
|
218
|
+
class_name = lexical_path.join("::")
|
|
219
|
+
defined_method_names(node).each do |method_name|
|
|
220
|
+
state.struct_catalog.record_defined_method(class_name: class_name, method_name: method_name)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def defined_method_names(node)
|
|
225
|
+
target = visibility_wrapped_def(node) || node
|
|
226
|
+
if def_node?(target)
|
|
227
|
+
target.receiver.nil? ? [target.name] : []
|
|
228
|
+
elsif attr_macro_call?(target)
|
|
229
|
+
attr_method_names(target)
|
|
230
|
+
else
|
|
231
|
+
[]
|
|
156
232
|
end
|
|
157
233
|
end
|
|
158
234
|
|
|
235
|
+
def attr_method_names(attr_node)
|
|
236
|
+
attr_node.arguments.arguments.flat_map do |arg|
|
|
237
|
+
name = arg.unescaped
|
|
238
|
+
case attr_node.name
|
|
239
|
+
when :attr_reader then [name.to_sym]
|
|
240
|
+
when :attr_writer then [:"#{name}="]
|
|
241
|
+
else [name.to_sym, :"#{name}="]
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
STRUCT_PROP_MACROS = %i[prop const].freeze
|
|
247
|
+
|
|
248
|
+
# Issue #2541 — `prop :name, Type, **rules` / `const :name, Type, **rules` in a class body. Records
|
|
249
|
+
# the reader (both macros) and the writer (`prop` only); the catalog decides at lookup whether the
|
|
250
|
+
# class is a `T::Struct`-family class at all. At the body's top level (`certain:`) the accessor is
|
|
251
|
+
# typed by the translated `Type`. Nested under an `if`, a `begin`, or a block in the body, the call
|
|
252
|
+
# may not run, so the accessor is recorded as possible: typed `Dynamic[top]`, which keeps a call
|
|
253
|
+
# to it quiet without claiming its type. A name that is not a literal Symbol records nothing
|
|
254
|
+
# (`T::Props` takes a Symbol).
|
|
255
|
+
def record_struct_prop_call(node, state, lexical_path, certain:)
|
|
256
|
+
return if lexical_path.empty?
|
|
257
|
+
return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
258
|
+
return unless STRUCT_PROP_MACROS.include?(node.name)
|
|
259
|
+
|
|
260
|
+
args = node.arguments&.arguments
|
|
261
|
+
return if args.nil? || args.empty?
|
|
262
|
+
|
|
263
|
+
return unless args.first.is_a?(Prism::SymbolNode)
|
|
264
|
+
|
|
265
|
+
name = args.first.unescaped
|
|
266
|
+
|
|
267
|
+
type_node = args[1] unless args[1].is_a?(Prism::KeywordHashNode)
|
|
268
|
+
# The constants in the type resolve at lookup, against the classes every file declares, so only
|
|
269
|
+
# the source text is kept; the signature's own type is the placeholder until then.
|
|
270
|
+
type_source = type_node&.slice if certain
|
|
271
|
+
class_name = lexical_path.join("::")
|
|
272
|
+
record_struct_accessor(state, class_name, name.to_sym, type_source, lexical_path, certain)
|
|
273
|
+
return unless node.name == :prop && !immutable_rule?(args)
|
|
274
|
+
|
|
275
|
+
record_struct_accessor(state, class_name, :"#{name}=", type_source, lexical_path, certain)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# `prop :x, T, immutable: true` is the long form of `const`: no writer.
|
|
279
|
+
def immutable_rule?(args)
|
|
280
|
+
args.any? do |arg|
|
|
281
|
+
arg.is_a?(Prism::KeywordHashNode) && arg.elements.any? do |element|
|
|
282
|
+
element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode) &&
|
|
283
|
+
element.key.unescaped == "immutable" && element.value.is_a?(Prism::TrueNode)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def record_struct_accessor(state, class_name, method_name, type_source, nesting, certain)
|
|
289
|
+
state.struct_catalog.record_struct_prop(
|
|
290
|
+
MethodSignature.new(
|
|
291
|
+
class_name: class_name, method_name: method_name, kind: :instance,
|
|
292
|
+
params: {}, return_type: Rigor::Type::Combinator.untyped, modifiers: []
|
|
293
|
+
),
|
|
294
|
+
certain: certain, type_source: type_source, nesting: nesting
|
|
295
|
+
)
|
|
296
|
+
end
|
|
297
|
+
|
|
159
298
|
def sig_call?(node)
|
|
160
299
|
node.is_a?(Prism::CallNode) &&
|
|
161
300
|
node.name == :sig &&
|
|
@@ -52,28 +52,33 @@ module Rigor
|
|
|
52
52
|
|
|
53
53
|
module_function
|
|
54
54
|
|
|
55
|
+
# @param resolve — optional callable mapping an unrooted constant name as written (`"Thing"`,
|
|
56
|
+
# `"Foo::Bar"`) to the class it names, or to nil when that is not certain, in which case the
|
|
57
|
+
# constant degrades to `Dynamic[top]`. Issue #2541 passes it for `T::Struct` accessor types; without
|
|
58
|
+
# it (every sig) a constant becomes `Nominal[<as written>]`, as before. The `T::` vocabulary is
|
|
59
|
+
# never handed to it.
|
|
55
60
|
# @return never `nil`; unrecognised forms degrade to `Type::Combinator.untyped`.
|
|
56
|
-
def translate(node)
|
|
61
|
+
def translate(node, resolve = nil)
|
|
57
62
|
return Rigor::Type::Combinator.untyped if node.nil?
|
|
58
63
|
|
|
59
64
|
case node
|
|
60
|
-
when Prism::ConstantReadNode then translate_constant_read(node)
|
|
61
|
-
when Prism::ConstantPathNode then translate_constant_path(node)
|
|
62
|
-
when Prism::CallNode then translate_call(node)
|
|
63
|
-
when Prism::ArrayNode then translate_tuple(node)
|
|
64
|
-
when Prism::HashNode then translate_shape(node)
|
|
65
|
+
when Prism::ConstantReadNode then translate_constant_read(node, resolve)
|
|
66
|
+
when Prism::ConstantPathNode then translate_constant_path(node, resolve)
|
|
67
|
+
when Prism::CallNode then translate_call(node, resolve)
|
|
68
|
+
when Prism::ArrayNode then translate_tuple(node, resolve)
|
|
69
|
+
when Prism::HashNode then translate_shape(node, resolve)
|
|
65
70
|
else degraded
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
73
|
|
|
69
|
-
def translate_constant_read(node)
|
|
70
|
-
name = node.name.to_s
|
|
71
|
-
return Rigor::Type::Combinator.untyped if name.empty?
|
|
74
|
+
def translate_constant_read(node, resolve = nil)
|
|
75
|
+
name = resolved_name(node.name.to_s, resolve)
|
|
76
|
+
return Rigor::Type::Combinator.untyped if name.nil? || name.empty?
|
|
72
77
|
|
|
73
78
|
Rigor::Type::Combinator.nominal_of(name)
|
|
74
79
|
end
|
|
75
80
|
|
|
76
|
-
def translate_constant_path(node)
|
|
81
|
+
def translate_constant_path(node, resolve = nil)
|
|
77
82
|
name = constant_path_name(node)
|
|
78
83
|
return degraded if name.nil?
|
|
79
84
|
|
|
@@ -81,9 +86,20 @@ module Rigor
|
|
|
81
86
|
# type alias.
|
|
82
87
|
return boolean_type if name == "T::Boolean"
|
|
83
88
|
|
|
89
|
+
name = resolved_name(name, resolve)
|
|
90
|
+
return degraded if name.nil?
|
|
91
|
+
|
|
84
92
|
Rigor::Type::Combinator.nominal_of(name)
|
|
85
93
|
end
|
|
86
94
|
|
|
95
|
+
# The constant `name` as `resolve` reads it (a rooted `::Foo` included); `name` itself when there is
|
|
96
|
+
# no resolver.
|
|
97
|
+
def resolved_name(name, resolve)
|
|
98
|
+
return name if resolve.nil?
|
|
99
|
+
|
|
100
|
+
resolve.call(name)
|
|
101
|
+
end
|
|
102
|
+
|
|
87
103
|
# `Prism::CallNode` covers three distinct surfaces:
|
|
88
104
|
#
|
|
89
105
|
# 1. `T.something(...)` — `untyped` / `anything` / `noreturn` / `nilable` / `any` / `all` /
|
|
@@ -96,58 +112,59 @@ module Rigor
|
|
|
96
112
|
# instantiation intact (the unwrap-site receiver a plugin like rigor-mangrove reads `type_args`
|
|
97
113
|
# from). Without this branch such forms degraded to `untyped`, silently dropping the
|
|
98
114
|
# receiver's generic arguments.
|
|
99
|
-
def translate_call(node)
|
|
100
|
-
return translate_t_method(node) if sorbet_t_namespaced?(node.receiver)
|
|
101
|
-
return translate_t_subscript(node) if sorbet_subscript?(node)
|
|
102
|
-
return translate_user_subscript(node) if user_generic_subscript?(node)
|
|
115
|
+
def translate_call(node, resolve = nil)
|
|
116
|
+
return translate_t_method(node, resolve) if sorbet_t_namespaced?(node.receiver)
|
|
117
|
+
return translate_t_subscript(node, resolve) if sorbet_subscript?(node)
|
|
118
|
+
return translate_user_subscript(node, resolve) if user_generic_subscript?(node)
|
|
103
119
|
|
|
104
120
|
degraded
|
|
105
121
|
end
|
|
106
122
|
|
|
107
123
|
# Handles the `T.foo(...)` family.
|
|
108
|
-
def translate_t_method(node)
|
|
124
|
+
def translate_t_method(node, resolve = nil)
|
|
109
125
|
case node.name
|
|
110
126
|
when :untyped then Rigor::Type::Combinator.untyped
|
|
111
127
|
when :anything then Rigor::Type::Combinator.top
|
|
112
128
|
when :noreturn then Rigor::Type::Combinator.bot
|
|
113
|
-
when :nilable then translate_nilable(node)
|
|
114
|
-
when :any then translate_any(node)
|
|
115
|
-
when :all then translate_all(node)
|
|
116
|
-
when :class_of then translate_class_of(node)
|
|
129
|
+
when :nilable then translate_nilable(node, resolve)
|
|
130
|
+
when :any then translate_any(node, resolve)
|
|
131
|
+
when :all then translate_all(node, resolve)
|
|
132
|
+
when :class_of then translate_class_of(node, resolve)
|
|
117
133
|
else degraded
|
|
118
134
|
end
|
|
119
135
|
end
|
|
120
136
|
|
|
121
|
-
def translate_nilable(node)
|
|
137
|
+
def translate_nilable(node, resolve = nil)
|
|
122
138
|
inner = first_argument(node)
|
|
123
139
|
return degraded if inner.nil?
|
|
124
140
|
|
|
125
141
|
Rigor::Type::Combinator.union(
|
|
126
|
-
translate(inner), Rigor::Type::Combinator.constant_of(nil)
|
|
142
|
+
translate(inner, resolve), Rigor::Type::Combinator.constant_of(nil)
|
|
127
143
|
)
|
|
128
144
|
end
|
|
129
145
|
|
|
130
|
-
def translate_any(node)
|
|
146
|
+
def translate_any(node, resolve = nil)
|
|
131
147
|
args = call_arguments(node)
|
|
132
148
|
return degraded if args.empty?
|
|
133
149
|
|
|
134
|
-
Rigor::Type::Combinator.union(*args.map { |arg| translate(arg) })
|
|
150
|
+
Rigor::Type::Combinator.union(*args.map { |arg| translate(arg, resolve) })
|
|
135
151
|
end
|
|
136
152
|
|
|
137
|
-
def translate_all(node)
|
|
153
|
+
def translate_all(node, resolve = nil)
|
|
138
154
|
args = call_arguments(node)
|
|
139
155
|
return degraded if args.empty?
|
|
140
156
|
|
|
141
|
-
Rigor::Type::Combinator.intersection(*args.map { |arg| translate(arg) })
|
|
157
|
+
Rigor::Type::Combinator.intersection(*args.map { |arg| translate(arg, resolve) })
|
|
142
158
|
end
|
|
143
159
|
|
|
144
160
|
# `T.class_of(C)` — singleton-class type for a single constant. Sorbet docs note
|
|
145
161
|
# `T.class_of(MyInterface)` rarely means what users expect (it's the singleton class of
|
|
146
162
|
# `MyInterface`, not "any class implementing the interface"); we honour the literal meaning here
|
|
147
163
|
# and translate to `Singleton[C]`.
|
|
148
|
-
def translate_class_of(node)
|
|
164
|
+
def translate_class_of(node, resolve = nil)
|
|
149
165
|
target = first_argument(node)
|
|
150
166
|
name = constant_path_name(target)
|
|
167
|
+
name = resolved_name(name, resolve) unless name.nil?
|
|
151
168
|
return degraded if name.nil?
|
|
152
169
|
|
|
153
170
|
Rigor::Type::Combinator.singleton_of(name)
|
|
@@ -157,9 +174,9 @@ module Rigor
|
|
|
157
174
|
# `CallNode` whose receiver is the `T::Array` `ConstantPathNode` and whose `name` is `:[]`.
|
|
158
175
|
# `T::Class[T]` lands here too; we collapse it to `Singleton[name]` (a deliberate narrowing —
|
|
159
176
|
# `T::Class` is structurally generic in Sorbet, but Rigor's `Singleton` carries class identity only).
|
|
160
|
-
def translate_t_subscript(node)
|
|
177
|
+
def translate_t_subscript(node, resolve = nil)
|
|
161
178
|
base_name = sorbet_subscript_base(node.receiver)
|
|
162
|
-
args = call_arguments(node).map { |arg| translate(arg) }
|
|
179
|
+
args = call_arguments(node).map { |arg| translate(arg, resolve) }
|
|
163
180
|
mapped = T_GENERIC_CLASSES[base_name]
|
|
164
181
|
|
|
165
182
|
if mapped
|
|
@@ -188,13 +205,14 @@ module Rigor
|
|
|
188
205
|
!sorbet_t_qualified?(receiver)
|
|
189
206
|
end
|
|
190
207
|
|
|
191
|
-
def translate_user_subscript(node)
|
|
208
|
+
def translate_user_subscript(node, resolve = nil)
|
|
192
209
|
# Parity with translate_constant_path: the constant's rendered name (carrying a leading `::` for
|
|
193
210
|
# absolute paths) becomes the nominal class name.
|
|
194
211
|
name = constant_path_name(node.receiver)
|
|
212
|
+
name = resolved_name(name, resolve) unless name.nil?
|
|
195
213
|
return degraded if name.nil?
|
|
196
214
|
|
|
197
|
-
args = call_arguments(node).map { |arg| translate(arg) }
|
|
215
|
+
args = call_arguments(node).map { |arg| translate(arg, resolve) }
|
|
198
216
|
Rigor::Type::Combinator.nominal_of(name, type_args: args)
|
|
199
217
|
end
|
|
200
218
|
|
|
@@ -214,21 +232,21 @@ module Rigor
|
|
|
214
232
|
|
|
215
233
|
# Tuple types in `sig` position appear as bare array literals: `sig { returns([String, Integer]) }`.
|
|
216
234
|
# Each element is itself a type expression we translate recursively.
|
|
217
|
-
def translate_tuple(node)
|
|
218
|
-
elements = node.elements.map { |element| translate(element) }
|
|
235
|
+
def translate_tuple(node, resolve = nil)
|
|
236
|
+
elements = node.elements.map { |element| translate(element, resolve) }
|
|
219
237
|
Rigor::Type::Combinator.tuple_of(*elements)
|
|
220
238
|
end
|
|
221
239
|
|
|
222
240
|
# Shape types in `sig` position appear as bare hash literals with symbol keys:
|
|
223
241
|
# `sig { returns({a: Integer, b: String}) }`. Each value is a type expression; the resulting
|
|
224
242
|
# `HashShape` is closed (no extra keys allowed).
|
|
225
|
-
def translate_shape(node)
|
|
243
|
+
def translate_shape(node, resolve = nil)
|
|
226
244
|
pairs = []
|
|
227
245
|
node.elements.each do |element|
|
|
228
246
|
next unless element.is_a?(Prism::AssocNode)
|
|
229
247
|
next unless element.key.is_a?(Prism::SymbolNode)
|
|
230
248
|
|
|
231
|
-
pairs << [element.key.unescaped.to_sym, translate(element.value)]
|
|
249
|
+
pairs << [element.key.unescaped.to_sym, translate(element.value, resolve)]
|
|
232
250
|
end
|
|
233
251
|
# `.to_h`: `hash_shape_of` validates its argument is a Hash and raises `ArgumentError` on the
|
|
234
252
|
# array-of-pairs form, so `sig { returns({name: String}) }` degraded to `Dynamic[top]` through
|