Class: Rigor::FlowContribution

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/flow_contribution.rb,
lib/rigor/flow_contribution/fact.rb,
lib/rigor/flow_contribution/merger.rb,
lib/rigor/flow_contribution/element.rb,
lib/rigor/flow_contribution/conflict.rb,
lib/rigor/flow_contribution/merge_result.rb

Overview

The public packaging of a flow contribution at a single call edge. Plugins, ‘RBS::Extended` annotations, and built-in narrowing rules all hand the analyzer this same bundle shape; the inference engine merges contributions through the policy described in [ADR-2 § “Plugin Contribution Merging”](../../docs/adr/2-extension-api.md) rather than letting any one source override another silently.

Eight content slots plus a Provenance block. A slot left as ‘nil` (or, for collection-shaped slots, an empty collection) means the contribution does not assert anything in that dimension; the merge policy treats it as absent.

The struct is the only shape plugin authors need to learn. Richer or more permissive shapes are not part of the first public contract — see ADR-2 § “Flow Contribution Bundle” for the binding definition.

The element-list flattening (‘to_element_list`) ADR-2 mentions is intentionally not implemented yet: it is the analyzer-internal bookkeeping behind the merge policy and will land alongside the plugin contribution merger in v0.1.0. Plugin authors should not rely on it.

Defined Under Namespace

Modules: Merger Classes: Conflict, Element, Fact, MergeResult, Provenance

Constant Summary collapse

SLOT_NAMES =
%i[
  return_type
  truthy_facts
  falsey_facts
  post_return_facts
  mutations
  invalidations
  exceptional
  role_conformance
].freeze
FACT_VALID_TARGET_KINDS =

Canonical slot payload for the four edge-aware fact slots (‘truthy_facts`, `falsey_facts`, `post_return_facts`, plus the equivalent under `mutations` / `invalidations` / `role_conformance` once those carriers grow Fact-shaped variants).

