Module: Rigor::Inference::Builtins

Defined in:
lib/rigor/inference/builtins/set_catalog.rb,
lib/rigor/inference/builtins/date_catalog.rb,
lib/rigor/inference/builtins/hash_catalog.rb,
lib/rigor/inference/builtins/time_catalog.rb,
lib/rigor/inference/builtins/array_catalog.rb,
lib/rigor/inference/builtins/range_catalog.rb,
lib/rigor/inference/builtins/method_catalog.rb,
lib/rigor/inference/builtins/string_catalog.rb,
lib/rigor/inference/builtins/complex_catalog.rb,
lib/rigor/inference/builtins/numeric_catalog.rb,
lib/rigor/inference/builtins/rational_catalog.rb,
lib/rigor/inference/builtins/comparable_catalog.rb,
lib/rigor/inference/builtins/enumerable_catalog.rb

Defined Under Namespace

Modules: NumericCatalog Classes: MethodCatalog

Constant Summary collapse

SET_CATALOG =

‘Set` catalog. Singleton — load once, consult during dispatch.

Set was rewritten in C and folded into CRuby for Ruby 3.2+; the reference branch (‘ruby_4_0`) ships the implementation in `references/ruby/set.c` with `Init_Set` registering every method directly. There is no `set.rb` prelude — the trailing `rb_provide(“set.rb”)` makes `require “set”` a no-op against the built-in.

The blocklist below catches the catalog ‘:leaf` entries the C-body classifier mis-attributes. Set’s iteration helpers (‘set_iter`, `RETURN_SIZED_ENUMERATOR`) and its identity- mode and reset paths drive into helpers the regex classifier does not yet recognise as block-yielding or mutating.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/set.yml",
    __dir__
  ),
  mutating_selectors: {
    "Set" => Set[
      # Indirect mutators classified `:leaf` because the C
      # classifier did not follow the helper functions:
      #
      # - `initialize_copy` calls `set_copy` to overwrite the
      #   receiver's table.
      # - `compare_by_identity` swaps the internal hash type
      #   via `set_reset_table_with_type`.
      # - `reset` rebuilds the internal table to dedup after
      #   element mutation.
      :initialize_copy, :compare_by_identity, :reset,
      # Block-dependent methods classified `:leaf` because the
      # C body uses `set_iter` / `RETURN_SIZED_ENUMERATOR`
      # rather than calling `rb_yield` directly:
      :each, :classify, :divide,
      # `disjoint?` delegates into `set_i_intersect`, which
      # for non-Set enumerables uses `rb_funcall(other,
      # :any?, ...)` — that is user-redefinable dispatch the
      # classifier missed because the call site is in a
      # sibling function.
      :disjoint?
    ]
  }
)
DATE_CATALOG =

‘Date` / `DateTime` catalog. Singleton — load once, consult during dispatch.

‘Date` and `DateTime` both come from CRuby’s bundled ‘date` gem (`references/ruby/ext/date/date_core.c`). A single `Init_date_core` function registers them, so the catalog carries both classes — `Date` plus the `DateTime` subclass whose own Init block extends with `hour` / `min` / `strftime` / `iso8601` etc. The Ruby-side prelude (`lib/date.rb`) only contributes `Date#infinite?` and the nested `Date::Infinity` class; the bulk of the surface is in C.

