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_topfallback) 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 => valuepair 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.
126 127 128 |
# File 'lib/rigor/inference/precision_scanner.rb', line 126 def initialize(scope: nil) @scope = scope || Scope.empty end |
Instance Method Details
#scan(root) ⇒ FileResult
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/rigor/inference/precision_scanner.rb', line 132 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 |