Class: Rigor::Inference::PrecisionScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/inference/precision_scanner.rb

Overview

Measures the *type quality* of inferred expressions — not whether the engine recognises an AST node class (that is ‘CoverageScanner`’s job), but whether the type it produces carries useful static information.

Each visited node is classified into one of eight precision tiers:

:constant         — Constant[T]: literal value known exactly
:nominal          — Nominal/Singleton: class identity known
:shaped           — Tuple/HashShape/IntegerRange/App: structure known
:refined          — Refined: narrowed by a predicate/assertion
:bot              — Bot: unreachable branch (definitively precise)
:dynamic_specific — Dynamic[X] where X is not Top: origin partial
:dynamic_top      — Dynamic[Top]: completely opaque (the "untyped" hole)
:top              — Top: universal supertype (no information)

The summary exposes ‘precision_ratio` (constant+nominal+shaped+refined+bot over total) and `opaque_ratio` (dynamic_top+top over total).

For Union types the worst member tier is used, since the union is only as precise as its least-precise constituent. Intersection uses the best member (the most specific side wins). Difference follows its base type.

Defined Under Namespace

Classes: FileResult

Constant Summary collapse

TIERS =
%i[
  constant nominal shaped refined bot
  dynamic_specific dynamic_top top
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(scope: nil) ⇒ PrecisionScanner

Returns a new instance of PrecisionScanner.

Parameters:

  • scope (Rigor::Scope) (defaults to: nil)

    base scope for type inference.



78
79
80
# File 'lib/rigor/inference/precision_scanner.rb', line 78

def initialize(scope: nil)
  @scope = scope || Scope.empty
end

Instance Method Details

#scan(root) ⇒ FileResult

Parameters:

  • root (Prism::Node)

    the parsed AST

Returns:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/rigor/inference/precision_scanner.rb', line 84

def scan(root)
  scope_index = ScopeIndexer.index(root, default_scope: @scope)
  tier_counts = TIERS.to_h { |t| [t, 0] }
  total = 0

  Source::NodeWalker.each(root) do |node|
    type = scope_index[node].type_of(node)
    tier = classify(type)
    tier_counts[tier] += 1
    total += 1
  end

  FileResult.new(total: total, tier_counts: tier_counts)
end