rigortype 0.2.9 → 0.3.0
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 +2 -2
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +1 -0
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +49 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +33 -3
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +19 -27
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +42 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -11,9 +11,11 @@ module Rigor
|
|
|
11
11
|
# and degrading to today's behaviour (no carrier / the `Data` nominal) the moment a premise is
|
|
12
12
|
# uncertain, so the tier is precision-additive and adds no false-positive surface:
|
|
13
13
|
#
|
|
14
|
-
# 1. `Data.define(:x, :y)` on a `Singleton[Data]` receiver with literal-Symbol args
|
|
15
|
-
# `DataClass{members: [...]}`. A
|
|
16
|
-
# block-
|
|
14
|
+
# 1. `Data.define(:x, :y)` on a `Singleton[Data]` receiver with literal-Symbol args ->
|
|
15
|
+
# `DataClass{members: [...]}`. A `do ... end` block folds too when its body redefines no member
|
|
16
|
+
# reader (bare-local block-form parity — the block AST stands in for the class name the read-time
|
|
17
|
+
# guard would otherwise need); a `&proc` block, a reader-redefining block, or non-literal members
|
|
18
|
+
# (`Data.define(*names)`) defer.
|
|
17
19
|
# 2. `.new` / `.[]` on a `DataClass` receiver -> a `DataInstance` whose member map is built from the
|
|
18
20
|
# call's positional or keyword arguments. An arity / key mismatch degrades to the `Data` (or the
|
|
19
21
|
# tagged class) nominal rather than a wrong member map.
|
|
@@ -63,12 +65,32 @@ module Rigor
|
|
|
63
65
|
|
|
64
66
|
def fold_define(context)
|
|
65
67
|
return nil unless context.method_name == :define
|
|
66
|
-
# Block-form (`Data.define(:x) do ... end`) defers — slice 4.
|
|
67
|
-
return nil unless context.block_type.nil?
|
|
68
68
|
|
|
69
69
|
members = member_names_from_args(context.args)
|
|
70
70
|
return nil if members.nil? || members.empty?
|
|
71
71
|
|
|
72
|
+
# Block-form (`Data.define(:x) do ... end`) bare-local parity (ADR-48 "Remaining"): the assigned
|
|
73
|
+
# constant / subclass forms already fold via the layout side-table, with the reader-redefinition
|
|
74
|
+
# guard consulting `Scope#user_def_for`; the bare-local form has no resolvable class name for it
|
|
75
|
+
# to key on. But the block AST is in hand, so the guard is applied directly against it: fold when
|
|
76
|
+
# the block redefines no member's synthesised reader (folding `inst.x` would otherwise run the
|
|
77
|
+
# redefined `def x`), and bail conservatively — the prior behaviour — the moment the block cannot
|
|
78
|
+
# be soundly cleared (a `&proc` argument, or any `def <member>` in the body).
|
|
79
|
+
return fold_define_block(context, members) unless context.block_type.nil?
|
|
80
|
+
|
|
81
|
+
Type::Combinator.data_class_of(members: members)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The block-form fold decision. Only a literal `do ... end` block whose body redefines no member
|
|
85
|
+
# reader folds; a `&proc` block argument (no scannable body) or a body with a member-reader `def`
|
|
86
|
+
# stays unfolded. The resulting `DataClass` carries no `class_name` — folding is already proven
|
|
87
|
+
# reader-safe here, so the read-time `reader_overridden?` guard (which keys on the class name) has
|
|
88
|
+
# nothing left to catch.
|
|
89
|
+
def fold_define_block(context, members)
|
|
90
|
+
block = context.call_node&.block
|
|
91
|
+
return nil unless block.is_a?(Prism::BlockNode)
|
|
92
|
+
return nil if block_redefines_member_reader?(block, members)
|
|
93
|
+
|
|
72
94
|
Type::Combinator.data_class_of(members: members)
|
|
73
95
|
end
|
|
74
96
|
|
|
@@ -126,6 +148,9 @@ module Rigor
|
|
|
126
148
|
shape = args.first
|
|
127
149
|
return nil unless shape.is_a?(Type::HashShape) && shape.closed?
|
|
128
150
|
return nil unless shape.optional_keys.empty?
|
|
151
|
+
# Non-Symbol keys (a HashShape may carry String / numeric / bool / nil scalar keys) can never
|
|
152
|
+
# match the Symbol member list — and mixed-class keys would make the `.sort` below raise.
|
|
153
|
+
return nil unless shape.pairs.keys.all?(Symbol)
|
|
129
154
|
return nil unless shape.pairs.keys.sort == members.sort
|
|
130
155
|
|
|
131
156
|
members.to_h { |name| [name, shape.pairs.fetch(name)] }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "constant_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -24,9 +25,27 @@ module Rigor
|
|
|
24
25
|
#
|
|
25
26
|
# See `docs/type-specification/value-lattice.md` for the union-distribution contract this tier
|
|
26
27
|
# mirrors.
|
|
28
|
+
#
|
|
29
|
+
# ADR-91 — this tier is the single owner of the `Kernel` module-function fold surface, covering
|
|
30
|
+
# the conversion/identity folds (`Array` / `Integer` / `Float` / `Rational` / `Complex` / `p` /
|
|
31
|
+
# `pp` / `String` / `Hash`) AND the `format` / `sprintf` template fold (moved here from
|
|
32
|
+
# `LiteralStringFolding` in ADR-91 WD2 — `String#%` stays there as a receiver-typed String
|
|
33
|
+
# method). Ownership gating no longer happens per-fold: the dispatcher consults this tier only
|
|
34
|
+
# when `context.method_name` is in {INTRINSIC_NAMES} and {kernel_owned_call?} passes (ADR-91
|
|
35
|
+
# WD1), so a fold body never re-derives the guard. Callers reaching `try_dispatch` directly
|
|
36
|
+
# (internal dispatcher, unit probes) vouch for ownership themselves.
|
|
27
37
|
module KernelDispatch
|
|
28
38
|
module_function
|
|
29
39
|
|
|
40
|
+
# ADR-91 WD1 — the compiled method-name surface of Kernel's foldable module functions. The
|
|
41
|
+
# dispatcher gates on membership here before consulting the tier, and the WD3 parity spec
|
|
42
|
+
# derives its case list from this same table so a fold added without an entry never runs and
|
|
43
|
+
# every entry is spelling-parity checked. Public (not private_constant) and Ractor-shareable
|
|
44
|
+
# because the gate and the spec both read it.
|
|
45
|
+
INTRINSIC_NAMES = Ractor.make_shareable(
|
|
46
|
+
Set[:Array, :Integer, :Float, :Rational, :Complex, :p, :pp, :String, :Hash, :format, :sprintf]
|
|
47
|
+
)
|
|
48
|
+
|
|
30
49
|
# `Kernel#Rational` / `Kernel#Complex` constructor folds. When every argument is a `Type::Constant`
|
|
31
50
|
# whose value is numeric, we can run the actual Ruby constructor and lift the result into a
|
|
32
51
|
# `Constant<Rational>` / `Constant<Complex>`. The factory accepts the same shapes as Ruby:
|
|
@@ -49,6 +68,26 @@ module Rigor
|
|
|
49
68
|
INTEGER_REFINEMENT_PREDICATES = Set[:decimal_int].freeze
|
|
50
69
|
private_constant :INTEGER_REFINEMENT_PREDICATES
|
|
51
70
|
|
|
71
|
+
# `Kernel#p` / `Kernel#pp` return their argument: one argument comes back verbatim, several come
|
|
72
|
+
# back as an Array of them, zero returns nil. The zero-arg form declines here (the RBS `nil` is
|
|
73
|
+
# already exact); the 1-arg form is a precision-PRESERVING identity (the argument type passes
|
|
74
|
+
# through unchanged — shapes, constants, and `Dynamic` alike); the n-arg form is a `Tuple`.
|
|
75
|
+
IDENTITY_PRINTERS = Set[:p, :pp].freeze
|
|
76
|
+
private_constant :IDENTITY_PRINTERS
|
|
77
|
+
|
|
78
|
+
# `Kernel#format` / `Kernel#sprintf` — a String-template fold moved here from
|
|
79
|
+
# `LiteralStringFolding` in ADR-91 WD2 so every Kernel module-function fold sits behind the
|
|
80
|
+
# WD1 gate.
|
|
81
|
+
FORMAT_METHODS = Set[:format, :sprintf].freeze
|
|
82
|
+
private_constant :FORMAT_METHODS
|
|
83
|
+
|
|
84
|
+
# Value classes `Kernel#String` folds over. Each is a literal-carrier class whose `to_s` is a
|
|
85
|
+
# deterministic pure function of the value, so running the real `String()` at fold time is sound.
|
|
86
|
+
STRING_SAFE_CLASSES = [
|
|
87
|
+
String, Symbol, Integer, Float, Rational, Complex, NilClass, TrueClass, FalseClass
|
|
88
|
+
].freeze
|
|
89
|
+
private_constant :STRING_SAFE_CLASSES
|
|
90
|
+
|
|
52
91
|
def try_dispatch(context)
|
|
53
92
|
receiver = context.receiver
|
|
54
93
|
method_name = context.method_name
|
|
@@ -58,10 +97,171 @@ module Rigor
|
|
|
58
97
|
return try_numeric_constructor(method_name, args) if NUMERIC_CONSTRUCTORS.key?(method_name)
|
|
59
98
|
return try_integer(args) if method_name == :Integer
|
|
60
99
|
return try_float(args) if method_name == :Float
|
|
100
|
+
return try_identity_printer(context) if IDENTITY_PRINTERS.include?(method_name)
|
|
101
|
+
return try_string(context) if method_name == :String
|
|
102
|
+
return try_hash(context) if method_name == :Hash
|
|
103
|
+
return try_format(context) if FORMAT_METHODS.include?(method_name)
|
|
104
|
+
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `Kernel#p(x)` / `Kernel#pp(x)` identity typing. Runtime contract: `p x` returns `x`, `p a, b`
|
|
109
|
+
# returns `[a, b]`, bare `p` returns nil. The 1-arg path returns the argument's type object
|
|
110
|
+
# UNCHANGED — never widened, never concretized (a `Dynamic` argument stays `Dynamic`) — so a
|
|
111
|
+
# `p`-wrapped expression is transparent to downstream inference. The n-arg path materialises the
|
|
112
|
+
# positional types as a `Tuple`. The 0-arg path declines (RBS `nil` is already exact).
|
|
113
|
+
#
|
|
114
|
+
# FP envelope: declines on a splat / forwarding argument (the runtime arity — and hence
|
|
115
|
+
# 1-arg-identity vs n-arg-Array — is not statically known). Ownership is gated once at the
|
|
116
|
+
# dispatcher (ADR-91 WD1), so this body no longer re-checks {#kernel_owned_call?}.
|
|
117
|
+
def try_identity_printer(context)
|
|
118
|
+
args = context.args
|
|
119
|
+
return nil if args.empty?
|
|
120
|
+
return nil if unresolved_argument_arity?(context.call_node)
|
|
121
|
+
return args.first if args.size == 1
|
|
122
|
+
|
|
123
|
+
Type::Combinator.tuple_of(*args)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# `Kernel#String(v)` — folds a value-pinned scalar `Constant` to `Constant[String]`
|
|
127
|
+
# (`String(42)` → `"42"`, `String(nil)` → `""`). Restricted to {STRING_SAFE_CLASSES} so the fold
|
|
128
|
+
# never models a user-defined `to_s` / `to_str`; anything else declines to the RBS `String`.
|
|
129
|
+
def try_string(context)
|
|
130
|
+
args = context.args
|
|
131
|
+
return nil unless args.size == 1
|
|
132
|
+
return nil if unresolved_argument_arity?(context.call_node)
|
|
133
|
+
|
|
134
|
+
arg = args.first
|
|
135
|
+
return nil unless arg.is_a?(Type::Constant)
|
|
136
|
+
return nil unless STRING_SAFE_CLASSES.any? { |klass| arg.value.instance_of?(klass) }
|
|
61
137
|
|
|
138
|
+
Type::Combinator.constant_of(String(arg.value))
|
|
139
|
+
rescue StandardError
|
|
62
140
|
nil
|
|
63
141
|
end
|
|
64
142
|
|
|
143
|
+
# `Kernel#Hash(v)` — the trivially-sound slice only. A `HashShape` argument passes through
|
|
144
|
+
# unchanged (`Hash(h)` returns `h` for a real Hash); `Hash(nil)` and `Hash([])` (an empty Tuple)
|
|
145
|
+
# collapse to the empty `HashShape`. Arguments relying on the `to_hash` protocol are not decidable
|
|
146
|
+
# from types alone and decline to the RBS envelope.
|
|
147
|
+
def try_hash(context)
|
|
148
|
+
args = context.args
|
|
149
|
+
return nil unless args.size == 1
|
|
150
|
+
return nil if unresolved_argument_arity?(context.call_node)
|
|
151
|
+
|
|
152
|
+
case (arg = args.first)
|
|
153
|
+
when Type::HashShape then arg
|
|
154
|
+
when Type::Constant then arg.value.nil? ? Type::Combinator.hash_shape_of({}) : nil
|
|
155
|
+
when Type::Tuple then arg.elements.empty? ? Type::Combinator.hash_shape_of({}) : nil
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# `Kernel#format("hello %s", lit)` / `Kernel#sprintf(...)` — moved here from
|
|
160
|
+
# `LiteralStringFolding` in ADR-91 WD2 so it sits behind the WD1 ownership gate with every
|
|
161
|
+
# other Kernel module-function fold. The template plus every value argument must be
|
|
162
|
+
# literal-bearing ({Type::Combinator.literal_string_compatible?}) or a `Type::Constant` of
|
|
163
|
+
# any value (Constants are always provably literal). The template arg specifically must be
|
|
164
|
+
# literal-bearing — a `Constant<Integer>` first arg would not be a valid format template, so
|
|
165
|
+
# the `Type::Constant` allowance applies only to subsequent value args.
|
|
166
|
+
#
|
|
167
|
+
# When the template AND every value argument are value-pinned `Constant`s, the fold sharpens
|
|
168
|
+
# to the exact `Constant[String]` (`format("%d", 1)` → `"1"`) — the module-function sibling
|
|
169
|
+
# of `ConstantFolding#try_fold_string_format`'s `String#%` fold. This is a strict refinement
|
|
170
|
+
# inside the same firing envelope: every exact-foldable input was already lifted to
|
|
171
|
+
# `literal-string`. Ownership is gated at the dispatcher, so the fold itself needs no check.
|
|
172
|
+
def try_format(context)
|
|
173
|
+
args = context.args
|
|
174
|
+
return nil if args.empty?
|
|
175
|
+
|
|
176
|
+
exact = fold_format_constant(args)
|
|
177
|
+
return exact if exact
|
|
178
|
+
return nil unless Type::Combinator.literal_string_compatible?(args.first)
|
|
179
|
+
return nil unless args.drop(1).all? { |arg| literal_or_constant?(arg) }
|
|
180
|
+
|
|
181
|
+
Type::Combinator.literal_string
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Runs the real `Kernel#format` when everything is value-pinned. A malformed directive or an
|
|
185
|
+
# argument-count mismatch raises at fold time; the handler declines so the literal-string
|
|
186
|
+
# lift (or the RBS `String` envelope) answers instead. Results larger than the shared
|
|
187
|
+
# `ConstantFolding::STRING_FOLD_BYTE_LIMIT` decline too, keeping the carrier-size convention.
|
|
188
|
+
def fold_format_constant(args)
|
|
189
|
+
return nil unless args.all?(Type::Constant)
|
|
190
|
+
return nil unless args.first.value.is_a?(String)
|
|
191
|
+
|
|
192
|
+
result = format(args.first.value, *args.drop(1).map(&:value))
|
|
193
|
+
return nil if result.bytesize > ConstantFolding::STRING_FOLD_BYTE_LIMIT
|
|
194
|
+
|
|
195
|
+
Type::Combinator.constant_of(result)
|
|
196
|
+
rescue StandardError
|
|
197
|
+
nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# KernelDispatch's own copy of the literal-or-Constant predicate (LiteralStringFolding keeps
|
|
201
|
+
# its own for the `String#%` fold that stayed there).
|
|
202
|
+
def literal_or_constant?(type)
|
|
203
|
+
Type::Combinator.literal_string_compatible?(type) || type.is_a?(Type::Constant)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# True when the call can be attributed to Kernel's own module function. Kernel's instance-side
|
|
207
|
+
# surface is PRIVATE, so a call with an explicit non-`self` receiver is a user-defined method —
|
|
208
|
+
# decline — EXCEPT the `Kernel` module object itself: `module_function` exposes every intrinsic
|
|
209
|
+
# as a public singleton too, so `Kernel.p(x)` / `Kernel.format(...)` dispatch to the same
|
|
210
|
+
# intrinsic as the implicit-self spelling. Likewise decline when the receiver class (or the
|
|
211
|
+
# toplevel) carries a discovered user redefinition of the name (`def p(node)` in a printer class
|
|
212
|
+
# must not be hijacked by the fold; the precise tiers run ahead of user-method inference). With
|
|
213
|
+
# no call_node / scope (internal dispatcher callers, unit probes) the guards pass — the caller
|
|
214
|
+
# vouches for the shape.
|
|
215
|
+
#
|
|
216
|
+
# ADR-91 WD1: the dispatcher calls this ONCE, before consulting the tier, so the fold bodies
|
|
217
|
+
# no longer re-check it. Kept public so the dispatcher (and unit probes) can reach it.
|
|
218
|
+
def kernel_owned_call?(context)
|
|
219
|
+
return false if user_redefined?(context)
|
|
220
|
+
|
|
221
|
+
!explicit_foreign_receiver?(context.call_node) || kernel_module_receiver?(context)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# The explicit-receiver spelling of a module function: the receiver TYPE is the `Kernel` module
|
|
225
|
+
# object itself. Typed (not node-spelled) so a user constant that happens to be named `Kernel`
|
|
226
|
+
# inside a namespace does not slip through.
|
|
227
|
+
def kernel_module_receiver?(context)
|
|
228
|
+
receiver = context.receiver
|
|
229
|
+
receiver.is_a?(Type::Singleton) && receiver.class_name == "Kernel"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def explicit_foreign_receiver?(call_node)
|
|
233
|
+
return false if call_node.nil?
|
|
234
|
+
|
|
235
|
+
receiver = call_node.receiver
|
|
236
|
+
!(receiver.nil? || receiver.is_a?(Prism::SelfNode))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def user_redefined?(context)
|
|
240
|
+
scope = context.scope
|
|
241
|
+
return false if scope.nil?
|
|
242
|
+
|
|
243
|
+
name = context.method_name
|
|
244
|
+
return true if scope.top_level_def_for(name)
|
|
245
|
+
|
|
246
|
+
case (receiver = context.receiver)
|
|
247
|
+
when Type::Nominal then scope.discovered_method?(receiver.class_name, name, :instance)
|
|
248
|
+
when Type::Singleton then scope.discovered_method?(receiver.class_name, name, :singleton)
|
|
249
|
+
else false
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# True when the argument list carries a splat or an argument-forwarding node — the positional
|
|
254
|
+
# arity (and therefore which fold shape applies) is not statically known. `nil` call_node
|
|
255
|
+
# (internal callers) reads as resolved: the args array is authoritative there.
|
|
256
|
+
def unresolved_argument_arity?(call_node)
|
|
257
|
+
arguments = call_node&.arguments
|
|
258
|
+
return false if arguments.nil?
|
|
259
|
+
|
|
260
|
+
arguments.arguments.any? do |argument|
|
|
261
|
+
argument.is_a?(Prism::SplatNode) || argument.is_a?(Prism::ForwardingArgumentsNode)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
65
265
|
# `Kernel#Integer(arg)` / `Integer(arg, base)`. Two folding paths, tried in order:
|
|
66
266
|
#
|
|
67
267
|
# 1. A `Refined[String, predicate]` argument whose predicate is a total-parse carrier narrows to
|
|
@@ -15,8 +15,9 @@ module Rigor
|
|
|
15
15
|
# size cap).
|
|
16
16
|
# - `Array#join` on `Tuple[…]` receivers whose every element plus the separator argument (when given)
|
|
17
17
|
# is literal-bearing.
|
|
18
|
-
# - `
|
|
19
|
-
#
|
|
18
|
+
# - `String#%` (literal-bearing receiver) when every value argument is literal-bearing or a
|
|
19
|
+
# Type::Constant of any value. (`Kernel#format` / `Kernel#sprintf` moved to {KernelDispatch} in
|
|
20
|
+
# ADR-91 WD2 — they are Kernel module functions, not receiver-typed String methods.)
|
|
20
21
|
#
|
|
21
22
|
# Result rule:
|
|
22
23
|
#
|
|
@@ -38,7 +39,6 @@ module Rigor
|
|
|
38
39
|
module_function
|
|
39
40
|
|
|
40
41
|
CONCAT_METHODS = %i[+ << concat].freeze
|
|
41
|
-
FORMAT_METHODS = %i[format sprintf].freeze
|
|
42
42
|
# v0.1.1 Track 1 slice 5a — methods that, called with no arguments on a literal-bearing receiver,
|
|
43
43
|
# return a value that is also literal-bearing. `#strip` / `#lstrip` / `#rstrip` / `#chomp` (no-arg)
|
|
44
44
|
# / `#chop` strip a known subset of characters from the ends, so the survivors are always a
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
# Integer plus an optional literal padding `String`. When the receiver and the (default or
|
|
56
56
|
# supplied) padding are both literal-bearing, the result is literal-bearing too.
|
|
57
57
|
WIDTH_PADDING_METHODS = %i[center ljust rjust].freeze
|
|
58
|
-
private_constant :CONCAT_METHODS,
|
|
58
|
+
private_constant :CONCAT_METHODS,
|
|
59
59
|
:LITERAL_PRESERVING_METHODS, :NON_EMPTY_LITERAL_PRESERVING_METHODS,
|
|
60
60
|
:WIDTH_PADDING_METHODS
|
|
61
61
|
|
|
@@ -64,7 +64,6 @@ module Rigor
|
|
|
64
64
|
method_name = context.method_name
|
|
65
65
|
args = context.args
|
|
66
66
|
return fold_array_join(receiver, args) if method_name == :join
|
|
67
|
-
return fold_format(args) if FORMAT_METHODS.include?(method_name)
|
|
68
67
|
return nil unless Type::Combinator.literal_string_compatible?(receiver)
|
|
69
68
|
return fold_string_percent(args) if method_name == :%
|
|
70
69
|
return fold_no_arg(receiver, method_name) if args.empty?
|
|
@@ -140,21 +139,27 @@ module Rigor
|
|
|
140
139
|
return nil unless receiver.elements.all? { |el| Type::Combinator.literal_string_compatible?(el) }
|
|
141
140
|
return nil unless args.size <= 1
|
|
142
141
|
return nil if args.size == 1 && !Type::Combinator.literal_string_compatible?(args.first)
|
|
142
|
+
# Defer to {ShapeDispatch}'s `tuple_join` when the precise `Constant<String>` fold is reachable —
|
|
143
|
+
# every element is a `Constant` and the separator is absent or a `Constant<String>`. This tier runs
|
|
144
|
+
# AHEAD of ShapeDispatch, so returning the generic `literal-string` here would shadow that strictly
|
|
145
|
+
# more precise result (`["a", "b"].join("-")` → `Constant<"a-b">` rather than `literal-string`).
|
|
146
|
+
# Mixed tuples that carry a non-`Constant` `literal-string` element keep folding to `literal-string`
|
|
147
|
+
# here, because no exact value is knowable for them.
|
|
148
|
+
return nil if constant_join_reachable?(receiver, args)
|
|
143
149
|
|
|
144
150
|
Type::Combinator.literal_string
|
|
145
151
|
end
|
|
146
152
|
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
# literal-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return
|
|
154
|
-
return nil unless Type::Combinator.literal_string_compatible?(args.first)
|
|
155
|
-
return nil unless args.drop(1).all? { |arg| literal_or_constant?(arg) }
|
|
153
|
+
# True when every tuple element is a `Constant` and the separator is absent or a `Constant<String>` —
|
|
154
|
+
# exactly the inputs for which `ShapeDispatch.tuple_join` materialises a precise `Constant<String>`.
|
|
155
|
+
# An empty tuple qualifies (`[].join` → `Constant<"">`). Callers have already verified every element
|
|
156
|
+
# is literal-string-compatible, so a `Constant` element is necessarily a `Constant<String>`.
|
|
157
|
+
def constant_join_reachable?(receiver, args)
|
|
158
|
+
return false unless receiver.elements.all?(Type::Constant)
|
|
159
|
+
return true if args.empty?
|
|
156
160
|
|
|
157
|
-
|
|
161
|
+
arg = args.first
|
|
162
|
+
arg.is_a?(Type::Constant) && arg.value.is_a?(String)
|
|
158
163
|
end
|
|
159
164
|
|
|
160
165
|
# `"foo %s" % "x"` / `"foo %s" % ["x", "y"]` — receiver is the template (already verified
|
|
@@ -217,7 +222,8 @@ module Rigor
|
|
|
217
222
|
end
|
|
218
223
|
|
|
219
224
|
private_class_method :fold_no_arg, :fold_concat, :fold_repeat, :fold_array_join,
|
|
220
|
-
:
|
|
225
|
+
:constant_join_reachable?,
|
|
226
|
+
:fold_string_percent, :fold_width_pad,
|
|
221
227
|
:non_empty_literal_result, :literal_or_constant?,
|
|
222
228
|
:integer_typed?, :known_negative_integer?,
|
|
223
229
|
:known_zero_integer?, :known_positive_integer?
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
3
5
|
require_relative "../../type"
|
|
6
|
+
require_relative "../../source/node_children"
|
|
4
7
|
|
|
5
8
|
module Rigor
|
|
6
9
|
module Inference
|
|
@@ -26,6 +29,36 @@ module Rigor
|
|
|
26
29
|
!scope.user_def_for(class_name, method_name).nil?
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
# The bare-local block-form counterpart of {#reader_overridden?}: when a `Data.define`/`Struct.new`
|
|
33
|
+
# block has no resolvable class name to look up in the project def-node table, its member-reader
|
|
34
|
+
# redefinitions are read straight off the block AST instead. True when the block body defines a
|
|
35
|
+
# `def <member>` (a plain instance method, receiver-less) for any member — folding that member's
|
|
36
|
+
# read would run the redefined reader, not return the member. Nested `class`/`module`/`sclass`
|
|
37
|
+
# bodies open a different namespace and are skipped; a `def` anywhere else in the body counts.
|
|
38
|
+
def block_redefines_member_reader?(block, members)
|
|
39
|
+
body = block.body
|
|
40
|
+
return false if body.nil?
|
|
41
|
+
|
|
42
|
+
member_set = members.is_a?(Set) ? members : members.to_set
|
|
43
|
+
reader_def?(body, member_set)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def reader_def?(node, member_set)
|
|
47
|
+
return false unless node.is_a?(Prism::Node)
|
|
48
|
+
return true if node.is_a?(Prism::DefNode) && node.receiver.nil? && member_set.include?(node.name)
|
|
49
|
+
# A nested class / module / singleton-class body opens a different namespace — its `def`s do not
|
|
50
|
+
# redefine the value object's reader, so the walk does not descend into it.
|
|
51
|
+
return false if reader_scope_boundary?(node)
|
|
52
|
+
|
|
53
|
+
node.rigor_each_child { |child| return true if reader_def?(child, member_set) }
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reader_scope_boundary?(node)
|
|
58
|
+
node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
|
|
59
|
+
node.is_a?(Prism::SingletonClassNode)
|
|
60
|
+
end
|
|
61
|
+
|
|
29
62
|
def instance_index(instance, args)
|
|
30
63
|
return nil unless args.size == 1
|
|
31
64
|
|
|
@@ -241,6 +241,9 @@ module Rigor
|
|
|
241
241
|
# would beat strictly-typed alternatives in pass 2 of the selector.
|
|
242
242
|
def strictly_typed_params?(method_type, actual_count)
|
|
243
243
|
fun = method_type.type
|
|
244
|
+
# A `(?)` method type declares no params at all: it is the gradual case by construction, so it
|
|
245
|
+
# must never win the strict pass over a genuinely typed sibling overload.
|
|
246
|
+
return false unless fun.respond_to?(:required_positionals)
|
|
244
247
|
return false unless arity_compatible?(fun, actual_count)
|
|
245
248
|
|
|
246
249
|
params = positional_params_for(fun, actual_count)
|
|
@@ -299,7 +302,11 @@ module Rigor
|
|
|
299
302
|
!fun.required_keywords.empty?
|
|
300
303
|
end
|
|
301
304
|
|
|
305
|
+
# `RBS::Types::UntypedFunction` (`(?)`) declares no arity to enforce, so every call site is
|
|
306
|
+
# arity-compatible with it.
|
|
302
307
|
def arity_compatible?(fun, actual_count)
|
|
308
|
+
return true unless fun.respond_to?(:required_positionals)
|
|
309
|
+
|
|
303
310
|
min_arity = fun.required_positionals.size + fun.trailing_positionals.size
|
|
304
311
|
return false if actual_count < min_arity
|
|
305
312
|
|
|
@@ -314,6 +321,10 @@ module Rigor
|
|
|
314
321
|
# Rest_positionals consumes the remainder; we repeat its single declaration for each absorbed
|
|
315
322
|
# argument.
|
|
316
323
|
def positional_params_for(fun, actual_count)
|
|
324
|
+
# `(?)` declares no positional params; the caller zips this against the actual args, so an empty
|
|
325
|
+
# list is what "accepts anything, constrains nothing" looks like here.
|
|
326
|
+
return [] unless fun.respond_to?(:required_positionals)
|
|
327
|
+
|
|
317
328
|
required = fun.required_positionals
|
|
318
329
|
optional = fun.optional_positionals
|
|
319
330
|
rest = fun.rest_positionals
|
|
@@ -4,6 +4,7 @@ require_relative "../../reflection"
|
|
|
4
4
|
require_relative "../../type"
|
|
5
5
|
require_relative "../../rbs_extended"
|
|
6
6
|
require_relative "../rbs_type_translator"
|
|
7
|
+
require_relative "../void_origin"
|
|
7
8
|
require_relative "overload_selector"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
@@ -96,7 +97,8 @@ module Rigor
|
|
|
96
97
|
block_type: context.block_type,
|
|
97
98
|
self_type_override: context.self_type_override,
|
|
98
99
|
public_only: context.public_only,
|
|
99
|
-
scope: context.scope
|
|
100
|
+
scope: context.scope,
|
|
101
|
+
call_node: context.call_node
|
|
100
102
|
)
|
|
101
103
|
end
|
|
102
104
|
|
|
@@ -135,23 +137,23 @@ module Rigor
|
|
|
135
137
|
private
|
|
136
138
|
|
|
137
139
|
def dispatch_for(receiver:, method_name:, args:, environment:, block_type:, self_type_override: nil, # rubocop:disable Metrics/ParameterLists
|
|
138
|
-
public_only: false, scope: nil)
|
|
140
|
+
public_only: false, scope: nil, call_node: nil)
|
|
139
141
|
args ||= []
|
|
140
142
|
case receiver
|
|
141
143
|
when Type::Union
|
|
142
144
|
dispatch_union(receiver, method_name, args, environment, block_type, self_type_override,
|
|
143
|
-
public_only: public_only, scope: scope)
|
|
145
|
+
public_only: public_only, scope: scope, call_node: call_node)
|
|
144
146
|
else
|
|
145
147
|
dispatch_one(receiver, method_name, args, environment, block_type, self_type_override,
|
|
146
|
-
public_only: public_only, scope: scope)
|
|
148
|
+
public_only: public_only, scope: scope, call_node: call_node)
|
|
147
149
|
end
|
|
148
150
|
end
|
|
149
151
|
|
|
150
152
|
def dispatch_union(receiver, method_name, args, environment, block_type, self_type_override = nil, # rubocop:disable Metrics/ParameterLists
|
|
151
|
-
public_only: false, scope: nil)
|
|
153
|
+
public_only: false, scope: nil, call_node: nil)
|
|
152
154
|
results = receiver.members.map do |member|
|
|
153
155
|
dispatch_one(member, method_name, args, environment, block_type, self_type_override,
|
|
154
|
-
public_only: public_only, scope: scope)
|
|
156
|
+
public_only: public_only, scope: scope, call_node: call_node)
|
|
155
157
|
end
|
|
156
158
|
return nil if results.any?(&:nil?)
|
|
157
159
|
|
|
@@ -159,7 +161,7 @@ module Rigor
|
|
|
159
161
|
end
|
|
160
162
|
|
|
161
163
|
def dispatch_one(receiver, method_name, args, environment, block_type, self_type_override = nil, # rubocop:disable Metrics/ParameterLists
|
|
162
|
-
public_only: false, scope: nil)
|
|
164
|
+
public_only: false, scope: nil, call_node: nil)
|
|
163
165
|
descriptor = receiver_descriptor(receiver)
|
|
164
166
|
return nil unless descriptor
|
|
165
167
|
|
|
@@ -173,11 +175,14 @@ module Rigor
|
|
|
173
175
|
method_definition,
|
|
174
176
|
class_name: class_name,
|
|
175
177
|
kind: kind,
|
|
178
|
+
method_name: method_name,
|
|
176
179
|
args: args,
|
|
177
180
|
type_vars: type_vars,
|
|
178
181
|
block_type: block_type,
|
|
179
182
|
environment: environment,
|
|
180
|
-
self_type_override: self_type_override
|
|
183
|
+
self_type_override: self_type_override,
|
|
184
|
+
scope: scope,
|
|
185
|
+
call_node: call_node
|
|
181
186
|
)
|
|
182
187
|
rescue StandardError
|
|
183
188
|
# Defensive: if RBS' definition builder raises on a broken hierarchy (e.g., partially loaded
|
|
@@ -318,7 +323,8 @@ module Rigor
|
|
|
318
323
|
|
|
319
324
|
# rubocop:disable Metrics/ParameterLists
|
|
320
325
|
def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:,
|
|
321
|
-
environment: nil, self_type_override: nil
|
|
326
|
+
method_name: nil, environment: nil, self_type_override: nil,
|
|
327
|
+
scope: nil, call_node: nil)
|
|
322
328
|
# rubocop:enable Metrics/ParameterLists
|
|
323
329
|
# Slice 4b-3 (ADR-7 § "Slice 4-A/4-B") — read the return-type override through the merger so
|
|
324
330
|
# future plugin / `:rbs_extended` bundles that also assert a `return_type` slot at this call
|
|
@@ -348,6 +354,15 @@ module Rigor
|
|
|
348
354
|
)
|
|
349
355
|
return nil unless method_type
|
|
350
356
|
|
|
357
|
+
# ADR-100 WD2/WD3 — the return-typing tier is where `void → top` widens, so it is the one place
|
|
358
|
+
# that still knows the RBS return was an author-declared `-> void` before the translator erases
|
|
359
|
+
# it to a plain `top`. Record the recovery on the scope's `void_origins` side-table, keyed by the
|
|
360
|
+
# call node, so the `static.value-use.void` check rule can fire when this `top` is used in value
|
|
361
|
+
# context. Recording is gated on `scope` && `call_node` being present, which naturally scopes it
|
|
362
|
+
# to the *direct*-RBS dispatch (the receiver's own resolvable class): the user-class / Object
|
|
363
|
+
# ancestor fallback nils both out (WD4 defers that, murkier, surface).
|
|
364
|
+
record_void_recovery(method_type, scope, call_node, [class_name, method_name, kind])
|
|
365
|
+
|
|
351
366
|
full_type_vars = compose_block_type_vars(method_type, type_vars, block_type)
|
|
352
367
|
|
|
353
368
|
RbsTypeTranslator.translate(
|
|
@@ -358,6 +373,24 @@ module Rigor
|
|
|
358
373
|
)
|
|
359
374
|
end
|
|
360
375
|
|
|
376
|
+
# Record the `void → top` recovery when the selected overload declares `-> void` and both `scope` and
|
|
377
|
+
# `call_node` are present (the direct-dispatch path). `void_site` is the `[class_name, method_name,
|
|
378
|
+
# kind]` triple the {VoidOrigin} carries.
|
|
379
|
+
def record_void_recovery(method_type, scope, call_node, void_site)
|
|
380
|
+
return unless scope && call_node && void_return?(method_type)
|
|
381
|
+
|
|
382
|
+
class_name, method_name, kind = void_site
|
|
383
|
+
scope.record_void_origin(
|
|
384
|
+
call_node,
|
|
385
|
+
VoidOrigin.new(class_name: class_name, method_name: method_name, kind: kind)
|
|
386
|
+
)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def void_return?(method_type)
|
|
390
|
+
fun = method_type.type
|
|
391
|
+
fun.respond_to?(:return_type) && fun.return_type.is_a?(RBS::Types::Bases::Void)
|
|
392
|
+
end
|
|
393
|
+
|
|
361
394
|
# ADR-7 § "Slice 4-A/4-B" — folds the `RBS::Extended` `return:` directive (and any other
|
|
362
395
|
# `return_type`-bearing contribution future slices add at this call site) through the merger
|
|
363
396
|
# before consuming. Returns the merged return type or nil when no contribution overrides the
|
|
@@ -41,8 +41,13 @@ module Rigor
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# `RBS::Types::UntypedFunction` (a `(?)` method type, e.g. core's `Proc#call`) declares no
|
|
45
|
+
# parameters and exposes none of the per-arity accessors. It has no param classes to compare
|
|
46
|
+
# against the receiver, so it carries no affinity either way.
|
|
44
47
|
def overload_param_classes_in_ancestry?(method_type, self_class_name, environment)
|
|
45
48
|
fun = method_type.type
|
|
49
|
+
return false unless fun.respond_to?(:required_positionals)
|
|
50
|
+
|
|
46
51
|
params = fun.required_positionals + fun.optional_positionals + fun.trailing_positionals
|
|
47
52
|
return false if params.empty?
|
|
48
53
|
|