rigortype 0.1.19 → 0.2.1
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 +41 -6
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules.rb +492 -71
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
- data/lib/rigor/analysis/runner.rb +17 -6
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +10 -14
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +28 -7
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +138 -16
- data/lib/rigor/cli/coverage_command.rb +138 -31
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration.rb +57 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +76 -5
- data/lib/rigor/environment.rb +66 -8
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +20 -28
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +169 -24
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +40 -48
- data/lib/rigor/inference/mutation_widening.rb +5 -11
- data/lib/rigor/inference/narrowing.rb +14 -16
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +129 -55
- data/lib/rigor/inference/statement_evaluator.rb +271 -114
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +10 -8
- data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +4 -5
- data/lib/rigor/plugin/manifest.rb +45 -66
- data/lib/rigor/plugin/registry.rb +6 -7
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +180 -0
- data/lib/rigor/protection/mutator.rb +267 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +14 -2
- data/lib/rigor/scope.rb +54 -11
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +16 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +19 -14
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
- data/sig/rigor/scope.rbs +9 -1
- data/sig/rigor/type.rbs +36 -1
- metadata +49 -1
|
@@ -249,6 +249,12 @@ module Rigor
|
|
|
249
249
|
# anonymous local-bound form projects to `Data` itself).
|
|
250
250
|
accepts(self_type, project_data_instance_to_nominal(other_type), mode: mode)
|
|
251
251
|
.with_reason("projected DataInstance to Nominal[#{other_type.class_name || 'Data'}]")
|
|
252
|
+
when Type::StructInstance
|
|
253
|
+
# ADR-48 Struct follow-up: same projection as DataInstance — a
|
|
254
|
+
# class-tagged Struct value is exactly one value of its tagging
|
|
255
|
+
# class (the anonymous form projects to `Struct` itself).
|
|
256
|
+
accepts(self_type, project_struct_instance_to_nominal(other_type), mode: mode)
|
|
257
|
+
.with_reason("projected StructInstance to Nominal[#{other_type.class_name || 'Struct'}]")
|
|
252
258
|
when Type::Difference, Type::Refined
|
|
253
259
|
# A refinement carrier's value set is a subset of its
|
|
254
260
|
# base. So if `self` (Nominal) accepts the base, it
|
|
@@ -386,6 +392,10 @@ module Rigor
|
|
|
386
392
|
Type::Combinator.nominal_of(instance.class_name || "Data")
|
|
387
393
|
end
|
|
388
394
|
|
|
395
|
+
def project_struct_instance_to_nominal(instance)
|
|
396
|
+
Type::Combinator.nominal_of(instance.class_name || "Struct")
|
|
397
|
+
end
|
|
398
|
+
|
|
389
399
|
def project_hash_shape_to_nominal(shape)
|
|
390
400
|
return Type::Combinator.nominal_of(Hash) if shape.pairs.empty?
|
|
391
401
|
|
|
@@ -822,15 +832,13 @@ module Rigor
|
|
|
822
832
|
Type::AcceptsResult.no(mode: mode, reasons: reason)
|
|
823
833
|
end
|
|
824
834
|
|
|
825
|
-
#
|
|
826
|
-
# "is D a subclass of C?"
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
# so ahead-of-time type checking no longer relies on Ruby
|
|
833
|
-
# loading the application classes.
|
|
835
|
+
# Uses Ruby's actual class hierarchy via Object.const_get to answer
|
|
836
|
+
# "is D a subclass of C?" for core, stdlib, and application classes.
|
|
837
|
+
# When either name fails to resolve we surface "maybe": the caller
|
|
838
|
+
# (overload selector) treats yes/maybe identically, so the conservative
|
|
839
|
+
# answer keeps overload coverage intact. RbsHierarchy exists but this
|
|
840
|
+
# path does not yet consult it; migration to an RBS-driven lookup
|
|
841
|
+
# is deferred.
|
|
834
842
|
def class_subtype_result(target_name:, actual_name:, mode:, kind:)
|
|
835
843
|
return Type::AcceptsResult.yes(mode: mode, reasons: "exact name match") if target_name == actual_name
|
|
836
844
|
|
|
@@ -183,9 +183,8 @@ module Rigor
|
|
|
183
183
|
# `|*rest|` binds an Array of the leftover positional arguments.
|
|
184
184
|
# The expected-types array is per-position, not per-rest; we
|
|
185
185
|
# cannot reliably pick a single element type for rest, so we
|
|
186
|
-
# default to `Array[Dynamic[Top]]`.
|
|
187
|
-
#
|
|
188
|
-
# available.
|
|
186
|
+
# default to `Array[Dynamic[Top]]`. Element-type precision for
|
|
187
|
+
# rest parameters is deferred (demand-gated).
|
|
189
188
|
def bind_rest(params_node, bindings)
|
|
190
189
|
rest = params_node.rest
|
|
191
190
|
return unless rest.respond_to?(:name) && rest&.name
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# catalog is NOT routed through
|
|
12
12
|
# `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
|
-
# The data is
|
|
15
|
-
#
|
|
14
|
+
# The data is wired into `MODULE_CATALOGS` in
|
|
15
|
+
# `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
|
|
16
16
|
COMPARABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
17
|
"comparable",
|
|
18
18
|
mutating_selectors: {
|
|
@@ -11,8 +11,8 @@ module Rigor
|
|
|
11
11
|
# catalog is NOT routed through
|
|
12
12
|
# `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS`
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
|
-
# The data is
|
|
15
|
-
#
|
|
14
|
+
# The data is wired into `MODULE_CATALOGS` in
|
|
15
|
+
# `MethodDispatcher::ConstantFolding` (ancestor-chain lookup).
|
|
16
16
|
ENUMERABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
17
|
"enumerable",
|
|
18
18
|
mutating_selectors: {
|
|
@@ -27,6 +27,24 @@ module Rigor
|
|
|
27
27
|
FOLDABLE_PURITIES = Set["leaf", "trivial", "leaf_when_numeric"].freeze
|
|
28
28
|
EMPTY_CATALOG = { "classes" => {} }.freeze
|
|
29
29
|
|
|
30
|
+
# Selectors that are classified `:leaf` by the C-body analysis
|
|
31
|
+
# (they read no global mutable state in the C sense) but whose
|
|
32
|
+
# result is NOT reproducible across Ruby processes, so they must
|
|
33
|
+
# never be folded into a `Constant`:
|
|
34
|
+
#
|
|
35
|
+
# - `hash` — every core `#hash` (`String`/`Symbol`/`Integer`/
|
|
36
|
+
# `Float`/…) is salted with a per-process SipHash seed, so
|
|
37
|
+
# `"x".hash` differs in every process. Folding bakes one
|
|
38
|
+
# process's value into the type and the on-disk cache.
|
|
39
|
+
# - `object_id` / `__id__` — identity-allocated per process.
|
|
40
|
+
#
|
|
41
|
+
# This is a UNIVERSAL block (across every catalogued class)
|
|
42
|
+
# because `hash` / `object_id` are `Object`-level and present on
|
|
43
|
+
# every receiver; a per-class blocklist would silently miss a
|
|
44
|
+
# class. The deterministic siblings (`inspect`, `to_s`) are
|
|
45
|
+
# unaffected.
|
|
46
|
+
NON_REPRODUCIBLE_SELECTORS = Set[:hash, :object_id, :__id__].freeze
|
|
47
|
+
|
|
30
48
|
# Shared root for the offline-generated catalogues. Resolving it
|
|
31
49
|
# here keeps the repo-relative `../../../../` hop in one place
|
|
32
50
|
# instead of copying it into every per-topic loader.
|
|
@@ -59,6 +77,7 @@ module Rigor
|
|
|
59
77
|
|
|
60
78
|
def safe_for_folding?(class_name, selector, kind: :instance)
|
|
61
79
|
class_name_str = class_name.to_s
|
|
80
|
+
return false if NON_REPRODUCIBLE_SELECTORS.include?(selector.to_sym)
|
|
62
81
|
return false if blocked?(class_name_str, selector)
|
|
63
82
|
|
|
64
83
|
entry = method_entry(class_name_str, selector, kind: kind)
|
|
@@ -22,7 +22,15 @@ module Rigor
|
|
|
22
22
|
:replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
|
|
23
23
|
:prepend, :force_encoding, :encode, :scrub, :unicode_normalize, :"[]=",
|
|
24
24
|
:upto, :each_byte, :each_char, :each_codepoint,
|
|
25
|
-
:each_grapheme_cluster, :each_line, :bytesplice
|
|
25
|
+
:each_grapheme_cluster, :each_line, :bytesplice,
|
|
26
|
+
# `crypt` is not a mutator but is blocked from folding for the
|
|
27
|
+
# same "do not bake a non-pure result into a Constant" reason:
|
|
28
|
+
# `rb_str_crypt` delegates to the platform `crypt(3)`, whose
|
|
29
|
+
# output (algorithm and digest) varies by libc / OS, so
|
|
30
|
+
# `"x".crypt("ab")` is not deterministic across the platforms
|
|
31
|
+
# an analyzed project may target. The catalog classifies it
|
|
32
|
+
# `:leaf` from its C body; this entry overrides that.
|
|
33
|
+
:crypt
|
|
26
34
|
],
|
|
27
35
|
"Symbol" => Set[
|
|
28
36
|
# Symbol is immutable in Ruby; the classifier mis-flags
|
|
@@ -4,6 +4,7 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "../ast"
|
|
7
|
+
require_relative "../source/constant_path"
|
|
7
8
|
require_relative "../analysis/self_call_resolution_recorder"
|
|
8
9
|
require_relative "block_parameter_binder"
|
|
9
10
|
require_relative "body_fixpoint"
|
|
@@ -14,6 +15,7 @@ require_relative "indexed_narrowing"
|
|
|
14
15
|
require_relative "macro_block_self_type"
|
|
15
16
|
require_relative "method_dispatcher"
|
|
16
17
|
require_relative "narrowing"
|
|
18
|
+
require_relative "struct_fold_safety"
|
|
17
19
|
|
|
18
20
|
module Rigor
|
|
19
21
|
module Inference
|
|
@@ -323,12 +325,7 @@ module Rigor
|
|
|
323
325
|
dynamic_top
|
|
324
326
|
end
|
|
325
327
|
|
|
326
|
-
#
|
|
327
|
-
# narrow: self, instance/class/global variable reads, block bodies.
|
|
328
|
-
# Slice 3+ refines these in place; for now we acknowledge the node
|
|
329
|
-
# class so the coverage scanner stops flagging it without recording
|
|
330
|
-
# a fail-soft event for every occurrence.
|
|
331
|
-
# Slice A-engine. `Prism::SelfNode` resolves to the scope's
|
|
328
|
+
# `Prism::SelfNode` resolves to the scope's
|
|
332
329
|
# `self_type` when one has been injected (by
|
|
333
330
|
# `StatementEvaluator` at class-body and method-body
|
|
334
331
|
# boundaries) or `Dynamic[Top]` at the top level. Class-body
|
|
@@ -412,7 +409,7 @@ module Rigor
|
|
|
412
409
|
end
|
|
413
410
|
|
|
414
411
|
def type_of_constant_path(node)
|
|
415
|
-
full_name =
|
|
412
|
+
full_name = Source::ConstantPath.qualified_name_or_nil(node)
|
|
416
413
|
return fallback_for(node, family: :prism) if full_name.nil?
|
|
417
414
|
|
|
418
415
|
resolve_constant_name(full_name) || fallback_for(node, family: :prism)
|
|
@@ -481,24 +478,6 @@ module Rigor
|
|
|
481
478
|
end
|
|
482
479
|
end
|
|
483
480
|
|
|
484
|
-
# Builds the dotted-colon name for a `Foo`, `Foo::Bar`, or `::Foo`
|
|
485
|
-
# path. Returns nil when an inner segment is not itself a constant
|
|
486
|
-
# reference (for example `expr::Foo`), so the caller can fall back.
|
|
487
|
-
def build_constant_path_name(node)
|
|
488
|
-
case node
|
|
489
|
-
when Prism::ConstantReadNode
|
|
490
|
-
node.name.to_s
|
|
491
|
-
when Prism::ConstantPathNode
|
|
492
|
-
parent = node.parent
|
|
493
|
-
return node.name.to_s if parent.nil?
|
|
494
|
-
|
|
495
|
-
parent_name = build_constant_path_name(parent)
|
|
496
|
-
return nil if parent_name.nil?
|
|
497
|
-
|
|
498
|
-
"#{parent_name}::#{node.name}"
|
|
499
|
-
end
|
|
500
|
-
end
|
|
501
|
-
|
|
502
481
|
# Slice 5 phase 1 upgrades hash literals to `HashShape{...}`
|
|
503
482
|
# when every entry is a static `AssocNode` whose key is a
|
|
504
483
|
# `SymbolNode` or `StringNode` with a known value (covering the
|
|
@@ -837,7 +816,7 @@ module Rigor
|
|
|
837
816
|
# Other pattern shapes (Range, Regexp, custom `===`) stay
|
|
838
817
|
# `:maybe` — the existing union fallback handles them.
|
|
839
818
|
def case_when_pattern_certainty(subject_type, pattern_node)
|
|
840
|
-
class_name =
|
|
819
|
+
class_name = Source::ConstantPath.qualified_name_or_nil(pattern_node)
|
|
841
820
|
return Narrowing.class_pattern_certainty(subject_type, class_name, environment: scope.environment) if class_name
|
|
842
821
|
|
|
843
822
|
literal = literal_pattern_value(pattern_node)
|
|
@@ -925,7 +904,8 @@ module Rigor
|
|
|
925
904
|
end
|
|
926
905
|
|
|
927
906
|
# `while` and `until` loops produce nil unless interrupted by
|
|
928
|
-
# `break VALUE
|
|
907
|
+
# `break VALUE`; the expression value of `break VALUE` is not yet
|
|
908
|
+
# modeled (scope break-path propagation landed in `eval_loop`).
|
|
929
909
|
# Returning Constant[nil] is safe and matches Ruby semantics for
|
|
930
910
|
# the common case.
|
|
931
911
|
def type_of_loop(_node)
|
|
@@ -2382,7 +2362,19 @@ module Rigor
|
|
|
2382
2362
|
environment: scope.environment,
|
|
2383
2363
|
locals: locals.freeze,
|
|
2384
2364
|
self_type: receiver,
|
|
2385
|
-
discovery: scope.discovery
|
|
2365
|
+
discovery: scope.discovery,
|
|
2366
|
+
struct_fold_safe_locals: struct_fold_safe_locals_for(def_node.body)
|
|
2367
|
+
)
|
|
2368
|
+
end
|
|
2369
|
+
|
|
2370
|
+
# ADR-48 Struct slice 3 — the fold-safe-local set for a method body
|
|
2371
|
+
# (runs only on a return-memo miss, so the per-call cost is bounded —
|
|
2372
|
+
# measured perf-neutral). Struct member layouts of constant receivers
|
|
2373
|
+
# are resolved through the discovery side-table the body scope inherits.
|
|
2374
|
+
def struct_fold_safe_locals_for(body)
|
|
2375
|
+
StructFoldSafety.fold_safe_locals(
|
|
2376
|
+
body,
|
|
2377
|
+
->(name) { scope.struct_member_layout(name)&.[](:members) }
|
|
2386
2378
|
)
|
|
2387
2379
|
end
|
|
2388
2380
|
|
|
@@ -7,18 +7,15 @@ module Rigor
|
|
|
7
7
|
# piece of a Rigor-side type expression that the reducer
|
|
8
8
|
# ({HktReducer}) walks against a concrete argument list.
|
|
9
9
|
#
|
|
10
|
-
# Slice 2a ships a programmatic constructor surface
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# `body` String stays opaque and `body_tree` is the
|
|
17
|
-
# evaluable form.
|
|
10
|
+
# Slice 2a ships a programmatic constructor surface; plugin
|
|
11
|
+
# and Rigor-bundled overlay authors may build a body tree
|
|
12
|
+
# by hand using these node types. The string-grammar parser
|
|
13
|
+
# (`HktBodyParser`, Slice 2b, shipped) reads `Definition#body`
|
|
14
|
+
# (populated by Slice 1's `HktDirectives.parse_define`) into
|
|
15
|
+
# this node tree; `body_tree` is the evaluable form.
|
|
18
16
|
#
|
|
19
|
-
# The
|
|
20
|
-
#
|
|
21
|
-
# near-term adopters:
|
|
17
|
+
# The nine node types cover JSON.parse, dry-monads, and the
|
|
18
|
+
# ADR-20 § D3 conditional / membership forms (shipped):
|
|
22
19
|
#
|
|
23
20
|
# - {TypeLeaf} — wraps a fully-built `Rigor::Type`
|
|
24
21
|
# (use for atoms like `nil`, `Constant<true>`,
|
|
@@ -11,29 +11,27 @@ module Rigor
|
|
|
11
11
|
# `%a{rigor:v1:hkt_define}` payloads) into the `HktBody`
|
|
12
12
|
# node tree the Slice 2a reducer evaluates against.
|
|
13
13
|
#
|
|
14
|
-
# The
|
|
15
|
-
# union-of-atoms
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# without conditionals). The conditional / indexed-access
|
|
20
|
-
# forms (`E <: T ? A : B`, `E in [k1, k2]`) drafted in D3
|
|
21
|
-
# remain a follow-up slice — bodies that contain `?`
|
|
22
|
-
# raise `ParseError` and the calling directive parser
|
|
23
|
-
# drops the body_tree (the body String remains stored and
|
|
24
|
-
# the reducer falls back to `app.bound`).
|
|
14
|
+
# The grammar implements the full ADR-20 § D3 subset:
|
|
15
|
+
# union-of-atoms/parameterised-forms for `JSON.parse`'s
|
|
16
|
+
# `json::value` recursive sum, plus the conditional and
|
|
17
|
+
# membership forms shipped in subsequent slices. Indexed-access
|
|
18
|
+
# forms remain deferred (no concrete demand yet).
|
|
25
19
|
#
|
|
26
|
-
# ## Grammar
|
|
20
|
+
# ## Grammar
|
|
27
21
|
#
|
|
28
22
|
# body := union
|
|
29
23
|
# union := type_expr ("|" type_expr)*
|
|
30
24
|
# type_expr := atom | nominal_app | app_ref | param
|
|
25
|
+
# | conditional
|
|
31
26
|
# atom := "nil" | "true" | "false" | "bool" | "untyped"
|
|
32
27
|
# param := UCNAME (when UCNAME ∈ params)
|
|
33
28
|
# nominal_app := class_name ("[" type_expr ("," type_expr)* "]")?
|
|
34
29
|
# class_name := "::"? UCNAME ("::" UCNAME)*
|
|
35
30
|
# app_ref := "App" "[" uri "," type_expr ("," type_expr)* "]"
|
|
36
31
|
# uri := IDENT ("::" IDENT)+
|
|
32
|
+
# conditional := "(" test "?" union ":" union ")"
|
|
33
|
+
# test := type_expr ("<:" | "==") type_expr
|
|
34
|
+
# | type_expr "in" "[" type_expr ("," type_expr)* "]"
|
|
37
35
|
# UCNAME := /[A-Z]\w*/
|
|
38
36
|
# IDENT := /[a-z_]\w*/
|
|
39
37
|
#
|
|
@@ -10,12 +10,12 @@ module Rigor
|
|
|
10
10
|
# `%a{rigor:v1:hkt_define: ...}` annotations in shipped
|
|
11
11
|
# `.rbs` files.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
13
|
+
# The registry stores registration metadata (arity, variance,
|
|
14
|
+
# bound) and the definition body as both a raw String and an
|
|
15
|
+
# evaluable `HktBody` node tree. `HktReducer` (Slice 2a) and
|
|
16
|
+
# `HktBodyParser` (Slice 2b) are both shipped and reduce
|
|
17
|
+
# `Type::App` instances against the definition. The carrier
|
|
18
|
+
# never needs to read from the registry
|
|
19
19
|
# because Slice 1's `Type::App` carries its `bound` directly;
|
|
20
20
|
# the registry exists at this slice solely so the parser
|
|
21
21
|
# round-trip and downstream slices have a stable target API.
|
|
@@ -65,16 +65,15 @@ module Rigor
|
|
|
65
65
|
# definition.
|
|
66
66
|
#
|
|
67
67
|
# `body` is the raw String payload from the `%a{...}`
|
|
68
|
-
# annotation (Slice 1's parser populates it)
|
|
69
|
-
#
|
|
68
|
+
# annotation (Slice 1's parser populates it); parsed into
|
|
69
|
+
# `body_tree` by `HktBodyParser` (Slice 2b, shipped).
|
|
70
70
|
#
|
|
71
|
-
# `body_tree` is the
|
|
71
|
+
# `body_tree` is the evaluable form: a
|
|
72
72
|
# `Rigor::Inference::HktBody::*` node tree the Slice 2a
|
|
73
73
|
# reducer walks against the application's concrete
|
|
74
74
|
# arguments. Plugin and Rigor-bundled overlay authors
|
|
75
75
|
# construct it programmatically through
|
|
76
|
-
# {
|
|
77
|
-
# it from `body` once it ships. The reducer treats a
|
|
76
|
+
# {.definition_with_body_tree}. The reducer treats a
|
|
78
77
|
# `nil` `body_tree` as "definition not yet evaluable"
|
|
79
78
|
# and returns the registered bound.
|
|
80
79
|
Definition = Data.define(:uri, :params, :body, :body_tree, :source_path, :source_line) do
|
|
@@ -42,10 +42,7 @@ module Rigor
|
|
|
42
42
|
#
|
|
43
43
|
# This is the single place the call-context field list is
|
|
44
44
|
# enumerated — the whole point of the value object is to absorb
|
|
45
|
-
# the wide keyword list the tiers used to each redeclare.
|
|
46
|
-
# ParameterLists disable here retires the per-tier disables (the
|
|
47
|
-
# `RbsDispatch` quartet-plus signatures) rather than adding to
|
|
48
|
-
# them.
|
|
45
|
+
# the wide keyword list the tiers used to each redeclare.
|
|
49
46
|
def self.build(receiver:, method_name:, args:, # rubocop:disable Metrics/ParameterLists
|
|
50
47
|
block_type: nil, environment: nil, call_node: nil,
|
|
51
48
|
scope: nil, self_type_override: nil, public_only: false)
|