rigortype 0.0.9 → 0.1.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 +45 -2
- data/data/builtins/ruby_core/array.yml +6 -6
- data/data/builtins/ruby_core/hash.yml +1 -1
- data/data/builtins/ruby_core/io.yml +3 -3
- data/data/builtins/ruby_core/numeric.yml +1 -1
- data/data/builtins/ruby_core/pathname.yml +100 -100
- data/data/builtins/ruby_core/proc.yml +1 -1
- data/data/builtins/ruby_core/time.yml +3 -3
- data/lib/rigor/analysis/check_rules.rb +228 -40
- data/lib/rigor/analysis/diagnostic.rb +15 -1
- data/lib/rigor/analysis/runner.rb +269 -7
- data/lib/rigor/builtins/regex_refinement.rb +104 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +1 -1
- data/lib/rigor/cache/rbs_constant_table.rb +2 -2
- data/lib/rigor/cache/rbs_descriptor.rb +2 -0
- data/lib/rigor/cache/rbs_instance_definitions.rb +79 -0
- data/lib/rigor/cache/store.rb +2 -0
- data/lib/rigor/cli/type_of_command.rb +3 -3
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli.rb +20 -7
- data/lib/rigor/configuration/severity_profile.rb +109 -0
- data/lib/rigor/configuration.rb +286 -15
- data/lib/rigor/environment/rbs_loader.rb +89 -13
- data/lib/rigor/environment.rb +12 -4
- data/lib/rigor/flow_contribution/conflict.rb +81 -0
- data/lib/rigor/flow_contribution/element.rb +53 -0
- data/lib/rigor/flow_contribution/fact.rb +88 -0
- data/lib/rigor/flow_contribution/merge_result.rb +67 -0
- data/lib/rigor/flow_contribution/merger.rb +275 -0
- data/lib/rigor/flow_contribution.rb +51 -0
- data/lib/rigor/inference/block_parameter_binder.rb +15 -0
- data/lib/rigor/inference/expression_typer.rb +87 -6
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +31 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +136 -9
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +21 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +68 -2
- data/lib/rigor/inference/method_dispatcher.rb +50 -1
- data/lib/rigor/inference/multi_target_binder.rb +2 -0
- data/lib/rigor/inference/narrowing.rb +246 -127
- data/lib/rigor/inference/scope_indexer.rb +124 -16
- data/lib/rigor/inference/statement_evaluator.rb +406 -37
- data/lib/rigor/plugin/access_denied_error.rb +24 -0
- data/lib/rigor/plugin/base.rb +284 -0
- data/lib/rigor/plugin/fact_store.rb +92 -0
- data/lib/rigor/plugin/io_boundary.rb +102 -0
- data/lib/rigor/plugin/load_error.rb +35 -0
- data/lib/rigor/plugin/loader.rb +307 -0
- data/lib/rigor/plugin/manifest.rb +203 -0
- data/lib/rigor/plugin/registry.rb +50 -0
- data/lib/rigor/plugin/services.rb +77 -0
- data/lib/rigor/plugin/trust_policy.rb +99 -0
- data/lib/rigor/plugin.rb +62 -0
- data/lib/rigor/rbs_extended.rb +57 -9
- data/lib/rigor/reflection.rb +2 -2
- data/lib/rigor/trinary.rb +1 -1
- data/lib/rigor/type/integer_range.rb +6 -2
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +7 -0
- data/sig/rigor/environment.rbs +10 -3
- data/sig/rigor/inference.rbs +1 -0
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +7 -0
- data/sig/rigor.rbs +8 -2
- metadata +20 -1
|
@@ -7,6 +7,7 @@ require_relative "../type"
|
|
|
7
7
|
require_relative "../environment"
|
|
8
8
|
require_relative "../rbs_extended"
|
|
9
9
|
require_relative "../analysis/fact_store"
|
|
10
|
+
require_relative "../builtins/regex_refinement"
|
|
10
11
|
|
|
11
12
|
module Rigor
|
|
12
13
|
module Inference
|
|
@@ -293,6 +294,29 @@ module Rigor
|
|
|
293
294
|
end
|
|
294
295
|
end
|
|
295
296
|
|
|
297
|
+
# ADR-7 § "Slice 4-A" — public Fact-shaped narrowing
|
|
298
|
+
# entry. Distinguishes a `Nominal[<class>]`-typed Fact
|
|
299
|
+
# (uses `narrow_class` / `narrow_not_class` for
|
|
300
|
+
# hierarchy-aware narrowing) from a refinement-shaped
|
|
301
|
+
# Fact (refined types, IntegerRange, Difference, …).
|
|
302
|
+
# The implementation lives next to its sibling helpers
|
|
303
|
+
# `narrow_class` and `narrow_not_refinement`; consumers
|
|
304
|
+
# outside `Narrowing` (today: `StatementEvaluator`'s
|
|
305
|
+
# post-return assertion path) reach for it via
|
|
306
|
+
# `Rigor::Inference::Narrowing.narrow_for_fact`.
|
|
307
|
+
def narrow_for_fact(current, fact, environment)
|
|
308
|
+
if fact.type.is_a?(Type::Nominal) && fact.type.type_args.empty?
|
|
309
|
+
class_name = fact.type.class_name
|
|
310
|
+
return narrow_not_class(current, class_name, exact: false, environment: environment) if fact.negative?
|
|
311
|
+
|
|
312
|
+
return narrow_class(current, class_name, exact: false, environment: environment)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
return narrow_not_refinement(current, fact.type) if fact.negative?
|
|
316
|
+
|
|
317
|
+
fact.type
|
|
318
|
+
end
|
|
319
|
+
|
|
296
320
|
# Public predicate analyser. Returns `[truthy_scope, falsey_scope]`,
|
|
297
321
|
# always; when no narrowing rule matches the predicate node both
|
|
298
322
|
# entries are the receiver scope unchanged.
|
|
@@ -352,7 +376,7 @@ module Rigor
|
|
|
352
376
|
# the predicate shape is recognised, or `nil` to signal "no
|
|
353
377
|
# narrowing" so the public surface can fall back to the entry
|
|
354
378
|
# scope.
|
|
355
|
-
def analyse(node, scope)
|
|
379
|
+
def analyse(node, scope) # rubocop:disable Metrics/CyclomaticComplexity
|
|
356
380
|
case node
|
|
357
381
|
when Prism::ParenthesesNode
|
|
358
382
|
analyse_parentheses(node, scope)
|
|
@@ -366,6 +390,8 @@ module Rigor
|
|
|
366
390
|
analyse_and(node, scope)
|
|
367
391
|
when Prism::OrNode
|
|
368
392
|
analyse_or(node, scope)
|
|
393
|
+
when Prism::MatchWriteNode
|
|
394
|
+
analyse_match_write(node, scope)
|
|
369
395
|
end
|
|
370
396
|
end
|
|
371
397
|
|
|
@@ -712,6 +738,60 @@ module Rigor
|
|
|
712
738
|
]
|
|
713
739
|
end
|
|
714
740
|
|
|
741
|
+
# `if /(?<x>...)/ =~ str` — Prism wraps the `=~` call in a
|
|
742
|
+
# `MatchWriteNode` listing the named-capture targets. The
|
|
743
|
+
# parent `eval_match_write` has already bound each target
|
|
744
|
+
# to `String | nil`; in the truthy branch (the regex
|
|
745
|
+
# matched) every named capture is guaranteed `String`,
|
|
746
|
+
# and in the falsey branch (no match) every capture is
|
|
747
|
+
# `nil`. Subtract the dead half on each edge so callers
|
|
748
|
+
# like `year.upcase` inside the truthy branch no longer
|
|
749
|
+
# fire `possible-nil-receiver`.
|
|
750
|
+
#
|
|
751
|
+
# v0.1.1 Track 1 slice 1 — when the regex source is a
|
|
752
|
+
# statically known `RegularExpressionNode` and a named
|
|
753
|
+
# capture's body matches one of the curated shapes in
|
|
754
|
+
# {Rigor::Builtins::RegexRefinement::RULES}, the truthy
|
|
755
|
+
# branch narrows further than `String` to the matching
|
|
756
|
+
# imported refinement (e.g. `decimal-int-string` for
|
|
757
|
+
# `\d+`). Bodies outside the table fall back to the
|
|
758
|
+
# v0.1.0 baseline (plain `String`).
|
|
759
|
+
def analyse_match_write(node, scope)
|
|
760
|
+
string_t = Type::Combinator.nominal_of("String")
|
|
761
|
+
nil_t = Type::Combinator.constant_of(nil)
|
|
762
|
+
refinements = match_write_capture_refinements(node)
|
|
763
|
+
truthy = scope
|
|
764
|
+
falsey = scope
|
|
765
|
+
node.targets.each do |target|
|
|
766
|
+
next unless target.is_a?(Prism::LocalVariableTargetNode)
|
|
767
|
+
|
|
768
|
+
truthy = truthy.with_local(target.name, refinements[target.name] || string_t)
|
|
769
|
+
falsey = falsey.with_local(target.name, nil_t)
|
|
770
|
+
end
|
|
771
|
+
[truthy, falsey]
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# Extracts `{ capture_name => Refinement }` for every named
|
|
775
|
+
# capture group in the `MatchWriteNode`'s wrapped `=~` call
|
|
776
|
+
# whose body the recogniser table accepts. Bodies that
|
|
777
|
+
# contain nested groups, anchors, alternation, or anything
|
|
778
|
+
# else outside the curated forms drop out and the caller
|
|
779
|
+
# falls back to plain `String`.
|
|
780
|
+
NAMED_CAPTURE_BODY_RE = /\(\?<([A-Za-z_][A-Za-z0-9_]*)>([^()|]*)\)/
|
|
781
|
+
private_constant :NAMED_CAPTURE_BODY_RE
|
|
782
|
+
|
|
783
|
+
def match_write_capture_refinements(node)
|
|
784
|
+
regex = node.call.is_a?(Prism::CallNode) ? node.call.receiver : nil
|
|
785
|
+
return {} unless regex.is_a?(Prism::RegularExpressionNode)
|
|
786
|
+
|
|
787
|
+
refinements = {}
|
|
788
|
+
regex.unescaped.scan(NAMED_CAPTURE_BODY_RE) do |name, body|
|
|
789
|
+
type = ::Rigor::Builtins::RegexRefinement.for_capture_body(body)
|
|
790
|
+
refinements[name.to_sym] = type if type
|
|
791
|
+
end
|
|
792
|
+
refinements
|
|
793
|
+
end
|
|
794
|
+
|
|
715
795
|
# Recognised CallNode predicates:
|
|
716
796
|
# - `recv.nil?` (Slice 6 phase 1, no args, no block)
|
|
717
797
|
# - unary `!recv` (`name == :!`, no args, no block)
|
|
@@ -724,42 +804,80 @@ module Rigor
|
|
|
724
804
|
# through to the no-narrowing fallback.
|
|
725
805
|
def analyse_call(node, scope)
|
|
726
806
|
return nil if node.block
|
|
727
|
-
return nil if node.receiver.nil?
|
|
728
807
|
|
|
729
|
-
|
|
730
|
-
|
|
808
|
+
unless node.receiver.nil?
|
|
809
|
+
shape_result = dispatch_call(node, scope, node.name)
|
|
810
|
+
return shape_result if shape_result
|
|
811
|
+
|
|
812
|
+
# v0.1.1 Track 1 slice 4 — String predicate flow facts.
|
|
813
|
+
string_predicate_result = analyse_string_predicate(node, scope)
|
|
814
|
+
return string_predicate_result if string_predicate_result
|
|
815
|
+
end
|
|
731
816
|
|
|
732
817
|
# Slice 7 phase 15 — RBS::Extended predicate
|
|
733
818
|
# effects. When the method's RBS signature carries
|
|
734
819
|
# `rigor:v1:predicate-if-true` / `predicate-if-false`
|
|
735
820
|
# annotations, apply them to narrow the corresponding
|
|
736
|
-
# local-variable arguments on each edge.
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
#
|
|
747
|
-
#
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
821
|
+
# local-variable arguments on each edge. v0.1.1 Track
|
|
822
|
+
# 1 slice 3 — implicit-self calls (`recv == nil`,
|
|
823
|
+
# e.g. `admin?` inside an instance method body) flow
|
|
824
|
+
# through here too so `self`-targeted facts can edit
|
|
825
|
+
# `scope.self_type`.
|
|
826
|
+
analyse_rbs_extended_contribution(node, scope)
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
# v0.1.1 Track 1 slice 4 — `String#start_with?` /
|
|
830
|
+
# `#end_with?` / `#include?` against a `Constant<String>`
|
|
831
|
+
# needle attaches a relational `FactStore::Fact` to the
|
|
832
|
+
# local on each edge. The receiver's type does NOT
|
|
833
|
+
# change — Rigor has no "starts-with-X" carrier today —
|
|
834
|
+
# so the fact carries the predicate semantics for any
|
|
835
|
+
# downstream consumer that wants to read it (e.g. a
|
|
836
|
+
# plugin's `prepare(services)` hook in v0.1.x).
|
|
837
|
+
# Truthy edge: positive polarity. Falsey edge: negative
|
|
838
|
+
# polarity. Mirrors the equality-predicate fact pattern
|
|
839
|
+
# already used by `analyse_equality_predicate`.
|
|
840
|
+
STRING_PREDICATE_NAMES = %i[start_with? end_with? include?].freeze
|
|
841
|
+
private_constant :STRING_PREDICATE_NAMES
|
|
842
|
+
|
|
843
|
+
def analyse_string_predicate(node, scope)
|
|
844
|
+
return nil unless string_predicate_call?(node)
|
|
845
|
+
|
|
846
|
+
needle = string_predicate_needle(node, scope)
|
|
847
|
+
return nil if needle.nil?
|
|
848
|
+
|
|
849
|
+
local_name = node.receiver.name
|
|
850
|
+
return nil if scope.local(local_name).nil?
|
|
751
851
|
|
|
752
852
|
[
|
|
753
|
-
|
|
754
|
-
|
|
853
|
+
scope.with_fact(string_predicate_fact(local_name, node.name, needle, polarity: :positive)),
|
|
854
|
+
scope.with_fact(string_predicate_fact(local_name, node.name, needle, polarity: :negative))
|
|
755
855
|
]
|
|
756
856
|
end
|
|
757
857
|
|
|
758
|
-
def
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
858
|
+
def string_predicate_call?(node)
|
|
859
|
+
STRING_PREDICATE_NAMES.include?(node.name) &&
|
|
860
|
+
node.receiver.is_a?(Prism::LocalVariableReadNode) &&
|
|
861
|
+
!node.arguments.nil? &&
|
|
862
|
+
node.arguments.arguments.size == 1
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
def string_predicate_needle(node, scope)
|
|
866
|
+
needle_type = static_literal_type(node.arguments.arguments.first, scope)
|
|
867
|
+
return nil unless needle_type.is_a?(Type::Constant) && needle_type.value.is_a?(String)
|
|
868
|
+
|
|
869
|
+
needle_type.value
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
def string_predicate_fact(name, predicate, needle, polarity:)
|
|
873
|
+
Analysis::FactStore::Fact.new(
|
|
874
|
+
bucket: :relational,
|
|
875
|
+
target: Analysis::FactStore::Target.local(name),
|
|
876
|
+
predicate: predicate,
|
|
877
|
+
payload: needle,
|
|
878
|
+
polarity: polarity,
|
|
879
|
+
stability: :local_binding
|
|
880
|
+
)
|
|
763
881
|
end
|
|
764
882
|
|
|
765
883
|
ZERO_CLASS_PREDICATES = %i[positive? negative? zero? nonzero?].freeze
|
|
@@ -1187,118 +1305,130 @@ module Rigor
|
|
|
1187
1305
|
node
|
|
1188
1306
|
end
|
|
1189
1307
|
|
|
1190
|
-
# Slice 7
|
|
1191
|
-
# analyser.
|
|
1192
|
-
#
|
|
1193
|
-
#
|
|
1194
|
-
#
|
|
1308
|
+
# Slice 4b-1 (ADR-7 § "Slice 4-A/4-B") — single-point
|
|
1309
|
+
# RBS::Extended contribution analyser. Replaces the
|
|
1310
|
+
# earlier sibling pair (`analyse_rbs_extended_predicate`
|
|
1311
|
+
# + `analyse_rbs_extended_assert_if`) with one path that
|
|
1312
|
+
# routes through `RbsExtended.read_flow_contribution` and
|
|
1313
|
+
# `Rigor::FlowContribution::Merger.merge`. The bundle's
|
|
1314
|
+
# `truthy_facts` slot already includes both the
|
|
1315
|
+
# `predicate-if-true` and `assert-if-true` Facts (slice
|
|
1316
|
+
# 4a routing closed the v0.0.9 imperfection); the
|
|
1317
|
+
# `falsey_facts` slot mirrors that. The merger composes
|
|
1318
|
+
# any future plugin contribution at the same site
|
|
1319
|
+
# alongside the RBS::Extended bundle without changing
|
|
1320
|
+
# this analyser.
|
|
1195
1321
|
#
|
|
1196
|
-
# Conservative envelope
|
|
1322
|
+
# Conservative envelope (carried over from the previous
|
|
1323
|
+
# implementation):
|
|
1197
1324
|
# - Receiver type must be `Type::Nominal`,
|
|
1198
1325
|
# `Type::Singleton`, or `Type::Constant`.
|
|
1199
1326
|
# - The method must be present in the loader.
|
|
1200
|
-
# - For each
|
|
1201
|
-
#
|
|
1202
|
-
#
|
|
1203
|
-
#
|
|
1204
|
-
#
|
|
1205
|
-
#
|
|
1206
|
-
#
|
|
1207
|
-
#
|
|
1208
|
-
# read-only), so `self`-targeted effects are
|
|
1209
|
-
# accepted by the parser but currently produce no
|
|
1327
|
+
# - For each fact, the corresponding positional
|
|
1328
|
+
# argument (matched by parameter name in the selected
|
|
1329
|
+
# overload) MUST be a `Prism::LocalVariableReadNode`
|
|
1330
|
+
# for narrowing to apply.
|
|
1331
|
+
# - When the target is `self`, narrowing applies to the
|
|
1332
|
+
# receiver — but the engine does not yet narrow
|
|
1333
|
+
# `self` itself, so `self`-targeted facts are
|
|
1334
|
+
# accepted by the merger but currently produce no
|
|
1210
1335
|
# scope edits.
|
|
1211
|
-
def
|
|
1336
|
+
def analyse_rbs_extended_contribution(node, scope)
|
|
1212
1337
|
method_def = resolve_rbs_extended_method(node, scope)
|
|
1213
1338
|
return nil if method_def.nil?
|
|
1214
1339
|
|
|
1215
|
-
|
|
1216
|
-
return nil if
|
|
1340
|
+
contribution = RbsExtended.read_flow_contribution(method_def)
|
|
1341
|
+
return nil if contribution.nil?
|
|
1217
1342
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
apply_predicate_effect(effect, node, scope, truthy_scope, falsey_scope, method_def)
|
|
1223
|
-
end
|
|
1224
|
-
[truthy_scope, falsey_scope]
|
|
1225
|
-
end
|
|
1226
|
-
|
|
1227
|
-
# v0.0.2 — `assert-if-true` / `assert-if-false`. Reads
|
|
1228
|
-
# the conditional assertion effects off the called
|
|
1229
|
-
# method and narrows the matching argument on the
|
|
1230
|
-
# corresponding edge. The unconditional `assert`
|
|
1231
|
-
# variant is NOT applied here; `StatementEvaluator`
|
|
1232
|
-
# applies it directly to the post-call scope.
|
|
1233
|
-
def analyse_rbs_extended_assert_if(node, scope)
|
|
1234
|
-
method_def = resolve_rbs_extended_method(node, scope)
|
|
1235
|
-
return nil if method_def.nil?
|
|
1343
|
+
result = Rigor::FlowContribution::Merger.merge([contribution])
|
|
1344
|
+
truthy_facts = result.truthy_facts
|
|
1345
|
+
falsey_facts = result.falsey_facts
|
|
1346
|
+
return nil if truthy_facts.empty? && falsey_facts.empty?
|
|
1236
1347
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1348
|
+
apply_facts_to_scope_pair(node, scope, truthy_facts, falsey_facts, method_def)
|
|
1349
|
+
end
|
|
1239
1350
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1351
|
+
def apply_facts_to_scope_pair(call_node, entry_scope, truthy_facts, falsey_facts, method_def)
|
|
1352
|
+
truthy_scope = entry_scope
|
|
1353
|
+
falsey_scope = entry_scope
|
|
1354
|
+
truthy_facts.each do |fact|
|
|
1355
|
+
truthy_scope = apply_fact_to_scope(fact, call_node, entry_scope, truthy_scope, method_def)
|
|
1356
|
+
end
|
|
1357
|
+
falsey_facts.each do |fact|
|
|
1358
|
+
falsey_scope = apply_fact_to_scope(fact, call_node, entry_scope, falsey_scope, method_def)
|
|
1245
1359
|
end
|
|
1246
1360
|
[truthy_scope, falsey_scope]
|
|
1247
1361
|
end
|
|
1248
1362
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
return
|
|
1363
|
+
def apply_fact_to_scope(fact, call_node, entry_scope, target_scope, method_def)
|
|
1364
|
+
target_node = fact_target_node(fact, call_node, method_def)
|
|
1365
|
+
return apply_self_fact(fact, target_node, entry_scope, target_scope) if fact.target_kind == :self
|
|
1366
|
+
return target_scope unless target_node.is_a?(Prism::LocalVariableReadNode)
|
|
1253
1367
|
|
|
1254
1368
|
local_name = target_node.name
|
|
1255
1369
|
current = entry_scope.local(local_name)
|
|
1256
|
-
return
|
|
1370
|
+
return target_scope if current.nil?
|
|
1257
1371
|
|
|
1258
|
-
narrowed =
|
|
1259
|
-
|
|
1260
|
-
[truthy_scope.with_local(local_name, narrowed), falsey_scope]
|
|
1261
|
-
else
|
|
1262
|
-
[truthy_scope, falsey_scope.with_local(local_name, narrowed)]
|
|
1263
|
-
end
|
|
1372
|
+
narrowed = narrow_for_fact(current, fact, entry_scope.environment)
|
|
1373
|
+
target_scope.with_local(local_name, narrowed)
|
|
1264
1374
|
end
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
#
|
|
1268
|
-
# `
|
|
1269
|
-
#
|
|
1270
|
-
#
|
|
1271
|
-
#
|
|
1272
|
-
#
|
|
1273
|
-
#
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1375
|
+
|
|
1376
|
+
# v0.1.1 Track 1 slice 3 — `target_kind == :self` facts
|
|
1377
|
+
# (`predicate-if-true self is T`, `predicate-if-false
|
|
1378
|
+
# self is T`) narrow whichever scope binding is bound to
|
|
1379
|
+
# the call's receiver. Four receiver shapes participate:
|
|
1380
|
+
#
|
|
1381
|
+
# `recv.method?` where recv is...
|
|
1382
|
+
# - `LocalVariableReadNode` -> narrow that local
|
|
1383
|
+
# - `InstanceVariableReadNode` -> narrow that ivar
|
|
1384
|
+
# - `Prism::SelfNode` (explicit self) -> narrow `self_type`
|
|
1385
|
+
# - nil (implicit self call inside a method body)
|
|
1386
|
+
# -> narrow `self_type`
|
|
1387
|
+
#
|
|
1388
|
+
# Other receiver shapes (method chains, expressions) have
|
|
1389
|
+
# no scope binding to narrow against and fall through.
|
|
1390
|
+
def apply_self_fact(fact, receiver_node, entry_scope, target_scope)
|
|
1391
|
+
case receiver_node
|
|
1392
|
+
when nil, Prism::SelfNode
|
|
1393
|
+
current = entry_scope.self_type
|
|
1394
|
+
return target_scope if current.nil?
|
|
1395
|
+
|
|
1396
|
+
narrowed = narrow_for_fact(current, fact, entry_scope.environment)
|
|
1397
|
+
target_scope.with_self_type(narrowed)
|
|
1398
|
+
when Prism::LocalVariableReadNode
|
|
1399
|
+
current = entry_scope.local(receiver_node.name)
|
|
1400
|
+
return target_scope if current.nil?
|
|
1401
|
+
|
|
1402
|
+
narrowed = narrow_for_fact(current, fact, entry_scope.environment)
|
|
1403
|
+
target_scope.with_local(receiver_node.name, narrowed)
|
|
1404
|
+
when Prism::InstanceVariableReadNode
|
|
1405
|
+
current = entry_scope.ivar(receiver_node.name)
|
|
1406
|
+
return target_scope if current.nil?
|
|
1407
|
+
|
|
1408
|
+
narrowed = narrow_for_fact(current, fact, entry_scope.environment)
|
|
1409
|
+
target_scope.with_ivar(receiver_node.name, narrowed)
|
|
1277
1410
|
else
|
|
1278
|
-
|
|
1411
|
+
target_scope
|
|
1279
1412
|
end
|
|
1280
1413
|
end
|
|
1281
1414
|
|
|
1282
|
-
#
|
|
1283
|
-
# `
|
|
1284
|
-
#
|
|
1285
|
-
#
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
return effect.refinement_type
|
|
1291
|
-
end
|
|
1292
|
-
|
|
1293
|
-
if effect.negative?
|
|
1294
|
-
narrow_not_class(current, effect.class_name, exact: false, environment: environment)
|
|
1415
|
+
# Resolves a Fact's target node. Mirrors the earlier
|
|
1416
|
+
# `effect_target_node` helper but reads from the
|
|
1417
|
+
# canonical Fact carrier; `:self` routes to the call
|
|
1418
|
+
# receiver, otherwise we look up the matching
|
|
1419
|
+
# positional argument by parameter name.
|
|
1420
|
+
def fact_target_node(fact, call_node, method_def)
|
|
1421
|
+
if fact.target_kind == :self
|
|
1422
|
+
call_node.receiver
|
|
1295
1423
|
else
|
|
1296
|
-
|
|
1424
|
+
lookup_positional_arg(call_node, method_def, fact.target_name)
|
|
1297
1425
|
end
|
|
1298
1426
|
end
|
|
1299
1427
|
|
|
1300
1428
|
def resolve_rbs_extended_method(node, scope)
|
|
1301
|
-
receiver_type =
|
|
1429
|
+
receiver_type = receiver_type_for_resolve(node, scope)
|
|
1430
|
+
return nil if receiver_type.nil?
|
|
1431
|
+
|
|
1302
1432
|
class_name = rbs_extended_class_name(receiver_type)
|
|
1303
1433
|
return nil if class_name.nil?
|
|
1304
1434
|
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
@@ -1312,6 +1442,13 @@ module Rigor
|
|
|
1312
1442
|
nil
|
|
1313
1443
|
end
|
|
1314
1444
|
|
|
1445
|
+
# Implicit self call (`admin?` with no receiver inside an
|
|
1446
|
+
# instance method body) — read the method definition from
|
|
1447
|
+
# `scope.self_type` per v0.1.1 Track 1 slice 3.
|
|
1448
|
+
def receiver_type_for_resolve(node, scope)
|
|
1449
|
+
node.receiver.nil? ? scope.self_type : scope.type_of(node.receiver)
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1315
1452
|
def rbs_extended_class_name(receiver_type)
|
|
1316
1453
|
case receiver_type
|
|
1317
1454
|
when Type::Nominal, Type::Singleton then receiver_type.class_name
|
|
@@ -1332,24 +1469,6 @@ module Rigor
|
|
|
1332
1469
|
nil
|
|
1333
1470
|
end
|
|
1334
1471
|
|
|
1335
|
-
# rubocop:disable Metrics/ParameterLists
|
|
1336
|
-
def apply_predicate_effect(effect, call_node, entry_scope, truthy_scope, falsey_scope, method_def)
|
|
1337
|
-
target_node = effect_target_node(effect, call_node, method_def)
|
|
1338
|
-
return [truthy_scope, falsey_scope] unless target_node.is_a?(Prism::LocalVariableReadNode)
|
|
1339
|
-
|
|
1340
|
-
local_name = target_node.name
|
|
1341
|
-
current = entry_scope.local(local_name)
|
|
1342
|
-
return [truthy_scope, falsey_scope] if current.nil?
|
|
1343
|
-
|
|
1344
|
-
narrowed = narrow_for_effect(current, effect, entry_scope.environment)
|
|
1345
|
-
if effect.truthy_only?
|
|
1346
|
-
[truthy_scope.with_local(local_name, narrowed), falsey_scope]
|
|
1347
|
-
else
|
|
1348
|
-
[truthy_scope, falsey_scope.with_local(local_name, narrowed)]
|
|
1349
|
-
end
|
|
1350
|
-
end
|
|
1351
|
-
# rubocop:enable Metrics/ParameterLists
|
|
1352
|
-
|
|
1353
1472
|
# Maps the effect's target parameter name to the call
|
|
1354
1473
|
# site argument by inspecting the selected overload's
|
|
1355
1474
|
# required-positional parameter list. Returns the Prism
|