Class: Rigor::Inference::PrecisionScanner
- Inherits:
-
Object
- Object
- Rigor::Inference::PrecisionScanner
- 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 expression node is classified into one of eight precision tiers (non-expression syntax nodes — argument / parameter lists, parameter declarations, hash pairs, statement wrappers, clause headers — are skipped; see NON_EXPRESSION_NODE_TYPES):
: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
- NON_EXPRESSION_NODE_TYPES =
Prism node classes that do not denote a value-producing expression, so typing them is meaningless — they have no runtime value to carry a type. Counting them (they always fall to the ‘dynamic_top` fallback) silently diluted the precision ratio: on a real survey target (shugo/textbringer) they were ~49% of every “opaque” node, dragging the headline number ~13 points below the true expression-level precision. We exclude them from BOTH numerator and denominator so the ratio measures what it claims to — the type quality of actual expressions.
The set is deliberately CONSERVATIVE: only nodes that are unambiguously non-expressions in Ruby’s grammar are listed —argument / parameter list containers and the parameter declarations inside them; the ‘key => value` pair node (its key and value are themselves walked and counted); the program / statements sequence wrappers (their value is the last child, already counted — listing them avoids double-counting); and the clause-header nodes whose body, not the header, carries the value. Anything that could be a value expression (`BlockNode`, `BeginNode`, `ImplicitNode`, `ParenthesesNode`, splats, …) is left in so a genuine inference gap stays visible.
Compared by class NAME so a Prism version that lacks one of the newer node classes does not break loading.
%w[ Prism::ProgramNode Prism::StatementsNode Prism::ArgumentsNode Prism::BlockArgumentNode Prism::ParametersNode Prism::BlockParametersNode Prism::NumberedParametersNode Prism::ItParametersNode Prism::KeywordHashNode Prism::RequiredParameterNode Prism::OptionalParameterNode Prism::RestParameterNode Prism::KeywordRestParameterNode Prism::BlockParameterNode Prism::RequiredKeywordParameterNode Prism::OptionalKeywordParameterNode Prism::ForwardingParameterNode Prism::NoKeywordsParameterNode Prism::ImplicitRestNode Prism::AssocNode Prism::AssocSplatNode Prism::WhenNode Prism::InNode Prism::ElseNode Prism::EnsureNode Prism::RescueNode ].to_set.freeze
Instance Method Summary collapse
-
#initialize(scope: nil) ⇒ PrecisionScanner
constructor
A new instance of PrecisionScanner.
- #scan(root) ⇒ FileResult
Constructor Details
#initialize(scope: nil) ⇒ PrecisionScanner
Returns a new instance of PrecisionScanner.
135 136 137 |
# File 'lib/rigor/inference/precision_scanner.rb', line 135 def initialize(scope: nil) @scope = scope || Scope.empty end |
Instance Method Details
#scan(root) ⇒ FileResult
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/rigor/inference/precision_scanner.rb', line 141 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| next if NON_EXPRESSION_NODE_TYPES.include?(node.class.name) 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 |