rigortype 0.1.6 → 0.1.7

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.
@@ -196,8 +196,8 @@ module Rigor
196
196
  Prism::UntilNode => :type_of_loop,
197
197
  Prism::ForNode => :type_of_dynamic_top,
198
198
  Prism::DefinedNode => :type_of_defined,
199
- Prism::NumberedReferenceReadNode => :type_of_string_or_nil,
200
- Prism::BackReferenceReadNode => :type_of_string_or_nil,
199
+ Prism::NumberedReferenceReadNode => :type_of_numbered_reference,
200
+ Prism::BackReferenceReadNode => :type_of_back_reference,
201
201
  Prism::MatchPredicateNode => :type_of_match_predicate,
202
202
  Prism::MatchRequiredNode => :type_of_match_required,
203
203
  Prism::MatchWriteNode => :type_of_dynamic_top,
@@ -347,6 +347,21 @@ module Rigor
347
347
  )
348
348
  end
349
349
 
350
+ # `$1` / `$2` / ... — numbered match-data globals. When the
351
+ # narrowing tier has bound a tighter type for this number
352
+ # (typically `String` after a `=~`-success guard like `unless
353
+ # /(\d+)/ =~ s; raise; end`), prefer the scope-bound type.
354
+ # Falls back to the default `String | nil`.
355
+ def type_of_numbered_reference(node)
356
+ scope.global(:"$#{node.number}") || type_of_string_or_nil(node)
357
+ end
358
+
359
+ # `$&` / `$'` / `$\`` / `$+` — symbolic back-references. Same
360
+ # narrowing model as numbered references.
361
+ def type_of_back_reference(node)
362
+ scope.global(node.name) || type_of_string_or_nil(node)
363
+ end
364
+
350
365
  # `expr in pattern` — pattern-match predicate. Returns `true`
351
366
  # when the pattern matches, `false` otherwise.
352
367
  def type_of_match_predicate(_node)
@@ -74,10 +74,21 @@ module Rigor
74
74
  # and binds the method-level type parameter that the
75
75
  # block's return type references to `block_type` (Slice 6
76
76
  # phase C sub-phase 2).
77
+ # @param self_type_override [Rigor::Type, nil] when set,
78
+ # the substitution for `Bases::Self` in the method's
79
+ # return type. Used by `MethodDispatcher#try_user_class_fallback`
80
+ # to preserve the ORIGINAL receiver as the substitute
81
+ # for `self` even though the dispatch is routed through
82
+ # `Nominal[Object]` — so that `Bundler::URI::Generic.dup`
83
+ # (which resolves through the `Object` fallback because
84
+ # `Bundler::URI::Generic` lacks RBS) returns
85
+ # `Bundler::URI::Generic` per `Kernel#dup: () -> self`
86
+ # rather than `Object`. Defaults to nil (compute self
87
+ # from the resolved class_name as before).
77
88
  # @return [Rigor::Type, nil] inferred return type, or `nil`
78
89
  # when no rule resolves (no class name, no method, dispatch
79
90
  # on a Top/Dynamic[Top] receiver, etc.).
80
- def try_dispatch(receiver:, method_name:, args:, environment:, block_type: nil)
91
+ def try_dispatch(receiver:, method_name:, args:, environment:, block_type: nil, self_type_override: nil)
81
92
  return nil if environment.nil?
82
93
  return nil unless environment.rbs_loader
83
94
 
@@ -86,7 +97,8 @@ module Rigor
86
97
  method_name: method_name,
87
98
  args: args,
88
99
  environment: environment,
89
- block_type: block_type
100
+ block_type: block_type,
101
+ self_type_override: self_type_override
90
102
  )
91
103
  end
92
104
 
@@ -128,26 +140,26 @@ module Rigor
128
140
  class << self
129
141
  private
130
142
 
131
- def dispatch_for(receiver:, method_name:, args:, environment:, block_type:)
143
+ def dispatch_for(receiver:, method_name:, args:, environment:, block_type:, self_type_override: nil)
132
144
  args ||= []
133
145
  case receiver
134
146
  when Type::Union
135
- dispatch_union(receiver, method_name, args, environment, block_type)
147
+ dispatch_union(receiver, method_name, args, environment, block_type, self_type_override)
136
148
  else
