Module: Rigor::RbsExtended::EnvelopeScanner

Defined in:
lib/rigor/rbs_extended/envelope_scanner.rb

Overview

Collects every effect envelope the project's own RBS declares (ADR-103 WD5 / WD14; #383).

It produces values, never diagnostics — the check that compares a bound against the proven summaries is Effects::EnvelopeCheck. Two tables come out, because they are consumed differently:

  • Result#method_envelopes — keyed by method key (Class#m / Class.m), applied as written.
  • Result#class_envelopes — keyed by class / module name, DISTRIBUTED by the check to every method of that Ruby class discovery knows (reopenings, other files, synthesized attr_* and define_method members), never to subclasses. A per-method envelope wins over a distributed one; nearest wins, and no -except syntax is needed.

Why it parses rather than reading the built environment

ConformanceChecker, the obvious model, walks RbsLoader's built env. An envelope cannot: the diagnostic has to name where the bound was written (sig/foo.rbs:12), and the ADR-54 env cache dumps every RBS::Location to a zero-range <cached> sentinel, so a warm run would lose both the position and the only evidence of which file a declaration came from. Parsing the project's own signature sources is a few milliseconds over a tree Rigor already globs, it is identical warm and cold, and it enforces ADR-103 WD6's trust rule structurally: a %a{pure} in rbs core or in a gem's shipped RBS is never read, so it cannot bound a project method that happens to share its key.

Malformed payloads and pure-versus-effect contradictions are recorded on a Reporter the scanner owns and ride out on Result#unresolved. They surface no diagnostic in this slice: effect.unknown-label and the annotation-in-a-project-without-effects: residual are #384's.

Defined Under Namespace

Classes: Result

Constant Summary collapse

EMPTY =
Result.new(method_envelopes: {}.freeze, class_envelopes: {}.freeze, unresolved: [].freeze)

Class Method Summary collapse

Class Method Details

.from_loader(loader:, registry:) ⇒ Hash{String => Rigor::Effects::Envelope}

The accepted-signature reader: every per-method envelope declared anywhere in a BUILT RBS::Environment — a gem's shipped signatures, Rigor's bundled overlays, core RBS (ADR-103 WD6; #386).

It exists for one purpose and is usable for exactly one: importing a bound at a call site that resolves into un-analysed code (Effects::EnvelopeIndex). It MUST NOT feed a contract check. Two facts make that a structural guarantee rather than a convention — scan, which the check reads, never opens this door, and the values here carry no location at all (the ADR-54 env cache dumps every RBS::Location to a <cached> sentinel, so a warm run could not name where the bound was written even if a diagnostic wanted to). There is also no body to check: a gem method is un-analysed by definition, which is why WD6 trusts the declaration instead, exactly as it already trusts the gem's types.

Class-level annotations are deliberately not read. Distribution needs "every method of the class discovery knows", which is a project fact; a gem's class-level tag would have to distribute over a definition set this reader does not have.

Parameters:

Returns:



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rigor/rbs_extended/envelope_scanner.rb', line 92

def from_loader(loader:, registry:)
  out = {}
  loader.each_annotated_method_member do |class_name, member|
    keys_for(class_name, member).each do |key|
      next if out.key?(key)

      envelope = RbsExtended.read_effect_envelope(
        member.annotations, owner_key: key, source: :effect_annotation, registry: registry
      )
      out[key] = envelope if envelope
    end
  end
  out.freeze
rescue StandardError
  {}.freeze
end

.scan(sources:, registry:) ⇒ Result

Parameters:

  • sources (Array<Array(String, String)>)

    [buffer name, RBS source] pairs — the project's .rbs files, plus the virtual entries rbs-inline and plugin source_rbs synthesis contribute.

  • registry (Rigor::Effects::Registry)

    the vocabulary an unknown label is judged against.

Returns:



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/rigor/rbs_extended/envelope_scanner.rb', line 53

def scan(sources:, registry:)
  reporter = Reporter.new
  methods = {}
  classes = {}
  sources.each do |name, content|
    declarations(name, content).each do |decl|
      walk(decl, [], methods, classes, registry, reporter)
    end
  end
  Result.new(
    method_envelopes: methods.freeze, class_envelopes: classes.freeze,
    unresolved: reporter.unresolved_payloads
  )
rescue StandardError
  EMPTY
end