Date / DateTime receivers are not lifted to a ‘Constant` carrier today (there is no Date literal node — the closest is `Date.today` / `Date.parse(…)`, which produce `Nominal`). The catalog wiring therefore mostly governs:

  1. The Integer-typed reader surface (‘#year`, `#month`, `#day`, `#wday`, `#hour`, `#min`, `#sec`) — RBS-declared `Integer` is preserved through dispatch.

  2. The blocklist below, which keeps mutator-style methods that the C-body classifier already flagged (‘mutates_self`) from being missed by a future `Constant<Date>` carrier, plus a defensive `:initialize_copy` entry for symmetry with the other catalogs.

The non-bang ‘#next_day` / `#prev_day` / `#next_month` / `#prev_month` / `#next_year` / `#prev_year` / `#>>` / `#<<` selectors all RETURN brand-new `Date` objects rather than mutating the receiver — they intentionally stay catalog-eligible. The two real mutators (`#initialize_copy`, `#marshal_load`) are already classified `:mutates_self` by the C-body regex, so they fall out of `MethodCatalog#safe_for_folding?` without an explicit blocklist entry; the entries below are defense-in-depth against indirect mutators the regex might miss in a future CRuby bump.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/date.yml",
    __dir__
  ),
  mutating_selectors: {
    "Date" => Set[
      # `d_lite_initialize_copy` is already classed
      # `:mutates_self` by the regex (it calls
      # `rb_check_frozen` and rewrites the receiver's
      # internal `dat` slots). Listed here for symmetry with
      # String / Array / Range / Set / Time and to keep the
      # blocklist self-documenting.
      :initialize_copy,
      # `d_lite_fill` is a `#ifndef NDEBUG` debug method that
      # warms the receiver's cached `simple` / `complex`
      # fields via the `get_s_*` / `get_c_*` macros. The
      # macros perform in-place writes on the receiver's
      # internal `dat` struct but use no helper the C-body
      # regex recognises, so the classifier mis-flags it
      # `:leaf`. Blocked so a future `Constant<Date>` carrier
      # never folds it.
      :fill
    ],
    "DateTime" => Set[
      # `DateTime` inherits the bulk of its surface from
      # `Date`. The dedicated DateTime-side methods are all
      # readers (`hour`, `min`, …) plus formatting
      # converters (`strftime`, `iso8601`, …); none mutate
      # the receiver. The single defensive entry mirrors the
      # Date side so that the inherited
      # `Date#initialize_copy` (registered against
      # `cDateTime` through subclassing) cannot fold through
      # the catalog if a future `Constant<DateTime>` carrier
      # ever lands.
      :initialize_copy
    ]
  }
)
HASH_CATALOG =

‘Hash` catalog. Singleton — load once, consult during dispatch.

Hash mirrors Array’s mutation pattern: nearly every iteration method yields through ‘rb_hash_foreach` plus a per-pair static callback (`each_value_i`, `keep_if_i`, …), and the C-body classifier does not follow into the callback so it lands as `:leaf` despite being block-dependent. The blocklist below captures every false-positive `:leaf` we have spotted in the generated YAML — bias toward conservatism so a missed fold is acceptable but a folded mutator/yielder is not.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/hash.yml",
    __dir__
  ),
  mutating_selectors: {
    "Hash" => Set[
      # Block-dependent iteration — yields via `rb_hash_foreach`
      # plus a per-pair callback that the regex classifier does
      # not follow:
      :each, :each_pair, :each_key, :each_value,
      :select, :filter, :reject,
      :transform_values,
      # Block-dependent merge — `rb_hash_merge` delegates into
      # `rb_hash_update`, which yields per conflict when a block
      # is given:
      :merge
    ]
  }
)
TIME_CATALOG =

‘Time` catalog. Singleton — load once, consult during dispatch.

Time is a pure-C built-in: the Init block in ‘references/ruby/time.c` registers the bulk of the surface, and the Ruby-side prelude `references/ruby/timev.rb` contributes the class-side constructors (`Time.now`, `Time.at`, `Time.new`) through Primitive cexpr stubs.