ADR-7 § “Slice 4-A” pins this object as the **single canonical translation target** for the four parallel contribution carriers the engine has carried so far:

  1. Built-in narrowing rules’ direct fact emission (Inference::Narrowing#predicate_scopes).

  2. RBS::Extended ‘predicate-if-*` directives (`Rigor::RbsExtended::PredicateEffect`).

  3. RBS::Extended ‘assert*` directives (`Rigor::RbsExtended::AssertEffect`).

  4. Future plugin contributions (slice 5 emission protocol).

Each of those four carriers translates to / from Fact at its boundary; downstream of #to_element_list and Merger.merge, every slot payload is a Fact (or a value that the merger compares by equality and never inspects). The typed ‘RbsExtended::*Effect` carriers stay internal to the parser side — they hold the source-text shape, but lose their identity at the `read_flow_contribution` boundary.

## Field set

  • ‘target_kind`: `:parameter` (call-site argument) or `:self` (receiver). Future slices may extend the set (`:local`, `:ivar`, `:result`); the merger is agnostic to the concrete kinds and only requires equality.

  • ‘target_name`: a `Symbol`. For `:parameter` it’s the declared parameter name. For ‘:self` it is the literal `:self` symbol so the field stays non-nil and the merge key is well-defined.

  • ‘type`: a `Rigor::Type::*` (Nominal, Refined, IntegerRange, Difference, …) the fact narrows the target toward (when `negative` is false) or away from (when `negative` is true).

  • ‘negative`: `true` for the `~T` negation form (`predicate-if-true x is ~Integer`), `false` for the plain positive form. Mirrors the `negative` field on `PredicateEffect` / `AssertEffect`.

The ‘target` accessor returns `:self` for self-targeted facts and `[:parameter, name]` otherwise — that’s the value Element#target keys on, so two facts that narrow the same parameter from different contribution sources land in the same merge bucket.

%i[parameter self].freeze
ELEMENT_VALID_EDGES =

Tagged element flattening of a Rigor::FlowContribution bundle —the analyzer-internal representation [ADR-2 § “Flow Contribution Bundle”](../../../docs/adr/2-extension-api.md) routes through the Merger.

The flattening is **mechanical, deterministic, and round- trippable** with the bundle: every non-empty slot expands into one or more elements keyed by ‘(target, edge, kind)`, and an array of elements rebuilds an equivalent bundle when routed through `Merger.merge`.

Plugin authors should not depend on the element shape — the bundle is the public contract; the element list is the implementation surface the merge policy operates over.

%i[normal truthy falsey post_return exceptional].freeze
ELEMENT_VALID_KINDS =
%i[
  return_type
  truthy_fact
  falsey_fact
  post_return_fact
  mutation
  invalidation
  exception
  role
].freeze
CONFLICT_VALID_REASONS =

Records a contradiction between two or more flow contributions detected during Merger.merge. Carried on MergeResult#conflicts so the analyzer / formatter can surface a ‘:contribution_merge` diagnostic per ADR-2 § “Plugin Contribution Merging”.

ADR-2 § “Plugin Contribution Merging” rules out first-wins / last-wins behaviour: when contributions conflict, both sources are reported and the merger falls back to the nearest non- conflicting higher-tier (or default) value for the affected ‘(target, edge, kind)` slot. The conflict object is the carrier of that report.

Slice-3 conflict reasons:

  • ‘:return_type_collapse` — two return-type contributions intersect to `bot`.

  • ‘:exceptional_disagreement` — two contributions assert incompatible non-`nil` exceptional effects.

  • ‘:lower_tier_contradiction` — a lower-tier contribution would weaken or contradict a higher-tier proof.

%i[
  return_type_collapse
  exceptional_disagreement
  lower_tier_contradiction
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(return_type: nil, truthy_facts: nil, falsey_facts: nil, post_return_facts: nil, mutations: nil, invalidations: nil, exceptional: nil, role_conformance: nil, provenance: Provenance.builtin) ⇒ FlowContribution

rubocop:disable Metrics/ParameterLists

Parameters:

  • return_type (Object, nil) (defaults to: nil)

    normal-edge return type. Use ‘nil` when the contribution does not refine the return type selected from the RBS contract.

  • truthy_facts (Array, nil) (defaults to: nil)

    facts that hold only on the truthy control-flow edge. Edge-local: a truthy-edge fact does NOT imply its falsey-edge complement (ADR-2 § “Plugin Contribution Merging”).

  • falsey_facts (Array, nil) (defaults to: nil)

    dual of ‘truthy_facts`.

  • post_return_facts (Array, nil) (defaults to: nil)

    facts that hold after the call returns normally on every edge — the carrier for assertion-style contributions.

  • mutations (Array, nil) (defaults to: nil)

    receiver and argument mutation effects.

  • invalidations (Array, nil) (defaults to: nil)

    targeted fact invalidations beyond what mutation effects already imply.

  • exceptional (Object, nil) (defaults to: nil)

    non-returning, raising, or unreachable effect.

  • role_conformance (Array, nil) (defaults to: nil)

    capability-role conformance facts the contribution provides.

  • provenance (Provenance) (defaults to: Provenance.builtin)

    source-family, plugin-id, node, and cache-descriptor metadata. Defaults to ‘Provenance.builtin`.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/rigor/flow_contribution.rb', line 76

def initialize(return_type: nil, truthy_facts: nil, falsey_facts: nil,
               post_return_facts: nil, mutations: nil, invalidations: nil,
               exceptional: nil, role_conformance: nil,
               provenance: Provenance.builtin)
  # rubocop:enable Metrics/ParameterLists
  @return_type = return_type
  @truthy_facts = freeze_collection(truthy_facts)
  @falsey_facts = freeze_collection(falsey_facts)
  @post_return_facts = freeze_collection(post_return_facts)
  @mutations = freeze_collection(mutations)
  @invalidations = freeze_collection(invalidations)
  @exceptional = exceptional
  @role_conformance = freeze_collection(role_conformance)
  @provenance = provenance
  freeze
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



140
141
142
# File 'lib/rigor/flow_contribution.rb', line 140

def ==(other)
  other.is_a?(FlowContribution) && to_h == other.to_h
end

#empty?Boolean

Returns true when every content slot is unset (nil or an empty collection). Provenance does not count toward emptiness — an empty bundle still carries source attribution.

Returns:

  • (Boolean)

    true when every content slot is unset (nil or an empty collection). Provenance does not count toward emptiness — an empty bundle still carries source attribution.



96
97
98
# File 'lib/rigor/flow_contribution.rb', line 96

def empty?
  SLOT_NAMES.all? { |slot| slot_empty?(public_send(slot)) }
end

#hashObject



145
146
147
# File 'lib/rigor/flow_contribution.rb', line 145

def hash
  to_h.hash
end

#to_element_listArray<Element>

Flattens this bundle into a tagged element list keyed by ‘(target, edge, kind)`. The flattening is mechanical and round-trippable through Rigor::FlowContribution::Merger.merge: feeding the result back through the merger produces an equivalent bundle.

Layout:

| slot | edge | kind | target | | ——————–|—————|———————|————————-| | return_type | normal | return_type | :return | | truthy_facts | truthy | truthy_fact | (per-fact target) | | falsey_facts | falsey | falsey_fact | (per-fact target) | | post_return_facts | post_return | post_return_fact | (per-fact target) | | mutations | normal | mutation | (per-mutation target) | | invalidations | normal | invalidation | (per-fact target) | | exceptional | exceptional | exception | :raise | | role_conformance | normal | role | (per-role target) |

Returns:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/rigor/flow_contribution.rb', line 125

def to_element_list # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  elements = []
  elements << element_for(:return, :normal, :return_type, return_type) unless return_type.nil?
  Array(truthy_facts).each { |fact| elements << element_for(fact_target(fact), :truthy, :truthy_fact, fact) }
  Array(falsey_facts).each { |fact| elements << element_for(fact_target(fact), :falsey, :falsey_fact, fact) }
  Array(post_return_facts).each do |fact|
    elements << element_for(fact_target(fact), :post_return, :post_return_fact, fact)
  end
  Array(mutations).each { |m| elements << element_for(fact_target(m), :normal, :mutation, m) }
  Array(invalidations).each { |i| elements << element_for(fact_target(i), :normal, :invalidation, i) }
  elements << element_for(:raise, :exceptional, :exception, exceptional) unless exceptional.nil?
  Array(role_conformance).each { |r| elements << element_for(fact_target(r), :normal, :role, r) }
  elements.freeze
end

#to_hObject



100
101
102
103
104
# File 'lib/rigor/flow_contribution.rb', line 100

def to_h
  SLOT_NAMES.each_with_object(provenance: provenance.to_h) do |slot, acc|
    acc[slot] = public_send(slot)
  end
end