137
- dispatch_one(receiver, method_name, args, environment, block_type)
149
+ dispatch_one(receiver, method_name, args, environment, block_type, self_type_override)
138
150
  end
139
151
  end
140
152
 
141
- def dispatch_union(receiver, method_name, args, environment, block_type)
153
+ def dispatch_union(receiver, method_name, args, environment, block_type, self_type_override = nil)
142
154
  results = receiver.members.map do |member|
143
- dispatch_one(member, method_name, args, environment, block_type)
155
+ dispatch_one(member, method_name, args, environment, block_type, self_type_override)
144
156
  end
145
157
  return nil if results.any?(&:nil?)
146
158
 
147
159
  Type::Combinator.union(*results)
148
160
  end
149
161
 
150
- def dispatch_one(receiver, method_name, args, environment, block_type)
162
+ def dispatch_one(receiver, method_name, args, environment, block_type, self_type_override = nil)
151
163
  descriptor = receiver_descriptor(receiver)
152
164
  return nil unless descriptor
153
165
 
@@ -163,7 +175,8 @@ module Rigor
163
175
  args: args,
164
176
  type_vars: type_vars,
165
177
  block_type: block_type,
166
- environment: environment
178
+ environment: environment,
179
+ self_type_override: self_type_override
167
180
  )
168
181
  rescue StandardError
169
182
  # Defensive: if RBS' definition builder raises on a broken
@@ -254,8 +267,10 @@ module Rigor
254
267
  param_names.zip(receiver_args).to_h
255
268
  end
256
269
 
270
+ # rubocop:disable Metrics/ParameterLists
257
271
  def translate_return_type(method_definition, class_name:, kind:, args:, type_vars:, block_type:,
258
- environment: nil)
272
+ environment: nil, self_type_override: nil)
273
+ # rubocop:enable Metrics/ParameterLists
259
274
  # Slice 4b-3 (ADR-7 § "Slice 4-A/4-B") — read the
260
275
  # return-type override through the merger so future
261
276
  # plugin / `:rbs_extended` bundles that also assert a
@@ -266,11 +281,17 @@ module Rigor
266
281
  return override if override
267
282
 
268
283
  instance_type = Type::Combinator.nominal_of(class_name)
269
- self_type =
284
+ resolved_self_type =
270
285
  case kind
271
286
  when :singleton then Type::Combinator.singleton_of(class_name)
272
287
  else instance_type
273
288
  end
289
+ # `self_type_override` lets the user-class fallback
290
+ # path preserve the ORIGINAL receiver as the substitute
291
+ # for `Bases::Self` — so `Kernel#dup: () -> self`
292
+ # resolved through the Object fallback returns the
293
+ # caller's type, not Object.
294
+ self_type = self_type_override || resolved_self_type
274
295
 