Time receivers are not lifted to a ‘Constant` carrier today (there is no `Time` literal node — the closest is `Time.now` / `Time.new(…)`, which produce `Nominal`). The catalog wiring therefore mostly governs:

  1. The size-projection-equivalent reader surface (‘#year`, `#month`, `#hour`, `#sec`, `#wday`, …) — RBS-declared `Integer` is preserved through dispatch.

  2. The blocklist below, which keeps the indirect-mutator methods that the C-body classifier mis-flagged as ‘:leaf` from ever folding through a hypothetical future `Constant<Time>` carrier.

The blocklist captures the false-positive ‘:leaf` entries whose helper functions the regex classifier did not recognise as mutators.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/time.yml",
    __dir__
  ),
  mutating_selectors: {
    "Time" => Set[
      # `time_init_copy` writes the `timew` and `vtm` slots on
      # the receiver via `time_set_timew` / `time_set_vtm`.
      # Classed `:leaf` because those setters are not in the
      # mutator regex's helper list. Blocked for symmetry with
      # String / Array / Range / Set initialize_copy entries.
      :initialize_copy,
      # `time_localtime_m` -> `time_localtime` calls
      # `time_modify(time)` to mark the receiver mutable
      # before rewriting its `vtm` cache and `tzmode`. The
      # docstring is explicit ("converts time to local time
      # in place"). The C-body classifier mis-flagged it as
      # `:leaf` because `time_modify` is not in its mutator
      # regex.
      :localtime,
      # `time_gmtime` (registered as both `gmtime` and `utc`
      # against `rb_cTime`) follows the same in-place pattern
      # as `time_localtime`: `time_modify(time)` then a
      # `time_set_vtm` write and `TZMODE_SET_UTC`. Both
      # selectors share the cfunc, so both must be blocked.
      :gmtime, :utc
    ]
  }
)
ARRAY_CATALOG =

‘Array` catalog. Singleton — load once, consult during dispatch.

Array has more mutation surface than String: every method that logically reshapes the array tends to call ‘rb_ary_modify` or an internal helper (`ary_replace`, `ary_resize`, `ary_pop`, `ary_push_internal`, …) that the classifier does not yet recognise. The blocklist captures the methods we have specifically observed flowing as `:leaf` despite mutating.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/array.yml",
    __dir__
  ),
  mutating_selectors: {
    "Array" => Set[
      # Mutators classified `:leaf` by the C-body heuristic
      :<<, :push, :replace, :clear, :concat, :insert, :"[]=",
      :unshift, :prepend, :pop, :shift, :delete_at, :slice!,
      :compact!, :flatten!, :uniq!, :sort!, :reverse!,
      :rotate!, :keep_if, :delete_if, :select!, :filter!,
      :reject!, :collect!, :map!, :assoc, :rassoc,
      :fill, :delete, :transpose,
      # Methods that yield (block-dependent) — classifier
      # may mark them leaf when the block call is gated:
      :each, :each_with_index, :each_index, :each_slice,
      :each_cons, :each_with_object,
      # Identity/comparison methods that take a block too
      :max, :min, :max_by, :min_by, :minmax, :minmax_by,
      :sort_by, :group_by, :partition, :all?, :any?, :none?,
      :one?, :find, :detect, :find_all, :find_index,
      :reduce, :inject, :flat_map, :collect_concat,
      :zip, :product, :combination, :permutation,
      :chunk_while, :slice_when, :tally
    ]
  }
)
RANGE_CATALOG =

‘Range` catalog. Singleton — load once, consult during dispatch.

Range is largely immutable: ‘begin`, `end`, and `excl` are set at construction by `range_initialize` and never mutated afterwards. The blocklist below therefore stays small. The entries we DO need are the iteration methods whose C body routes through a helper the block/yield regex does not recognise, so the classifier mis-flags them as `:leaf` despite yielding to a block.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/range.yml",
    __dir__
  ),
  mutating_selectors: {
    "Range" => Set[
      # `range_initialize` / `range_initialize_copy` write
      # `begin`/`end`/`excl` slots on the receiver; classed
      # `:leaf` because the writes go through the struct
      # accessor not `rb_check_frozen`. Blocked for symmetry
      # with String / Array.
      :initialize, :initialize_copy,
      # `range_reverse_each` yields to its block via
      # `range_each_func` -> caller's block; the regex
      # classifier follows direct `rb_yield*` calls only.
      :reverse_each,
      # `range_percent_step` returns an Enumerator unless a
      # block is supplied, in which case it yields. Treated
      # as block-dependent so the fold tier never invokes it
      # against a literal Range and tries to materialise an
      # Enumerator into a Constant.
      :%
    ]
  }
)
STRING_CATALOG =

