Class: Rigor::Effects::FileCollection

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/effects/file_collection.rb

Overview

What one analyzed file contributes to the project's effect graph (ADR-103 WD12).

The collector produces one of these per file and the runner marshals it back from a fork-pool worker with the file's diagnostics, so every field is Marshal-clean: frozen Hashes of Strings, Summary values, and Data edges. Nothing here holds a Prism node, a Scope or an environment.

It carries four tables:

  • #summaries — the direct summary of each method the file defines, keyed Class#m / Class.m / <toplevel>#m (WD14). A reopening in another file contributes the same key and the two join.
  • #edges — per method key, the calls that must be resolved against the project before they become graph edges. Resolution is deferred to the propagator because a file cannot see the whole class graph; the collector only records what the typer decided about the receiver.
  • #superclasses / #includes — the ancestry the propagator needs to resolve an edge through inherited methods and to find every project-known override of a call's target (the closed-world join of WD4).

Merging is associative and commutative in every table, so folding a run's files in pool-completion order yields exactly the table sequential analysis yields.

Defined Under Namespace

Classes: Edge

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path: nil, summaries: NO_TABLE, edges: NO_TABLE, superclasses: NO_TABLE, includes: NO_TABLE, failed: false) ⇒ FileCollection

Returns a new instance of FileCollection.



55
56
57
58
59
60
61
62
63
64
# File 'lib/rigor/effects/file_collection.rb', line 55

def initialize(path: nil, summaries: NO_TABLE, edges: NO_TABLE,
               superclasses: NO_TABLE, includes: NO_TABLE, failed: false)
  @path = path
  @summaries = freeze_table(summaries)
  @edges = freeze_edges(edges)
  @superclasses = freeze_table(superclasses)
  @includes = freeze_table(includes)
  @failed = failed ? true : false
  freeze
end

Instance Attribute Details

#edgesObject (readonly)

Returns the value of attribute edges.



53
54
55
# File 'lib/rigor/effects/file_collection.rb', line 53

def edges
  @edges
end

#includesObject (readonly)

Returns the value of attribute includes.



53
54
55
# File 'lib/rigor/effects/file_collection.rb', line 53

def includes
  @includes
end

#pathObject (readonly)

Returns the value of attribute path.



53
54
55
# File 'lib/rigor/effects/file_collection.rb', line 53

def path
  @path
end

#summariesObject (readonly)

Returns the value of attribute summaries.



53
54
55
# File 'lib/rigor/effects/file_collection.rb', line 53

def summaries
  @summaries
end

#superclassesObject (readonly)

Returns the value of attribute superclasses.



53
54
55
# File 'lib/rigor/effects/file_collection.rb', line 53

def superclasses
  @superclasses
end

Class Method Details

.empty(path = nil) ⇒ Object

The collection a file with nothing to say contributes — a parse failure, a file of constants, or a run where the collector was never activated.



49
50
51
# File 'lib/rigor/effects/file_collection.rb', line 49

def self.empty(path = nil)
  new(path: path)
end

.merge_all(collections) ⇒ Object

Folds a run's collections in one linear pass: each key's summaries join once, each table is built once, and the frozen result is constructed once at the end. Order-independent in every table except superclasses, where a later collection's spelling wins exactly as a chain of #merge calls would leave it, so a path-sorted fold is reproducible.

A collection that is #empty? and not #failed? contributes nothing, which is #merge's own short-circuit spelled once: a file with no methods and no calls has no summary to fold, and its ancestry has no unit to attach to.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rigor/effects/file_collection.rb', line 97

def self.merge_all(collections)
  summaries = {}
  edges = {}
  superclasses = {}
  includes = {}
  failed = false

  collections.each do |collection|
    failed ||= collection.failed?
    next if collection.empty?

    fold_summaries(summaries, collection.summaries)
    fold_lists(edges, collection.edges)
    superclasses.update(collection.superclasses)
    fold_lists(includes, collection.includes)
  end

  includes.each_value(&:uniq!)
  new(path: nil, summaries: summaries, edges: edges,
      superclasses: superclasses, includes: includes, failed: failed)
end

Instance Method Details

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



137
138
139
140
# File 'lib/rigor/effects/file_collection.rb', line 137

def ==(other)
  other.is_a?(FileCollection) && other.summaries == @summaries && other.edges == @edges &&
    other.superclasses == @superclasses && other.includes == @includes && other.failed? == @failed
end

#empty?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/rigor/effects/file_collection.rb', line 72

def empty?
  @summaries.empty? && @edges.empty?
end

#failed?Boolean

Whether the collector gave up on this file entirely (the fail-soft path). Its methods contribute nothing rather than contributing a wrong summary; rigor check is unaffected either way.

Returns:

  • (Boolean)


68
69
70
# File 'lib/rigor/effects/file_collection.rb', line 68

def failed?
  @failed
end

#hashObject



143
144
145
# File 'lib/rigor/effects/file_collection.rb', line 143

def hash
  [self.class, @summaries, @edges, @superclasses, @includes, @failed].hash
end

#merge(other) ⇒ Object

Folds another collection into this one. Summaries join per key, edge lists union, ancestry merges.

Fold a whole run with merge_all, not with this in a reduce. Every call here rebuilds and re-freezes the accumulated tables, so folding a run one file at a time costs O(files × methods) — it was 5.1 s of mastodon's 6.4 s collection overhead and the whole of gitlab's superlinear one (docs/notes/20260817-effect-collection-perf.md). This stays for a two-collection merge, which is what its cost model fits.



83
84
85
86
87
# File 'lib/rigor/effects/file_collection.rb', line 83

def merge(other)
  return self if other.empty? && !other.failed?

  self.class.merge_all([self, other])
end