275
296
  method_type = OverloadSelector.select(
276
297
  method_definition,
@@ -657,21 +657,38 @@ module Rigor
657
657
  fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
658
658
  return nil if fallback_receiver.nil?
659
659
 
660
+ # Preserve the ORIGINAL receiver type as the `self`
661
+ # substitution so `Kernel#dup: () -> self` and other
662
+ # `self`-returning methods route through Object's RBS
663
+ # while still returning the caller's type rather than
664
+ # `Object`. Without this, `base = self.dup` inside a
665
+ # `Bundler::URI::Generic` instance method types `base`
666
+ # as `Object` because `Bundler::URI::Generic` is not in
667
+ # RBS and the fallback's `self` resolves to Object.
660
668
  RbsDispatch.try_dispatch(
661
669
  receiver: fallback_receiver,
662
670
  method_name: method_name,
663
671
  args: arg_types,
664
672
  environment: environment,
665
- block_type: block_type
673
+ block_type: block_type,
674
+ self_type_override: receiver_type
666
675
  )
667
676
  end
668
677
 
669
678
  def user_class_fallback_receiver(receiver_type, environment)
670
679
  case receiver_type
671
680
  when Type::Nominal
672
- return nil if Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
681
+ # Modules: even when RBS knows the module, an instance
682
+ # method on a mixin-only module (e.g. `PP::ObjectMixin`)
683
+ # observes Kernel / Object methods through every concrete
684
+ # includer's ancestor chain. Route through the
685
+ # `Nominal[Object]` fallback so `self.inspect` /
686
+ # `self.respond_to?` / `self.class` resolve cleanly when
687
+ # the module itself does not declare them.
688
+ known = Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
689
+ return environment.nominal_for_name("Object") if !known || environment.rbs_module?(receiver_type.class_name)
673
690
 
674
- environment.nominal_for_name("Object")
691
+ nil
675
692
  when Type::Singleton
676
693
  return nil if Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
677
694
 
@@ -950,7 +950,7 @@ module Rigor
950
950
  end
951
951
 
952
952
  def simple_dispatch_name?(name)
953
- %i[nil? ! is_a? kind_of? instance_of? == != ===].include?(name)
953
+ %i[nil? ! is_a? kind_of? instance_of? == != === =~].include?(name)
954
954
  end
955
955
 
956
956
  def dispatch_call_simple(node, scope, name)
@@ -960,9 +960,111 @@ module Rigor
960
960
  when :instance_of? then analyse_class_predicate(node, scope, exact: true)
961
961
  when :==, :!= then analyse_equality_predicate(node, scope, equality: name)
962
962
  when :=== then analyse_case_equality_predicate(node, scope)
963
+ when :=~ then analyse_regex_match_predicate(node, scope)
963
964
  end
964
965
  end
965
966
 
967
+ # Survey item (b): `/regex/ =~ str` and `str =~ /regex/`
968
+ # bind the regex match-data globals on each edge.
969
+ #
970
+ # - Truthy edge (`=~` returned an Integer position — the
971
+ # match succeeded): `$~` to `Nominal[MatchData]`; `$&`
972
+ # and `$1..$N` (where N is the number of capture groups
973
+ # in the regex source) to `Nominal[String]`. This is the
974
+ # same optimistic-narrowing shape the existing
975
+ # `analyse_match_write` uses for named captures inside
976
+ # `if /(?<x>...)/ =~ str` — optional groups in the
977
+ # regex source (`(\d+)?`) would bind `$N` to `nil` at
978
+ # runtime, but the floor here matches the common idiom
979
+ # (required captures) and lets `unless /(\d+)/ =~ s;
980
+ # raise; end; $1.to_i` resolve cleanly.
981
+ # - Falsey edge (`=~` returned nil — no match): `$~` and
982
+ # every numbered / back-reference global bound to
983
+ # `Constant<nil>`.
984
+ #
985
+ # Returns nil (no narrowing) when the receiver / argument
986
+ # pair does not include a `RegularExpressionNode` literal
987
+ # we can count.
988
+ def analyse_regex_match_predicate(node, scope)
989
+ return nil if node.arguments.nil?
990
+ return nil unless node.arguments.arguments.size == 1
991
+
992
+ regex_node = regex_match_literal(node.receiver, node.arguments.arguments.first)
993
+ return nil if regex_node.nil?
994
+
995
+ group_count = count_regex_capture_groups(regex_node.unescaped)
996
+ regex_match_predicate_scopes(scope, group_count)
997
+ end
998
+
999
+ def regex_match_literal(left, right)
1000
+ return left if left.is_a?(Prism::RegularExpressionNode)
1001
+ return right if right.is_a?(Prism::RegularExpressionNode)
1002
+
1003
+ nil
1004
+ end
1005
+
1006
+ # Curated set of back-reference globals bound by every
1007
+ # `=~`. Numbered references (`$1..$N`) are handled
1008
+ # separately because N depends on the regex source.
1009
+ REGEX_MATCH_GLOBALS = %i[$~ $& $` $' $+].freeze
1010
+ private_constant :REGEX_MATCH_GLOBALS
1011
+
1012
+ def regex_match_predicate_scopes(scope, group_count)
1013
+ string_t = Type::Combinator.nominal_of("String")
1014
+ match_data_t = Type::Combinator.nominal_of("MatchData")
1015
+ nil_t = Type::Combinator.constant_of(nil)
1016
+
1017
+ truthy = scope
1018
+ falsey = scope
1019
+ truthy = truthy.with_global(:$~, match_data_t)
1020
+ falsey = falsey.with_global(:$~, nil_t)
1021
+ REGEX_MATCH_GLOBALS.each do |name|
1022
+ next if name == :$~
1023
+
1024
+ truthy = truthy.with_global(name, string_t)
1025
+ falsey = falsey.with_global(name, nil_t)
1026
+ end
1027
+ group_count.times do |i|
1028
+ name = :"$#{i + 1}"
1029
+ truthy = truthy.with_global(name, string_t)
1030
+ falsey = falsey.with_global(name, nil_t)
1031
+ end
1032
+ [truthy, falsey]
1033
+ end
1034
+
1035
+ # Counts capture groups (numbered + named — both
1036
+ # contribute to `$1..$N`) in a regex source. Backslash
1037
+ # escapes are skipped; non-capturing `(?:...)`, lookahead
1038
+ # `(?=...)` / `(?!...)`, and lookbehind `(?<=...)` /
1039
+ # `(?<!...)` do NOT count. Named groups `(?<name>...)`
1040
+ # DO count. The walker is intentionally light — it does
1041
+ # not parse the regex AST, just scans char-by-char — so
1042
+ # exotic constructs that overlap the lookaround syntax
1043
+ # may miscount; the unsoundness is bounded (over- or
1044
+ # under-binding a few `$N` globals) and we already accept
1045
+ # the same shape of unsoundness for `analyse_match_write`.
1046
+ def count_regex_capture_groups(source)
1047
+ i = 0
1048
+ total = 0
1049
+ length = source.length
1050
+ while i < length
1051
+ c = source[i]
1052
+ if c == "\\"
1053
+ i += 2
1054
+ next
1055
+ end
1056
+ if c == "("
1057
+ if source[i + 1] == "?"
1058
+ total += 1 if source[i + 2] == "<" && source[i + 3] != "=" && source[i + 3] != "!"
1059
+ else
1060
+ total += 1
1061
+ end
1062
+ end
1063
+ i += 1
1064
+ end
1065
+ total
1066
+ end
1067
+
966
1068
  def dispatch_call_numeric(node, scope, name)
967
1069
  if COMPARISON_OPERATORS.include?(name)
968
1070
  analyse_comparison_predicate(node, scope, comparator: name)
@@ -343,11 +343,26 @@ module Rigor
343
343
  unless qualified_prefix.empty?
344
344
  body_scope = body_scope.with_self_type(Type::Combinator.singleton_of(qualified_prefix.join("::")))
345
345
  end
346
- rvalue_type = body_scope.type_of(node.value)
346
+ rvalue_type = meta_new_constant_type(node, full) || body_scope.type_of(node.value)
347
347
  existing = accumulator[full]
348
348
  accumulator[full] = existing ? Type::Combinator.union(existing, rvalue_type) : rvalue_type
349
349
  end
350
350
 
351
+ # Survey item (e): when the rvalue is a recognised
352
+ # `Module.new do ... end` / `Class.new do ... end` /
353
+ # `Struct.new(*sym) do ... end` / `Data.define(*sym) do
354
+ # ... end` form, type the named constant as
355
+ # `Singleton[<full>]` so the discovered-method table
356
+ # registered under `full` becomes reachable through
357
+ # singleton-side dispatch (`Const.[]=` etc.). Returns nil
358
+ # for non-meta-new rvalues so the caller falls back to the
359
+ # default `body_scope.type_of(node.value)` shape.
360
+ def meta_new_constant_type(node, full)
361
+ return nil unless meta_new_block_body(node)
362
+
363
+ Type::Combinator.singleton_of(full)
364
+ end
365
+
351
366
  # Slice 7 phase 12 — in-source method discovery pre-pass.
352
367
  # Walks every class/module body and records the methods
353
368
  # introduced via `Prism::DefNode` (instance + singleton)
@@ -429,16 +444,22 @@ module Rigor
429
444
  # v0.1.2 — when a `Const = Data.define(*sym) do ... end`
430
445
  # / `Const = Struct.new(*sym) do ... end` constant write
431
446
  # carries a block, the block body holds method overrides
432
- # whose canonical class is `Const`. Returns the block body
433
- # node (a `Prism::StatementsNode`) when the rvalue
434
- # matches; nil otherwise. Used by `walk_methods` /
435
- # `walk_def_nodes` to push `Const` onto the qualified
436
- # prefix before recursing.
447
+ # whose canonical class is `Const`. Survey item (e) extended
448
+ # the recognition to `Const = Module.new do ... end` and
449
+ # `Const = Class.new(?super) do ... end` — the
450
+ # ADR-16 Tier A "block-as-method" idiom at constant-write
451
+ # position. Returns the block body node (a
452
+ # `Prism::StatementsNode`) when the rvalue matches; nil
453
+ # otherwise. Used by `walk_methods` / `walk_def_nodes` to
454
+ # push `Const` onto the qualified prefix before recursing.
437
455
  def meta_new_block_body(node)
438
456
  return nil unless node.is_a?(Prism::ConstantWriteNode)
439
457
 
440
458
  rvalue = node.value
441
- return nil unless data_define_call?(rvalue) || struct_new_call?(rvalue)
459
+ return nil unless data_define_call?(rvalue) ||
460
+ struct_new_call?(rvalue) ||
461
+ module_new_call?(rvalue) ||
462
+ class_new_call?(rvalue)
442
463
 
443
464
  rvalue.block&.body
444
465
  end
@@ -946,6 +967,31 @@ module Rigor
946
967
  positional.all?(Prism::SymbolNode)
947
968
  end
948
969
 
970
+ # Recognises `Module.new` and `Module.new(&block)` /
971
+ # `Module.new do ... end` at constant-write rvalue
972
+ # position. The block body is the anonymous module's
973
+ # `module_eval` body; defs inside it bind methods on the
974
+ # named constant (`Const = Module.new do ...; def foo; ...; end; end`).
975
+ # Arguments are NOT inspected because `Module.new` accepts
976
+ # no positionals — Ruby raises ArgumentError if any are
977
+ # passed — so a malformed call falls through the walker
978
+ # without affecting analysis.
979
+ def module_new_call?(node)
980
+ meta_call_with_name?(node, :Module, :new)
981
+ end
982
+
983
+ # Recognises `Class.new`, `Class.new(super_class)`, and the
984
+ # block form `Class.new { ... }`. Like `module_new_call?`,
985
+ # the block body is walked as the anonymous class's body.
986
+ # The optional `super_class` positional is accepted but does
987
+ # NOT route through `ancestor` discovery in this slice — the
988
+ # synthesised class still answers method lookups via its
989
+ # own body's defs, mirroring how `Struct.new` / `Data.define`
990
+ # are handled.
991
+ def class_new_call?(node)
992
+ meta_call_with_name?(node, :Class, :new)
993
+ end
994
+
949
995
  def meta_call_with_name?(node, receiver_name, method_name)
950
996
  return false unless node.is_a?(Prism::CallNode)
951
997
  return false unless node.name == method_name
@@ -497,13 +497,25 @@ module Rigor
497
497
  def eval_begin(node)
498
498
  primary_type, primary_scope = eval_begin_primary(node)
499
499
  rescue_chain = collect_rescue_chain_results(node.rescue_clause, scope)
500
-
501
- if rescue_chain.empty?
500
+ # Rescue arms whose body unconditionally exits (`return`,
501
+ # `next`, `break`, `raise`, `throw`, `exit`, `abort`,
502
+ # `fail`) contribute neither a type fragment NOR a scope
503
+ # to the post-begin flow — control left the `begin` via
504
+ # that arm. Mirrors the `eval_if` / `eval_unless` /
505
+ # `eval_and_or` early-return narrowing. Without this
506
+ # filter, a `rescue ... return` on a local bound only in
507
+ # the primary body nil-injects that local across the
508
+ # join, defeating the rescue arm's whole point of guaranteeing
509
+ # the primary local is in scope for downstream statements.
510
+ live_rescues = rescue_chain.reject { |_pair, arm_node| branch_unconditionally_exits?(arm_node.statements) }
511
+ .map(&:first)
512
+
513
+ if live_rescues.empty?
502
514
  exit_type = primary_type
503
515
  exit_scope = primary_scope
504
516
  else
505
- exit_type = Type::Combinator.union(primary_type, *rescue_chain.map(&:first))
506
- exit_scope = reduce_scopes_with_nil_injection([primary_scope, *rescue_chain.map(&:last)])
517
+ exit_type = Type::Combinator.union(primary_type, *live_rescues.map(&:first))
518
+ exit_scope = reduce_scopes_with_nil_injection([primary_scope, *live_rescues.map(&:last)])
507
519
  end
508
520
 
509
521
  if node.ensure_clause
@@ -540,7 +552,7 @@ module Rigor
540
552
  current = rescue_node
541
553
  while current
542
554
  rescue_scope = bind_rescue_reference(current, entry_scope)
543
- results << eval_branch_or_nil(current.statements, rescue_scope)
555
+ results << [eval_branch_or_nil(current.statements, rescue_scope), current]
544
556
  current = current.subsequent
545
557
  end
546
558
  results
@@ -696,10 +708,35 @@ module Rigor
696
708
  # edge-aware: `a && b` can only produce the falsey fragment of
697
709
  # `a` when the RHS is skipped, while `a || b` can only produce
698
710
  # the truthy fragment of `a` when the RHS is skipped.
711
+ #
712
+ # When the RHS unconditionally exits (`raise` / `return` /
713
+ # `throw` / `exit` / `abort` / `fail` / `next` / `break`), the
714
+ # post-OR / post-AND scope is the LHS-skipped edge alone:
715
+ # `a or raise` only survives when `a` was truthy, so subsequent
716
+ # statements observe `a` narrowed to its truthy fragment; the
717
+ # symmetric `a and raise` survives only when `a` was falsey.
718
+ # Same shape as the `eval_if` / `eval_unless` early-return
719
+ # narrowing.
699
720
  def eval_and_or(node)
700
721
  left_type, left_scope = sub_eval(node.left, scope)
701
722
  truthy_left, falsey_left = Narrowing.predicate_scopes(node.left, left_scope)
702
723
  rhs_entry = node.is_a?(Prism::AndNode) ? truthy_left : falsey_left
724
+ if branch_unconditionally_exits?(node.right)
725
+ # Walk the RHS for side-effects (on_enter callbacks,
726
+ # diagnostic dispatch on the raise / return expression
727
+ # itself) but discard its scope: control never reaches
728
+ # any statement after `a or raise` via that edge.
729
+ sub_eval(node.right, rhs_entry)
730
+ surviving_type =
731
+ if node.is_a?(Prism::AndNode)
732
+ Narrowing.narrow_falsey(left_type)
733
+ else
734
+ Narrowing.narrow_truthy(left_type)
735
+ end
736
+ surviving_scope = node.is_a?(Prism::AndNode) ? falsey_left : truthy_left
737
+ return [surviving_type, surviving_scope]
738
+ end
739
+
703
740
  right_type, right_scope = sub_eval(node.right, rhs_entry)
704
741
  skipped_type =
705
742
  if node.is_a?(Prism::AndNode)
@@ -1106,7 +1143,16 @@ module Rigor
1106
1143
  # narrowing logic via `Narrowing.narrow_for_fact` so the
1107
1144
  # predicate / assert / plugin paths all converge on the
1108
1145
  # same hierarchy-aware narrowing rules.
1146
+ #
1147
+ # v0.1.8 Pillar 2 Slice 1 added the `:local` target_kind
1148
+ # branch so plugins recognising bespoke call shapes
1149
+ # (`expect(x).to be_a(T)`) can directly narrow a named
1150
+ # local in the surrounding scope, bypassing the
1151
+ # parameter-name lookup that requires an authoritative RBS
1152
+ # sig on the called method (which RSpec matchers lack).
1109
1153
  def apply_post_return_fact(fact, call_node, current_scope, method_def)
1154
+ return apply_local_post_return_fact(fact, current_scope) if fact.target_kind == :local
1155
+
1110
1156
  target_node = fact_target_node(fact, call_node, method_def)
1111
1157
  return apply_self_post_return_fact(fact, target_node, current_scope) if fact.target_kind == :self
1112
1158
  return current_scope unless target_node.is_a?(Prism::LocalVariableReadNode)
@@ -1119,6 +1165,21 @@ module Rigor
1119
1165
  current_scope.with_local(local_name, narrowed)
1120
1166
  end
1121
1167
 
1168
+ # v0.1.8 Pillar 2 Slice 1 — narrows the named local directly
1169
+ # without consulting the call node's argument list. The fact's
1170
+ # `target_name` is the local-variable name as written in
1171
+ # source. Silently no-ops when the local is unbound in the
1172
+ # current scope (the plugin's named local may have already
1173
+ # gone out of scope when the contribution fires).
1174
+ def apply_local_post_return_fact(fact, current_scope)
1175
+ local_name = fact.target_name
1176
+ current_type = current_scope.local(local_name)
1177
+ return current_scope if current_type.nil?
1178
+
1179
+ narrowed = Narrowing.narrow_for_fact(current_type, fact, current_scope.environment)
1180
+ current_scope.with_local(local_name, narrowed)
1181
+ end
1182
+
1122
1183
  # v0.1.1 Track 1 slice 3 — `assert self is T` post-return
1123
1184
  # narrowing for the four supported receiver shapes (mirrors
1124
1185
  # `Narrowing#apply_self_fact`).
@@ -73,8 +73,8 @@ module Rigor
73
73
  # This file ships the value class only. Slice 2b wires the
74
74
  # pre-pass that scans Tier C call sites + the
75
75
  # `SyntheticMethodIndex` the dispatcher consults; slice 2c
76
- # authors `examples/rigor-dry-struct/` and
77
- # `examples/rigor-dry-types/` as the worked consumers.
76
+ # authors `plugins/rigor-dry-struct/` and
77
+ # `plugins/rigor-dry-types/` as the worked consumers.
78
78
  class HeredocTemplate
79
79
  NAME_PLACEHOLDER = "\#{name}"
80
80
 
@@ -83,7 +83,7 @@ module Rigor
83
83
  # This file ships the value class only. Slice 3b wires the
84
84
  # scanner that walks Tier B call sites + the per-method
85
85
  # explosion via `SyntheticMethodIndex`; slice 3c authors
86
- # `examples/rigor-devise/` model side as the worked consumer.
86
+ # `plugins/rigor-devise/` model side as the worked consumer.
87
87
  class TraitRegistry
88
88
  REST_POSITION = :rest
89
89
 
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
@@ -25,6 +25,7 @@ module Rigor
25
25
  def singleton_for_name: (String | Symbol name) -> Type::Singleton?
26
26
  def constant_for_name: (String | Symbol name) -> Type::t?
27
27
  def class_known?: (String | Symbol name) -> bool
28
+ def rbs_module?: (String | Symbol name) -> bool
28
29
  def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
29
30
  def reflection: () -> untyped?
30
31
 
@@ -52,6 +53,7 @@ module Rigor
52
53
 
53
54
  def initialize: (?libraries: Array[String], ?signature_paths: Array[String | _ToPath], ?cache_store: untyped?) -> void
54
55
  def class_known?: (String | Symbol name) -> bool
56
+ def rbs_module?: (String | Symbol name) -> bool
55
57
  def instance_definition: (String | Symbol class_name) -> untyped?
56
58
  def instance_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped?
57
59
  def uncached_instance_definition: (String | Symbol class_name) -> untyped?
data/sig/rigor.rbs CHANGED
@@ -9,6 +9,7 @@ module Rigor
9
9
  attr_reader paths: Array[String]
10
10
  attr_reader plugins: Array[String]
11
11
  attr_reader cache_path: String
12
+ attr_reader baseline_path: String?
12
13
 
13
14
  def self.load: (?String path) -> Configuration
14
15
  def initialize: (?Hash[String, untyped] data) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -223,6 +223,7 @@ files:
223
223
  - data/builtins/ruby_core/time.yml
224
224
  - exe/rigor
225
225
  - lib/rigor.rb
226
+ - lib/rigor/analysis/baseline.rb
226
227
  - lib/rigor/analysis/buffer_binding.rb
227
228
  - lib/rigor/analysis/check_rules.rb
228
229
  - lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb
@@ -260,6 +261,7 @@ files:
260
261
  - lib/rigor/cache/rbs_known_class_names.rb
261
262
  - lib/rigor/cache/store.rb
262
263
  - lib/rigor/cli.rb
264
+ - lib/rigor/cli/baseline_command.rb
263
265
  - lib/rigor/cli/diff_command.rb
264
266
  - lib/rigor/cli/explain_command.rb
265
267
  - lib/rigor/cli/lsp_command.rb