‘String` and `Symbol` catalog. Singleton — load once, consult during dispatch.

The blocklist below is the curated set of catalog ‘:leaf` entries the C-body classifier mis-attributes (the body of `rb_str_replace` calls `str_modifiable` / `str_discard` which the regex-based classifier does not recognise as mutation primitives). Adding to the blocklist is the corrective surface for false positives until the classifier learns the helper functions.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/string.yml",
    __dir__
  ),
  mutating_selectors: {
    "String" => Set[
      :replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
      :prepend, :force_encoding, :encode, :scrub, :unicode_normalize, :"[]=",
      :upto, :each_byte, :each_char, :each_codepoint,
      :each_grapheme_cluster, :each_line, :bytesplice
    ],
    "Symbol" => Set[
      # Symbol is immutable in Ruby; the classifier mis-flags
      # `inspect` because `rb_sym_inspect` builds a temporary
      # mutable buffer. Allow it.
    ]
  }
)
COMPLEX_CATALOG =

‘Complex` catalog. Singleton — load once, consult during dispatch.

‘Complex` is a fully-immutable value type in Ruby: once a complex number is constructed (via `Complex(real, imag)` or `Complex.rect` / `Complex.polar`) its `real` and `imag` slots are never rewritten. Every public instance method either returns `self` unchanged or builds a fresh `Complex` / `Numeric`. The C-body classifier already correctly flags the four `:dispatch` methods (`<=>`, `to_s`, `inspect`, `rationalize`) so there are no false-positive `:leaf` entries to override. The blocklist therefore carries only the conventional `:initialize_copy` defence-in-depth entry so a hypothetical future `Constant<Complex>` carrier cannot fold an aliasing copy through the catalog (mirrors `range_catalog.rb`, `time_catalog.rb`, `date_catalog.rb`).

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/complex.yml",
    __dir__
  ),
  mutating_selectors: {
    "Complex" => Set[
      # Defence in depth: `Complex` does not currently expose
      # a public `initialize_copy`, but blocking it keeps the
      # convention identical to every other catalog so future
      # CRuby additions cannot leak a copy-mutator through.
      :initialize_copy
    ]
  }
)
RATIONAL_CATALOG =

‘Rational` catalog. Singleton — load once, consult during dispatch.

Rational is fully immutable: numerator / denominator slots are written once during ‘nurat_s_new_internal` and the C body never reaches for `rb_check_frozen`. Every catalog entry classifies cleanly (`:leaf`, `:leaf_when_numeric`, or `:dispatch` for the two methods that delegate into user-redefinable `==` / `Float()` — `nurat_eqeq_p` and `nurat_fdiv`). Bang-suffixed mutators do not exist on Rational.

The blocklist therefore stays minimal. ‘initialize_copy` is added defensively (mirrors Range / Set) so a hypothetical future `Constant<Rational>` carrier cannot fold an aliasing copy through the catalog and surface a shared mutable handle.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/rational.yml",
    __dir__
  ),
  mutating_selectors: {
    "Rational" => Set[
      :initialize_copy
    ]
  }
)
COMPARABLE_CATALOG =

‘Comparable` module catalog. Singleton — load once.

‘Comparable` is a Ruby module, not a class, so the catalog is NOT routed through `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS` (which dispatches on the receiver’s concrete class). The data is consumed by future include-aware lookup —see ‘docs/CURRENT_WORK.md` for the planned slice.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/comparable.yml",
    __dir__
  ),
  mutating_selectors: {
    "Comparable" => Set[]
  }
)
ENUMERABLE_CATALOG =

‘Enumerable` module catalog. Singleton — load once.

‘Enumerable` is a Ruby module, not a class, so the catalog is NOT routed through `MethodDispatcher::ConstantFolding::CATALOG_BY_CLASS` (which dispatches on the receiver’s concrete class). The data is consumed by future include-aware lookup —see ‘docs/CURRENT_WORK.md` for the planned slice.

MethodCatalog.new(
  path: File.expand_path(
    "../../../../data/builtins/ruby_core/enumerable.yml",
    __dir__
  ),
  mutating_selectors: {
    "Enumerable" => Set[]
  }
)