Module: Rigor::Analysis::CheckRules

Defined in:
lib/rigor/analysis/check_rules.rb

Overview

First-preview catalogue of ‘rigor check` diagnostic rules.

The rules are intentionally narrow: they fire ONLY when the engine is confident enough to make a useful claim, and they MUST NOT raise on unrecognised AST shapes, RBS gaps, or missing scope information. Each rule consumes the per-node scope index produced by ‘Rigor::Inference::ScopeIndexer.index` and yields zero or more `Rigor::Analysis::Diagnostic` values.

The first shipped rule, ‘UndefinedMethodOnTypedReceiver`, flags an explicit-receiver `Prism::CallNode` whose receiver statically resolves to a `Type::Nominal` or `Type::Singleton` known to the analyzer’s RBS environment AND whose method name does not appear on that class’s instance / singleton method table. This is the canonical “type check” signal (“Foo has no method bar”), but it explicitly does NOT fire for:

  • implicit-self calls (no ‘node.receiver`) — too noisy without per-method RBS for every helper in the class.

  • dynamic / unknown receivers (‘Dynamic`, `Top`, `Union`) — by definition we cannot enumerate the method set.

  • shape carriers (‘Tuple`, `HashShape`, `Constant`) — their dispatch goes through `ShapeDispatch` / `ConstantFolding` which the rule does not yet model.

  • receivers whose class name is NOT registered in the loader (RBS-blind environments, unknown stdlib).

The above list is the deliberate conservative envelope of the first preview; later slices broaden it. rubocop:disable Metrics/ModuleLength

Class Method Summary collapse

Class Method Details

.diagnose(path:, root:, scope_index:) ⇒ Array<Rigor::Analysis::Diagnostic>

Yields diagnostics for every unrecognised method call on a typed receiver in ‘root`’s subtree. The caller MUST have already produced ‘scope_index` through `Rigor::Inference::ScopeIndexer.index(root, default_scope:)`.

Parameters:

  • path (String)

    used to populate ‘Diagnostic#path`; the rule does not open files.

  • root (Prism::Node)
  • scope_index (Hash{Prism::Node => Rigor::Scope})

Returns:



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rigor/analysis/check_rules.rb', line 56

def diagnose(path:, root:, scope_index:) # rubocop:disable Metrics/CyclomaticComplexity
  diagnostics = []
  Source::NodeWalker.each(root) do |node|
    next unless node.is_a?(Prism::CallNode)

    diagnostic = undefined_method_diagnostic(path, node, scope_index)
    diagnostics << diagnostic if diagnostic

    arity_diagnostic = wrong_arity_diagnostic(path, node, scope_index)
    diagnostics << arity_diagnostic if arity_diagnostic

    nil_diagnostic = nil_receiver_diagnostic(path, node, scope_index)
    diagnostics << nil_diagnostic if nil_diagnostic

    dump_diagnostic = dump_type_diagnostic(path, node, scope_index)
    diagnostics << dump_diagnostic if dump_diagnostic

    assert_diagnostic = assert_type_diagnostic(path, node, scope_index)
    diagnostics << assert_diagnostic if assert_diagnostic
  end
  diagnostics
end