Skip to content

Add provenance for relation uses and unused-relation diagnostics#245

Merged
leynos merged 10 commits into
mainfrom
implement-unused-relation-diagnostics-l6v2bg
Mar 26, 2026
Merged

Add provenance for relation uses and unused-relation diagnostics#245
leynos merged 10 commits into
mainfrom
implement-unused-relation-diagnostics-l6v2bg

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Mar 21, 2026

Summary

  • Introduced explicit use-site provenance for relation uses and simplified the internal recording path to populate provenance (UseOrigin) for relation uses. Implemented the unused-relation diagnostics leveraging this provenance, including a production lint rule, semantic-model changes, unit and behavioural tests, and documentation/roadmap updates.

Changes

  • Semantic model and provenance
    • Added UseOrigin enum with variants: RelationHead, RelationBody, ForIterable, ForGuard, Variable
    • UseSite now includes origin and exposes origin(), is_relation_read(), etc.
    • SemanticModel enhancements: relation_symbols(), relation_symbol_at_span(span), has_resolved_relation_read(symbol_id)
    • UseSite records extended to carry origin; UseOrigin::Variable added for variable uses
  • Traversal / recording
    • Updated src/sema/traverse.rs to tag relation uses with provenance:
      • rule heads -> UseOrigin::RelationHead
      • rule bodies -> UseOrigin::RelationBody
      • for-loops: iterable -> UseOrigin::ForIterable, guard -> UseOrigin::ForGuard
    • record_top_level_relation_use now accepts an origin parameter and uses a fixed UseKind::Relation
    • Updated collect_head_expr and collect_expression_term to pass appropriate provenance
  • Public API exposure
    • src/sema/mod.rs and related spots updated to expose UseOrigin, and tests import/use UseOrigin
  • Lint rule: unused-relation
    • New production lint rule module: src/linter/rules/correctness/unused_relation.rs
    • Exported via src/linter/rules/correctness/mod.rs and pub mod rules in src/linter/mod.rs
    • Rule uses semantic_provenance helpers to report unread declared relations
  • Tests
    • Updated semantic-model tests to assert provenance distinctions (head vs body, for-iterable/guard reads)
    • Updated tests/semantic_scope_resolution.rs to verify provenance handling
    • Added tests/unused_relation_rule.rs for end-to-end rule behavior
  • Documentation and roadmap
    • docs/ddlint-design.md updated to reflect relation use-site provenance and read-vs-write semantics
    • docs/parser-implementation-notes.md updated to mention provenance distinction
    • docs/roadmap.md item 4.1.1 updated to reflect implemented status
    • New ExecPlan doc: docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • Miscellaneous
    • Adjusted relevant imports and module exports to accommodate UseOrigin and new rule
    • Added helper methods to SemanticModel for relation symbol lookup and read-checks

Rationale

  • The unused-relation rule requires distinguishing reads from writes for relation uses. Carrying explicit provenance (UseOrigin) with relation uses allows accurate detection of unread declarations without re-walking the AST and simplifies rule implementation and testing.

Implementation plan (summary)

  1. Add semantic use provenance (done): extend UseSite with UseOrigin and add helpers on SemanticModel.
  2. Add semantic query helpers (done): relation_symbols(), relation_symbol_at_span(), has_resolved_relation_read().
  3. Implement the production rule (done): unused_relation.rs using declare_lint! with metadata name "unused-relation".
  4. Add rule-focused tests (done): unit tests for the rule and behavioural tests via Runner.
  5. Update docs and roadmap (done): reflect provenance contract and status.

Validation plan

  • Run unit tests for semantic model and rule helpers, behavioural tests for unused-relation, and the full test suite.

Progress

  • Semantic provenance implemented and wired through traversal
  • Production rule module and exports added
  • Semantic model helpers and tests updated
  • Rule-focused tests added
  • Documentation and roadmap updated
  • All related tests and gates should pass before merging

📎 Task: https://www.devboxer.com/task/dc1f967e-59b4-4962-badb-63f1a0e6adcd

Summary by Sourcery

Add semantic provenance for relation use sites and implement an unused-relation lint rule that flags declared relations which are never read, updating semantic queries, tests, and documentation accordingly.

New Features:

  • Introduce an unused-relation correctness lint rule that reports declared relations with no resolved read-like uses.
  • Expose semantic-model helpers for querying relation symbols, locating a relation declaration by span, and checking whether a relation has any resolved reads.
  • Add a production rules module hierarchy for lints, starting with correctness rules.

Enhancements:

  • Extend the semantic model with a UseOrigin enum on UseSite to distinguish relation writes in heads from reads in bodies and for iterables/guards, and to tag variable uses.
  • Record relation use-site provenance during semantic traversal for rule heads, bodies, and for constructs, and propagate it through the public API.
  • Strengthen semantic tests to assert correct relation-use origins and read-vs-write classification end-to-end.

Documentation:

  • Document relation use-site provenance and read-vs-write semantics in the design notes and parser implementation notes, and update the lint catalog description for unused-relation.
  • Add an execution plan document for implementing unused-relation and mark the corresponding roadmap item as completed.

Tests:

  • Add unit tests for semantic provenance helpers and relation read-detection, and behavioural tests validating unused-relation diagnostics and their ordering.

…entation

Add a detailed execution plan for implementing the `unused-relation` lint rule.
This document outlines the purpose, scope, design decisions, risks, and milestones
for adding the first production correctness lint rule that warns about relations
which are declared but never read from, distinguishing read vs write uses
semantically. It also defines the semantic provenance metadata needed,
testing strategies, and documentation and roadmap update plans.

This ExecPlan serves as the blueprint before implementing code,
reflecting all the project and quality constraints for item 4.1.1 in the roadmap.

No implementation code changes were made; this is a comprehensive specification
and planning document to guide future development.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Mar 21, 2026

Reviewer's Guide

Adds explicit provenance (UseOrigin) to semantic use sites, wires it through traversal, and builds an unused-relation production lint rule that uses new SemanticModel helpers to warn on relations that are never read, along with tests and documentation/roadmap updates.

Sequence diagram for unused-relation lint rule using semantic provenance

sequenceDiagram
    actor Developer
    participant Parser
    participant SemanticModelBuilder
    participant SemanticModel
    participant CstRuleStore
    participant Runner
    participant UnusedRelationRule

    Developer->>Parser: parse(source)
    Parser-->>Developer: ParsedProgram

    Developer->>SemanticModelBuilder: build(ParsedProgram)
    loop traverse_rules
        SemanticModelBuilder->>SemanticModelBuilder: collect_head_expr(expr, ctx)
        SemanticModelBuilder->>SemanticModelBuilder: record_top_level_relation_use(ctx, UseOrigin::RelationHead, expr)
        SemanticModelBuilder->>SemanticModel: push UseSite(kind=UseKind::Relation, origin=UseOrigin::RelationHead)

        SemanticModelBuilder->>SemanticModelBuilder: collect_expression_term(expr, context)
        SemanticModelBuilder->>SemanticModelBuilder: record_top_level_relation_use(context, UseOrigin::RelationBody, expr)
        SemanticModelBuilder->>SemanticModel: push UseSite(kind=UseKind::Relation, origin=UseOrigin::RelationBody)

        SemanticModelBuilder->>SemanticModelBuilder: collect_for_loop(for_loop, context)
        SemanticModelBuilder->>SemanticModelBuilder: record_top_level_relation_use(context, UseOrigin::ForIterable, for_loop.iterable)
        SemanticModelBuilder->>SemanticModel: push UseSite(kind=UseKind::Relation, origin=UseOrigin::ForIterable)
        opt guard_present
            SemanticModelBuilder->>SemanticModelBuilder: record_top_level_relation_use(context, UseOrigin::ForGuard, guard)
            SemanticModelBuilder->>SemanticModel: push UseSite(kind=UseKind::Relation, origin=UseOrigin::ForGuard)
        end
    end
    SemanticModelBuilder-->>Developer: SemanticModel

    Developer->>CstRuleStore: register(UnusedRelationRule)
    Developer->>Runner: new(&CstRuleStore, source, &ParsedProgram, RuleConfig)
    Developer->>Runner: run()

    loop each N_RELATION_DECL node
        Runner->>UnusedRelationRule: check_node(node, ctx, diagnostics)
        UnusedRelationRule->>SemanticModel: relation_symbol_at_span(span)
        SemanticModel-->>UnusedRelationRule: Option<SymbolId>
        alt symbol_found
            UnusedRelationRule->>SemanticModel: has_resolved_relation_read(symbol_id)
            SemanticModel-->>UnusedRelationRule: bool
            alt no_read_like_use
                UnusedRelationRule->>UnusedRelationRule: format message
                UnusedRelationRule->>Runner: push LintDiagnostic
            else has_read_like_use
                UnusedRelationRule->>Runner: no diagnostic
            end
        else no_symbol
            UnusedRelationRule->>Runner: no diagnostic
        end
    end

    Runner-->>Developer: Vec<LintDiagnostic>
Loading

Class diagram for semantic provenance and unused-relation lint rule

classDiagram
    class UseKind {
        <<enum>>
        Relation
        Variable
    }

    class UseOrigin {
        <<enum>>
        RelationHead
        RelationBody
        ForIterable
        ForGuard
        Variable
        +is_relation_read() bool
    }

    class Resolution {
        <<enum>>
        Unresolved
        Builtin
        Resolved
        Shadowed
    }

    class SymbolId {
        +0 usize
    }

    class Symbol {
        +name() &str
        +kind() DeclarationKind
        +span() &Span
    }

    class DeclarationKind {
        <<enum>>
        Relation
        Function
        Type
    }

    class Span {
    }

    class ScopeId {
    }

    class UseSite {
        -name String
        -kind UseKind
        -origin UseOrigin
        -scope ScopeId
        -span Span
        -source_order usize
        -resolution Resolution
        +kind() UseKind
        +origin() UseOrigin
        +scope() ScopeId
        +span() &Span
        +resolution() Resolution
    }

    class SemanticModel {
        -symbols Vec~Symbol~
        -uses Vec~UseSite~
        +symbols() &Vec~Symbol~
        +relation_symbols() Iterator~(SymbolId, &Symbol)~
        +uses() &Vec~UseSite~
        +symbol(id SymbolId) Option~&Symbol~
        +relation_symbol_at_span(span &Span) Option~SymbolId~
        +has_resolved_relation_read(symbol_id SymbolId) bool
    }

    class SemanticModelBuilder {
        +collect_head_expr(expr Expr, ctx RuleHeadContext)
        +collect_expression_term(expr Expr, context VariableUseContext)
        +collect_for_loop(for_loop RuleForLoop, context VariableUseContext)
        -record_top_level_relation_use(context VariableUseContext, origin UseOrigin, expr Expr)
    }

    class Expr {
    }

    class RuleHeadContext {
        +scope() ScopeId
        +span() &Span
    }

    class VariableUseContext {
        +current_scope() ScopeId
        +span() &Span
        +literal_index() usize
    }

    class Rule {
        <<interface>>
    }

    class LintDiagnostic {
        +new(name &str, message String, range TextRange) LintDiagnostic
        +rule_name() &str
        +message() &str
    }

    class TextRange {
    }

    class UnusedRelationRule {
        +name() &str
        +group() &str
        +level() Level
        +check_node(node SyntaxNode, ctx RuleCtx, diagnostics &mut Vec~LintDiagnostic~)
    }

    class RuleCtx {
        +semantic_model() &SemanticModel
    }

    class SyntaxNode {
        +text_range() TextRange
    }

    class SyntaxKind {
        <<enum>>
        N_RELATION_DECL
    }

    class Level {
        <<enum>>
        warn
    }

    UseSite --> UseKind
    UseSite --> UseOrigin
    UseSite --> ScopeId
    UseSite --> Span
    UseSite --> Resolution

    SemanticModel --> SymbolId
    SemanticModel --> Symbol
    SemanticModel --> UseSite
    SemanticModel --> Span
    SemanticModel --> UseKind
    SemanticModel --> UseOrigin
    SemanticModel --> Resolution

    SemanticModelBuilder --> SemanticModel : builds
    SemanticModelBuilder --> Expr
    SemanticModelBuilder --> RuleHeadContext
    SemanticModelBuilder --> VariableUseContext
    SemanticModelBuilder --> UseSite
    SemanticModelBuilder --> UseOrigin

    Symbol --> DeclarationKind
    Symbol --> Span

    UnusedRelationRule ..|> Rule
    UnusedRelationRule --> LintDiagnostic
    UnusedRelationRule --> SyntaxNode
    UnusedRelationRule --> RuleCtx
    UnusedRelationRule --> SemanticModel
    UnusedRelationRule --> SymbolId
    UnusedRelationRule --> Symbol
    UnusedRelationRule --> SyntaxKind
    UnusedRelationRule --> TextRange

    RuleCtx --> SemanticModel
Loading

File-Level Changes

Change Details Files
Introduce use-site provenance into the semantic model and expose helpers for relation queries.
  • Add UseOrigin enum to classify use provenance (relation head/body, for-iterable/guard, variable) with an is_relation_read helper.
  • Extend UseSite with an origin field and accessor, and ensure variable-use recording sets the appropriate origin.
  • Add SemanticModel helpers relation_symbols(), relation_symbol_at_span(), and has_resolved_relation_read() plus a small relation_symbol_id test helper.
src/sema/model.rs
src/sema/variables.rs
src/sema/tests.rs
Propagate provenance when recording relation uses during semantic traversal.
  • Update traversal to tag rule-head relation uses as RelationHead and body atoms as RelationBody.
  • Tag for-loop iterable relation uses as ForIterable and guards as ForGuard.
  • Change record_top_level_relation_use to take a UseOrigin (fixing UseKind::Relation internally) and populate UseSite.origin.
src/sema/traverse.rs
Tighten semantic-model tests to assert relation-use provenance and read classification end to end.
  • Add tests that distinguish origin for head vs body uses and for-iterable vs normal body reads.
  • Assert that semantic_model_records_declarations_and_resolved_uses_end_to_end and new scenarios check UseOrigin and is_relation_read semantics.
  • Verify that has_resolved_relation_read ignores head-only and unresolved uses.
src/sema/tests.rs
tests/semantic_scope_resolution.rs
Implement the production unused-relation lint rule and wire it into the linter rule hierarchy.
  • Add src/linter/rules/ module tree and expose it from src/linter/mod.rs.
  • Implement UnusedRelationRule via declare_lint! targeting relation declarations, resolving them to symbols by span, and using has_resolved_relation_read to decide whether to emit a diagnostic.
  • Provide unit tests for the rule module using Runner and CstRuleStore.
src/linter/mod.rs
src/linter/rules/mod.rs
src/linter/rules/correctness/mod.rs
src/linter/rules/correctness/unused_relation.rs
Add behavioural tests for the unused-relation rule to validate end-to-end diagnostics and ordering.
  • Introduce tests/unused_relation_rule.rs that runs Runner with UnusedRelationRule over various small programs.
  • Assert expected diagnostic messages, absence of false positives when relations are read, and deterministic ordering for multiple unused relations.
tests/unused_relation_rule.rs
Document the new provenance contract, the unused-relation rule semantics, and mark the roadmap item as complete.
  • Update dddlint-design.md to describe relation use-site provenance, clarify which positions count as reads, and refine the unused-relation catalog entry.
  • Update parser-implementation-notes.md to mention relation-use provenance guarantees.
  • Add an ExecPlan doc for implementing unused-relation diagnostics and mark roadmap item 4.1.1 as done.
docs/ddlint-design.md
docs/parser-implementation-notes.md
docs/roadmap.md
docs/execplans/4-1-1-implement-unused-relation-diagnostics.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 42026e77-e627-4aa1-af7f-dddbb2268a83

📥 Commits

Reviewing files that changed from the base of the PR and between 14beb63 and 1fb75cb.

📒 Files selected for processing (1)
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md

Summary by CodeRabbit

  • New Features

    • Added the unused-relation lint: warns when a declared relation has no resolved read-like uses; rule-head-only occurrences are treated as writes.
  • Documentation

    • Clarified relation use provenance and updated lint catalogue and roadmap to reflect the new read-vs-write semantics.
  • Tests

    • Added unit and behavioural tests validating the unused-relation rule and provenance-based read detection.

Walkthrough

Record per-use provenance distinguishing relation-head writes from rule-body/for-loop reads; expose semantic queries and indices to detect resolved relation reads; implement and export an unused-relation lint that warns when a declared relation has no resolved read-like uses; add tests, docs and an execplan.

Changes

Cohort / File(s) Summary
Documentation & Roadmap
docs/ddlint-design.md, docs/parser-implementation-notes.md, docs/roadmap.md, docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
Add provenance design and implementation notes; mark task 4.1.1 complete; update lint catalogue and unused-relation description to reflect read-vs-head semantics.
Semantic Model Core
src/sema/model.rs
Add UseOrigin enum and is_relation_read(); extend UseSite with origin and accessor; add precomputed indices and APIs: relation_symbols(), relation_symbol_at_span(), has_resolved_relation_read().
Semantic Exports
src/sema/mod.rs
Re-export UseOrigin publicly.
Semantic Collection & Builder
src/sema/traverse.rs, src/sema/variables.rs, src/sema/builder.rs
Record concrete UseOrigin for relation/variable uses; update traversal to pass specific origins; precompute span_to_relation_symbol and symbols_with_reads in model builder.
Sema Tests
src/sema/tests.rs, tests/semantic_scope_resolution.rs
Add and adapt tests to assert UseOrigin classifications and resolved-read detection; add helpers to locate relation symbol ids.
Linter Module Surface
src/linter/mod.rs, src/linter/rules/mod.rs, src/linter/rules/correctness/mod.rs
Export pub mod rules; and new correctness rules module; re-export UnusedRelationRule.
Unused-Relation Rule
src/linter/rules/correctness/unused_relation.rs
Implement UnusedRelationRule (unused-relation, warn) that resolves relation by declaration span and queries has_resolved_relation_read() to decide diagnostics; include unit tests for rule behaviour.
Behavioural Tests & Support
tests/unused_relation_rule.rs, tests/support.rs
Add behavioural tests for unused-relation; add run_unused_relation_rule test helper to parse and run the rule end-to-end.

Sequence Diagram(s)

sequenceDiagram
    participant Source as DDL Source
    participant Parser as Parser
    participant Builder as Semantic Builder
    participant Model as Semantic Model
    participant Rule as Unused-Relation Rule
    participant Diag as Lint Diagnostic

    Source->>Parser: Parse DDL
    Parser->>Builder: Provide CST/AST
    Builder->>Builder: Traverse nodes and record uses (with UseOrigin)
    Builder->>Model: Build SemanticModel (with span_to_relation_symbol, symbols_with_reads)
    Rule->>Model: relation_symbols()
    loop For each declared relation
        Rule->>Model: relation_symbol_at_span(span)
        Rule->>Model: has_resolved_relation_read(symbol_id)
        alt No read-like uses
            Rule->>Diag: Emit "relation `<name>` is declared but never read from"
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #243: Extends and modifies the semantic-model subsystem (UseSite/UseKind machinery) that this change builds upon.
  • PR #240: Introduced the lint/rule infrastructure (declare_lint!, Runner, CstRule) reused by the new unused-relation rule.

Poem

✨ Trace each use from head to read,
Mark the origins where they lead,
Find the relations left alone,
Sound the lint and raise the tone,
Tidy graphs and calm the feed.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title directly and clearly summarises the main change: introducing provenance for relation uses and implementing unused-relation diagnostics, which aligns with the primary objectives of the changeset.
Description check ✅ Passed The pull request description is comprehensive and clearly related to the changeset, detailing semantic model changes, traversal updates, the unused-relation lint rule, test additions, and documentation updates that all align with the provided file summaries.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch implement-unused-relation-diagnostics-l6v2bg

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

… relations

- Implement `unused-relation` lint rule to warn when relations are declared but never read from rule bodies, `for` iterables, or guards.
- Introduce UseOrigin in semantic model to distinguish relation use provenance (head writes vs body/iterable/guard reads).
- Extend semantic model with helpers for querying relation usage and read-status.
- Add comprehensive unit and behavioral tests validating rule correctness and semantic use provenance.
- Document the new rule contract, semantic provenance, and update design docs and roadmap to mark item 4.1.1 done.
- Passed all formatting, linting, and test CI gates.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Add ExecPlan doc for unused-relation diagnostics (4.1.1) Implement unused-relation diagnostics with semantic provenance Mar 22, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 22, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/sema/traverse.rs

Comment on file

        &mut self,
        context: VariableUseContext<'_>,
        use_kind: UseKind,
        origin: UseOrigin,

❌ New issue: Excess Number of Function Arguments
SemanticModelBuilder.record_top_level_relation_use has 5 arguments, max arguments = 4

@coderabbitai

This comment was marked as resolved.

…_use

Removed the UseKind::Relation parameter from the record_top_level_relation_use method and hardcoded UseKind::Relation within the method body. This simplifies the method calls by removing unnecessary argument passing where UseKind::Relation was always used.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Implement unused-relation diagnostics with semantic provenance Add provenance for relation uses and unused-relation diagnostics Mar 23, 2026
Refactored calls to record_top_level_relation_use by removing unnecessary multi-line formatting, consolidating them into single lines for improved readability and conciseness.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review March 23, 2026 09:31
sourcery-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e441b98531

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/sema/model.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sema/model.rs (1)

297-339: 🧹 Nitpick | 🔵 Trivial

Index relation lookups instead of rescanning the model.

relation_symbol_at_span() and has_resolved_relation_read() both walk the stored vectors linearly, and UnusedRelationRule::check_node() invokes both for every relation declaration. Precompute a span index and a read-set, or expose unread relations directly, so this rule stays near-linear on large schemas.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sema/model.rs` around lines 297 - 339, relation_symbol_at_span() and
has_resolved_relation_read() scan vectors on every call, causing
UnusedRelationRule::check_node() to be O(n^2); fix by precomputing indexes: add
a lookup from Span to SymbolId (e.g., HashMap<Span, SymbolId>) and a read-set of
relation SymbolIds (e.g., HashSet<SymbolId>) on the Model, populate them once
when the model is built or when uses are finalized (walk self.symbols once to
fill span->id and walk self.uses once to insert into read-set when
use_site.kind()==UseKind::Relation && use_site.origin().is_relation_read() &&
use_site.resolution()==Resolution::Resolved(id)), and replace
relation_symbol_at_span() and has_resolved_relation_read() to consult these
precomputed structures (or add accessors like relation_by_span() and
is_relation_read_resolved()) so UnusedRelationRule::check_node() can perform
near-linear checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/ddlint-design.md`:
- Around line 757-766: Add a short caption immediately above the rule-catalogue
markdown table (the block that begins with the header row starting "| Rule Name
| Group | Default Level | Autofixable | Description |" and contains entries like
"unused-relation" and "unused-variable"); insert a one-sentence caption (e.g.,
"Table: DDLint rule catalogue and metadata.") on its own line directly before
the table so the table can be referenced unambiguously in nearby prose.

In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Around line 12-15: Update the "purpose" wording for roadmap item 4.1.1 to
explicitly state that the change exports an unused-relation lint rule which
callers must register (rather than implying it will emit lint warnings by
default); mention the exported rule name `unused-relation` and that consumers
need to opt-in/register the rule, and apply the same rewording to the other
occurrence of this wording (the second instance referencing the same milestone).

In `@docs/roadmap.md`:
- Around line 359-360: Update roadmap item "4.1.1. Implement `unused-relation`
diagnostics" to use provenance-based read semantics by replacing the phrase "no
usage sites" with "no resolved read-like uses" (or equivalent wording "never
read-like / no resolved read-like uses") so that head-only writes are not
counted as satisfying usage; edit the text in the 4.1.1 bullet to reflect "no
resolved read-like uses" and ensure it references the same 3.3.2 and 3.3.4
requirements and ddlint-design.md §3.3.

In `@tests/semantic_scope_resolution.rs`:
- Around line 86-91: The current assertion uses source_uses.iter().all(...),
which vacuously passes for an empty collection; before the existing check in
tests/semantic_scope_resolution.rs add an explicit non-empty assertion (e.g.,
assert!(!source_uses.is_empty(), "Expected at least one Source use recorded"))
and then keep the existing assert that every use's origin().is_relation_read()
so you fail fast if no uses were collected while preserving the relation-read
validation.

In `@tests/unused_relation_rule.rs`:
- Around line 21-88: Add two additional rstest cases inside the
unused_relation_rule_matches_expected_messages test: one where a relation is
only read via a for-iterable (e.g. a rule body using "for x in SomeRel { ... }"
or the DSL equivalent that iterates over a relation) and one where a relation is
only read via a for-guard (e.g. "for x in OtherRel if cond { ... }"), both
expecting that relation not to be reported as unused; update the #[case(...)]
entries passed to run_rule (using the same concat! pattern and expected_messages
vector) so the test verifies the rule treats for-iterable and for-guard origins
as read-like, referencing the existing test function name
unused_relation_rule_matches_expected_messages and the helper run_rule to run
and assert diagnostics.

---

Outside diff comments:
In `@src/sema/model.rs`:
- Around line 297-339: relation_symbol_at_span() and
has_resolved_relation_read() scan vectors on every call, causing
UnusedRelationRule::check_node() to be O(n^2); fix by precomputing indexes: add
a lookup from Span to SymbolId (e.g., HashMap<Span, SymbolId>) and a read-set of
relation SymbolIds (e.g., HashSet<SymbolId>) on the Model, populate them once
when the model is built or when uses are finalized (walk self.symbols once to
fill span->id and walk self.uses once to insert into read-set when
use_site.kind()==UseKind::Relation && use_site.origin().is_relation_read() &&
use_site.resolution()==Resolution::Resolved(id)), and replace
relation_symbol_at_span() and has_resolved_relation_read() to consult these
precomputed structures (or add accessors like relation_by_span() and
is_relation_read_resolved()) so UnusedRelationRule::check_node() can perform
near-linear checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed4b5701-3494-4d23-962f-049496766e0e

📥 Commits

Reviewing files that changed from the base of the PR and between 10e5e01 and e441b98.

📒 Files selected for processing (15)
  • docs/ddlint-design.md
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • docs/parser-implementation-notes.md
  • docs/roadmap.md
  • src/linter/mod.rs
  • src/linter/rules/correctness/mod.rs
  • src/linter/rules/correctness/unused_relation.rs
  • src/linter/rules/mod.rs
  • src/sema/mod.rs
  • src/sema/model.rs
  • src/sema/tests.rs
  • src/sema/traverse.rs
  • src/sema/variables.rs
  • tests/semantic_scope_resolution.rs
  • tests/unused_relation_rule.rs

Comment thread docs/ddlint-design.md
Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread docs/roadmap.md Outdated
Comment thread tests/semantic_scope_resolution.rs
Comment thread tests/unused_relation_rule.rs
… relations

The `unused-relation` lint rule (`UnusedRelationRule`) is implemented and exported for registration in the lint runner. It warns about declared relations that have no read-like uses, considering rule bodies, `for` iterables, and guards as reads, but not rule heads.

Key changes include:
- Semantic model enhancements to efficiently track relation reads and quickly identify unused relations.
- A shared behavioral test helper introduced for running the unused-relation rule.
- Comprehensive unit and behavioral tests covering the new lint rule semantics.
- Documentation and roadmap updates describing the lint rule and its intended usage.

This is the first of the correctness lints in the DDLint catalog, enabling better program correctness diagnostics around unused declarations.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (2)
src/linter/rules/correctness/unused_relation.rs (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Align the rule docs with the resolved read-like contract.

never read from now underspecifies the shipped behaviour: head-only writes and unresolved body reads still warn. Mirror the has_resolved_relation_read() semantics in the module doc and Rustdoc.

✏️ Suggested wording
-//! `unused-relation` warns about declared relations that are never read from.
+//! `unused-relation` warns about declared relations that have no resolved
+//! read-like uses. Rule-head occurrences count as writes, not reads.
@@
-    /// Detects relations that are declared but never read from.
+    /// Detects relations that are declared but have no resolved read-like uses.
+    /// Rule-head occurrences count as writes, not reads.

Also applies to: 14-15

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/linter/rules/correctness/unused_relation.rs` at line 1, Update the module
doc and any Rustdoc in the unused-relation rule to reflect the resolved
read-like contract used by has_resolved_relation_read(): instead of saying
relations that are "never read from", document that the rule only treats reads
that are resolved as excluding a relation from warning (so head-only writes and
unresolved body reads still trigger unused-relation warnings); explicitly
reference has_resolved_relation_read() in the doc to mirror its semantics and
ensure the wording matches the implementation in the unused_relation module.
src/sema/tests.rs (1)

161-183: ⚠️ Potential issue | 🟡 Minor

Fail fast when these origin vectors are empty.

all(...) passes on an empty collection, so the Sink, Source, and Items assertions can hide a regression where relation uses stop being recorded at all.

🔧 Proposed hardening
     // `Sink` only appears in rule heads.
+    assert!(!sink_origins.is_empty(), "expected at least one `Sink` use");
     assert!(
         sink_origins
             .iter()
             .all(|origin| matches!(origin, UseOrigin::RelationHead)),
         "expected all `Sink` uses to be rule heads, got: {sink_origins:?}",
     );

     // `Source` only appears as a body read.
+    assert!(!source_origins.is_empty(), "expected at least one `Source` use");
     assert!(
         source_origins
             .iter()
             .all(|origin| matches!(origin, UseOrigin::RelationBody)),
         "expected all `Source` uses to be body reads, got: {source_origins:?}",
     );

     // `Items` only appears as the iterable of a `for`.
+    assert!(!items_origins.is_empty(), "expected at least one `Items` use");
     assert!(
         items_origins
             .iter()
             .all(|origin| matches!(origin, UseOrigin::ForIterable)),
         "expected all `Items` uses to be `for` iterables, got: {items_origins:?}",
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sema/tests.rs` around lines 161 - 183, The assertions using all(...) on
sink_origins, source_origins, and items_origins can silently succeed for empty
vectors; update the checks to first assert that each vector is non-empty and
then assert that every element matches the expected UseOrigin variant
(UseOrigin::RelationHead for sink_origins, UseOrigin::RelationBody for
source_origins, and UseOrigin::ForIterable for items_origins) so a regression
that stops recording uses fails fast; locate and modify the assertions
referencing sink_origins, source_origins, and items_origins in tests.rs to add
the non-empty precondition before the existing all(...) match checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Around line 171-180: The doc's proposed provenance list must be updated to
match the shipped UseOrigin contract: replace the current bullet list (and drop
the separate "semantic-rule head/body" distinctions) with the actual exported
variants used on UseSite — RelationHead, RelationBody, ForIterable, ForGuard,
and Variable — and fold any semantic-rule head/body discussion into those
variants if needed; ensure you reference the UseOrigin enum and UseSite
placement so the documentation exactly mirrors the runtime API.
- Line 139: Rename the remaining title-cased headings to sentence case: change
"Decision Log" to "Decision log", each "Milestone N: Add/Implement/Update ..."
to "Milestone N: Add/implement/update ...", "Surprises & Discoveries" to
"Surprises & discoveries", and "Outcomes & Retrospective" to "Outcomes &
retrospective"; apply the same sentence-case changes at the other occurrences
noted (lines referenced in the comment) so all headings in this document follow
the repository's sentence case documentation style.
- Around line 12-13: Replace all occurrences of the American spelling "catalog"
with British "catalogue" across the exec plan prose — specifically update the
sentence mentioning Roadmap item `4.1.1` and the exported lint
`unused-relation`, and also fix the other instances noted (the blocks
corresponding to the comments referencing ranges 121-122 and 290-291); ensure
every prose occurrence (not code identifiers) uses "catalogue" to conform to the
en-GB-oxendict style guide.

In `@src/sema/builder.rs`:
- Around line 73-79: Extract the combined predicate into a helper that returns
Option<SymbolId>: create a private method (e.g., resolve_relation_read(&self,
use_site: &UseSite) -> Option<SymbolId>) that checks use_site.kind() ==
UseKind::Relation, use_site.origin().is_relation_read(), and matches
use_site.resolution() to Resolution::Resolved(symbol_id) and returns
Some(symbol_id) or None; then replace the inline if in the loop over self.uses
(the block that currently inserts into symbols_with_reads) with a call to that
helper and insert the returned symbol_id when Some, so finish() becomes a simple
map/insert using that helper.

In `@src/sema/tests.rs`:
- Around line 134-139: The string passed to the #[with(...)] attribute uses
backslash newline continuations which are forbidden; replace that escaped
multiline literal with a concat!() call that concatenates separate short string
literals (e.g. "Sink(x) :- ", "Source(x), ", "for (y in Items(x)) Check(y), ",
"for (z in Items(x) if Check(z)) Check(z).") so the #[with(...)] invocation
contains concat!(...) instead of a single backslash-continued string; update the
attribute surrounding the rule text accordingly (look for the #[with(...)]
attribute and the rule text containing Sink, Source, Items, Check).

In `@tests/support.rs`:
- Line 1: Update the module doc comment at the top of tests/support.rs to use
en-GB spelling: change the word "behavioral" to "behavioural" in the
module-level doc string so the module doc follows the repo's en-GB-oxendict
convention.

---

Duplicate comments:
In `@src/linter/rules/correctness/unused_relation.rs`:
- Line 1: Update the module doc and any Rustdoc in the unused-relation rule to
reflect the resolved read-like contract used by has_resolved_relation_read():
instead of saying relations that are "never read from", document that the rule
only treats reads that are resolved as excluding a relation from warning (so
head-only writes and unresolved body reads still trigger unused-relation
warnings); explicitly reference has_resolved_relation_read() in the doc to
mirror its semantics and ensure the wording matches the implementation in the
unused_relation module.

In `@src/sema/tests.rs`:
- Around line 161-183: The assertions using all(...) on sink_origins,
source_origins, and items_origins can silently succeed for empty vectors; update
the checks to first assert that each vector is non-empty and then assert that
every element matches the expected UseOrigin variant (UseOrigin::RelationHead
for sink_origins, UseOrigin::RelationBody for source_origins, and
UseOrigin::ForIterable for items_origins) so a regression that stops recording
uses fails fast; locate and modify the assertions referencing sink_origins,
source_origins, and items_origins in tests.rs to add the non-empty precondition
before the existing all(...) match checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ce412d3-814e-4cee-a072-19b24f88c087

📥 Commits

Reviewing files that changed from the base of the PR and between e441b98 and 7bd94fe.

📒 Files selected for processing (10)
  • docs/ddlint-design.md
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • docs/roadmap.md
  • src/linter/rules/correctness/unused_relation.rs
  • src/sema/builder.rs
  • src/sema/model.rs
  • src/sema/tests.rs
  • tests/semantic_scope_resolution.rs
  • tests/support.rs
  • tests/unused_relation_rule.rs

Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread src/sema/builder.rs Outdated
Comment thread src/sema/tests.rs Outdated
Comment thread tests/support.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 26, 2026

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

Please address the comments from this code review:

## Overall Comments
- The combination of `relation_symbol_at_span` and `has_resolved_relation_read` results in an O(#relations × #uses) scan (each helper re-walks the full `uses` list); consider either precomputing a `SymbolId -> has_read` map once or exposing an iterator over unread relations so the `unused-relation` rule only needs one pass.
- `SemanticModel::relation_symbol_at_span` currently does a linear scan of all relation symbols on every invocation; if we expect large programs or frequent use of this helper, it may be worth indexing symbols by span (e.g., a `HashMap<Span, SymbolId>`) during model construction to avoid repeated scans.

## Individual Comments

### Comment 1
<location path="src/sema/tests.rs" line_range="133-142" />
<code_context>
+fn relation_use_origins_distinguish_heads_from_reads(
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for `UseOrigin::ForGuard` and multiple use-sites per relation

Currently the test only covers head/body/for-iterable provenance, never asserts `UseOrigin::ForGuard`, and only checks `first()` rather than all uses.

To better lock in provenance behavior, please:
- Add a test that builds a `for` with a guard using a relation (e.g. `for (x in Source(x)) when Check(x) ...`) and asserts that the relation is tagged with `UseOrigin::ForGuard`.
- In this or a separate test, assert over all recorded use-sites (e.g. `iter().map(origin).collect()`) so regressions affecting additional uses (multiple heads/body atoms) are caught.

Suggested implementation:

```rust
#[rstest]
fn relation_use_origins_distinguish_heads_from_reads(
    #[with(
        "Sink(x) :- \
         Source(x), \
         for (y in Items(x)) Check(y), \
         for (z in Items(x)) when Check(z) Check(z)."
    )]
    semantic_model: super::SemanticModel,
) {
    let sink_uses = uses_named(&semantic_model, "Sink", UseKind::Relation);
    let source_uses = uses_named(&semantic_model, "Source", UseKind::Relation);
    let items_uses = uses_named(&semantic_model, "Items", UseKind::Relation);
    let check_uses = uses_named(&semantic_model, "Check", UseKind::Relation);

    let sink_origins: Vec<_> = sink_uses.iter().map(|use_site| use_site.origin()).collect();
    let source_origins: Vec<_> = source_uses.iter().map(|use_site| use_site.origin()).collect();
    let items_origins: Vec<_> = items_uses.iter().map(|use_site| use_site.origin()).collect();
    let check_origins: Vec<_> = check_uses.iter().map(|use_site| use_site.origin()).collect();

    // `Sink` only appears in rule heads.
    assert!(
        sink_origins
            .iter()
            .all(|origin| matches!(origin, UseOrigin::Head)),
        "expected all `Sink` uses to be rule heads, got: {:?}",
        sink_origins
    );

    // `Source` only appears as a body read.
    assert!(
        source_origins
            .iter()
            .all(|origin| matches!(origin, UseOrigin::Body)),
        "expected all `Source` uses to be body reads, got: {:?}",
        source_origins
    );

    // `Items` only appears as the iterable of a `for`.
    assert!(
        items_origins
            .iter()
            .all(|origin| matches!(origin, UseOrigin::ForIterable)),
        "expected all `Items` uses to be `for` iterables, got: {:?}",
        items_origins
    );

    // `Check` appears both in the `for` body and in the `for` guard.
    assert!(
        check_origins
            .iter()
            .any(|origin| matches!(origin, UseOrigin::Body)),
        "expected at least one body use for `Check`, got: {:?}",
        check_origins
    );
    assert!(
        check_origins
            .iter()
            .any(|origin| matches!(origin, UseOrigin::ForGuard)),
        "expected at least one guard use for `Check`, got: {:?}",
        check_origins
    );

```

Because only part of the original test body was visible in the snippet, you should:

1. Remove any remaining tail of the old `relation_use_origins_distinguish_heads_from_reads` test that may still appear after the replaced `assert!` calls (e.g. the rest of the old `assert_eq!(...)` or extra assertions/closing brace) so that the function body is exactly what’s in the REPLACE block plus a single closing `}`.
2. Verify the actual `UseOrigin` variant names in your codebase. If they differ, adjust the `matches!(..., UseOrigin::Head)`, `UseOrigin::Body`, `UseOrigin::ForIterable`, and `UseOrigin::ForGuard` patterns to the correct variants.
3. Ensure that `UseOrigin` is in scope in this module (either via `use` or full path like `super::UseOrigin`) so that the new `matches!` expressions compile.
4. If your `for`/`when` syntax differs slightly (e.g. `when` keyword or guard position), tweak the program string to the precise syntax your parser expects while preserving:
   - a `for` with a guard using `Check` (`UseOrigin::ForGuard`),
   - at least one non-guard use of `Check` in the body,
   - multiple uses of `Items` as `for` iterables.
</issue_to_address>

### Comment 2
<location path="tests/unused_relation_rule.rs" line_range="8-17" />
<code_context>
+use ddlint::parse;
+use rstest::rstest;
+
+fn run_rule(source: &str) -> Vec<ddlint::linter::LintDiagnostic> {
+    let parsed = parse(source);
+    assert!(
+        parsed.errors().is_empty(),
+        "unused-relation behavioural source should parse cleanly: {:?}",
+        parsed.errors()
+    );
+
+    let mut store = CstRuleStore::new();
+    store.register(Box::new(UnusedRelationRule));
+    Runner::new(&store, source, &parsed, RuleConfig::new()).run()
+}
+
</code_context>
<issue_to_address>
**suggestion:** Avoid duplicating the `run_rule` helper across unit and behavioural tests

This helper closely matches `run_rule` in `src/linter/rules/correctness/unused_relation.rs` tests. To avoid duplicated logic and keep unit and behavioural tests in sync, please extract a shared test utility (e.g. in `tests/support`) or reuse the existing helper via `#[cfg(test)]` so changes to rule-running behavior only need to be made once.

Suggested implementation:

```rust
 //! Behavioural tests for the shipped `unused-relation` lint rule.

 use rstest::rstest;

 mod support;
 use support::run_unused_relation_rule;

```

```rust
fn run_rule(source: &str) -> Vec<ddlint::linter::LintDiagnostic> {
    run_unused_relation_rule(source)
}

```

To fully avoid duplication and centralize the rule-running logic, you should also:

1. Add a shared test utility module for rule execution, e.g. `tests/support/mod.rs` (or `tests/support.rs`), with something like:
   ```rust
   use ddlint::linter::rules::correctness::UnusedRelationRule;
   use ddlint::linter::{CstRuleStore, RuleConfig, Runner};
   use ddlint::parse;

   pub fn run_unused_relation_rule(source: &str) -> Vec<ddlint::linter::LintDiagnostic> {
       let parsed = parse(source);
       assert!(
           parsed.errors().is_empty(),
           "unused-relation test source should parse cleanly: {:?}",
           parsed.errors()
       );

       let mut store = CstRuleStore::new();
       store.register(Box::new(UnusedRelationRule));
       Runner::new(&store, source, &parsed, RuleConfig::new()).run()
   }
   ```
   Ensure the path (`mod support; use support::run_unused_relation_rule;`) in `tests/unused_relation_rule.rs` matches how you lay out the support module (`tests/support.rs` vs `tests/support/mod.rs`).

2. Update the unit tests in `src/linter/rules/correctness/unused_relation.rs` (or wherever the existing `run_rule` helper lives) to use this shared helper as well:
   - Either move their helper into the new test support module and import it there, or
   - Make the shared helper live in a `#[cfg(test)]` module inside the main crate and import it both from the unit test module and from `tests/unused_relation_rule.rs` (adjusting paths accordingly).

3. Once unit tests are switched to the shared helper, you can delete the original, now-duplicated `run_rule` helper from the unit test file so that the rule-running behavior is defined in only one place.
</issue_to_address>

### Comment 3
<location path="docs/execplans/4-1-1-implement-unused-relation-diagnostics.md" line_range="12-15" />
<code_context>
+
+Roadmap item `4.1.1` is the first production lint rule in the initial
+correctness catalog. After this change, a contributor should be able to lint a
+DDlog file and receive a `unused-relation` warning for each declared relation
+that is never read from anywhere in the analysed program.
+
</code_context>
<issue_to_address>
**suggestion (typo):** Use the article "an" instead of "a" before `unused-relation`.

This should read "receive an `unused-relation` warning" to use the correct article for a vowel sound.

```suggestion
Roadmap item `4.1.1` is the first production lint rule in the initial
correctness catalog. After this change, a contributor should be able to lint a
DDlog file and receive an `unused-relation` warning for each declared relation
that is never read from anywhere in the analysed program.
```
</issue_to_address>

### Comment 4
<location path="docs/execplans/4-1-1-implement-unused-relation-diagnostics.md" line_range="77" />
<code_context>
+- Keep scope limited to implementing `unused-relation`, the additive semantic
+  provenance needed for it, its tests, and the required documentation and
+  roadmap updates.
+- Do not implement `unused-variable`, `shadowed-variable`, CLI rule listing,
+  configuration-file loading, or rich `miette` conversion in this milestone.
+- Keep parser grammar and parse-stage diagnostics unchanged unless a bug blocks
</code_context>
<issue_to_address>
**issue (review_instructions):** The acronym “CLI” is introduced without definition on first use.

Consider expanding the acronym on first mention, for example:

> - Do not implement `unused-variable`, `shadowed-variable`, command-line interface (CLI) rule listing,

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me inspect the current state of the relevant files.


🏁 Script executed:

#!/bin/bash
echo "=== src/sema/tests.rs (relevant section) ==="
rg -n "relation_use_origins_distinguish_heads_from_reads|ForGuard|ForIterable|RelationHead|RelationBody|check_origins|sink_origins|source_origins|items_origins" src/sema/tests.rs

echo ""
echo "=== tests/unused_relation_rule.rs ==="
cat tests/unused_relation_rule.rs

echo ""
echo "=== tests/support.rs ==="
cat tests/support.rs 2>/dev/null || echo "(file not found)"

echo ""
echo "=== docs/execplans/4-1-1-implement-unused-relation-diagnostics.md (lines 10-20 and 74-82) ==="
sed -n '10,20p' docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
echo "..."
sed -n '74,82p' docs/execplans/4-1-1-implement-unused-relation-diagnostics.md

echo ""
echo "=== src/sema/model.rs — relation_symbol_at_span and has_resolved_relation_read ==="
rg -n "relation_symbol_at_span|has_resolved_relation_read|span_to_relation_symbol|symbols_with_reads|HashMap|HashSet" src/sema/model.rs | head -40

echo ""
echo "=== src/sema/builder.rs — finish method ==="
rg -n "span_to_relation_symbol|symbols_with_reads|finish" src/sema/builder.rs | head -30

Length of output: 6993


@leynos — yes, all of the comments are resolved. Here is a summary:

Overall performance comments — ✅ Both addressed via precomputation in SemanticModelBuilder::finish():

  • relation_symbol_at_span is now an O(1) HashMap lookup (span_to_relation_symbol).
  • has_resolved_relation_read is now an O(1) HashSet lookup (symbols_with_reads).

Comment 1 — ForGuard coverage + all-use-site assertions — ✅ Resolved. src/sema/tests.rs now collects all origins into Vecs and asserts that Check carries both UseOrigin::RelationBody and UseOrigin::ForGuard uses. (Note: the reviewer's suggested snippet used incorrect variant names UseOrigin::Head/UseOrigin::Body; the implementation correctly applied the real names UseOrigin::RelationHead/UseOrigin::RelationBody.)

Comment 2 — deduplicate run_rule — ✅ Resolved. tests/support.rs exposes run_unused_relation_rule; tests/unused_relation_rule.rs delegates to it via mod support; use support::run_unused_relation_rule;.

Comment 3 — article "a" → "an" — ✅ Resolved. The sentence was reworded and the article-before-backtick issue no longer appears.

Comment 4 — expand "CLI" acronym — ✅ Resolved. The text now reads "command-line interface (CLI) rule listing".

en-GB-oxendict / cyclomatic-complexity filter — no requirements to annul. None of the suggestions introduced non-en-GB-oxendict spellings or unnecessary branching.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/sema/builder.rs

Comment on file

            if use_site.kind() == UseKind::Relation
                && use_site.origin().is_relation_read()
                && let Resolution::Resolved(symbol_id) = use_site.resolution()

❌ New issue: Complex Conditional
SemanticModelBuilder.finish has 1 complex conditionals with 2 branches, threshold = 2

@coderabbitai

This comment was marked as resolved.

…l updates

- Add `unused-relation` lint rule detecting declared relations with no resolved read-like uses.
- Extend semantic model with `UseOrigin` enum variants to distinguish relation use origins (head writes vs body reads, for iterables, guards, variables).
- Introduce semantic helpers in `SemanticModelBuilder` for tracking resolved relation reads.
- Implement `UnusedRelationRule` using the new semantic queries.
- Add focused unit and behavioral tests to verify read vs write use distinctions.
- Update documentation and roadmap to clarify read-versus-write distinction semantics.

This change improves correctness linting by accurately differentiating read and write usages of relations, enabling meaningful unused relation warnings.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Line 61: Replace the American spelling "rule-catalog" with the British English
"rule-catalogue" wherever the string occurs in this document (specifically
replace the token "rule-catalog" used in the sentence "There is no shipped
rule-catalog module yet.") so it adheres to the en-GB-oxendict -ogue convention.
- Around line 407-411: The list of gate command lines has inconsistent
indentation: remove the extra leading whitespace before the backticks on the
`set -o pipefail; make nixie 2>&1 | tee /tmp/4-1-1-make-nixie.log` line so it
matches the surrounding entries (ensure all five backticked command lines have
the same left alignment).

In `@src/linter/rules/correctness/unused_relation.rs`:
- Around line 17-20: Remove the local private function text_range_to_span and
instead import the existing pub(crate) implementation by adding use
crate::parser::ast::rule::text_range_to_span; at the top of the file; ensure any
local references (text_range_to_span) now resolve to that imported symbol so you
don't duplicate the function already defined in rule.rs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b43e63a4-c325-47c0-9a61-8f0088cbb317

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd94fe and 045e964.

📒 Files selected for processing (5)
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • src/linter/rules/correctness/unused_relation.rs
  • src/sema/builder.rs
  • src/sema/tests.rs
  • tests/support.rs

Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread src/linter/rules/correctness/unused_relation.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/sema/builder.rs

Comment on file

        if use_site.kind() == UseKind::Relation
            && use_site.origin().is_relation_read()
            && let Resolution::Resolved(symbol_id) = use_site.resolution()

❌ New issue: Complex Conditional
SemanticModelBuilder.resolve_relation_read has 1 complex conditionals with 2 branches, threshold = 2

@coderabbitai

This comment was marked as resolved.

…ad filtering

Removed the unused `resolve_relation_read` helper function from `SemanticModelBuilder` and refactored relation read filtering logic inline for clarity and conciseness. Also removed redundant code in `unused_relation` lint rule (removing a local `text_range_to_span` that duplicates an imported function). Minor docs fix to wording in milestone documentation.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
docs/execplans/4-1-1-implement-unused-relation-diagnostics.md (2)

152-156: ⚠️ Potential issue | 🟡 Minor

Replace rule-catalog with rule-catalogue.

Fix Line 155 to keep en-GB-oxendict spelling consistent in prose.

✏️ Proposed fix
-  rule-catalog registration surface, and adding one would broaden scope beyond
+  rule-catalogue registration surface, and adding one would broaden scope beyond

As per coding guidelines, "Use British English based on the Oxford English Dictionary (en-GB-oxendict): use ... -ogue endings (analogue, catalogue)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md` around lines
152 - 156, Replace the American spelling "rule-catalog" with the British Oxford
spelling "rule-catalogue" in the decision prose that begins "Decision: expose
the first production rule..." (the sentence that also mentions CstRuleStore);
update that token so the document uses en-GB-oxendict "-ogue" spelling
consistently.

406-410: ⚠️ Potential issue | 🟡 Minor

Fix misaligned command indentation in the gate list.

Remove the extra leading space on Line 409 to keep list continuation formatting consistent.

✏️ Proposed fix
 - Passed gate commands:
   `set -o pipefail; make fmt 2>&1 | tee /tmp/4-1-1-final-make-fmt.log`
   `set -o pipefail; make markdownlint 2>&1 | tee /tmp/4-1-1-make-markdownlint.log`
-   `set -o pipefail; make nixie 2>&1 | tee /tmp/4-1-1-make-nixie.log`
+  `set -o pipefail; make nixie 2>&1 | tee /tmp/4-1-1-make-nixie.log`
   `set -o pipefail; make check-fmt 2>&1 | tee /tmp/4-1-1-final-check-fmt.log`

As per coding guidelines, "Follow markdownlint-cli2 recommendations in documentation".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md` around lines
406 - 410, Fix the misaligned markdown list item in the gate commands by
removing the extra leading space before the backticked command on the line
containing "`set -o pipefail; make nixie 2>&1 | tee /tmp/4-1-1-make-nixie.log`"
so it is aligned with the other list entries in the gate list within the
docs/execplans/4-1-1-implement-unused-relation-diagnostics.md file; ensure the
three backticked command lines all start at the same indentation level to
satisfy markdownlint-cli2 recommendations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Around line 61-64: Replace the first occurrence of the acronym "CLI" with its
expanded form "Command-Line Interface (CLI)" in the document (so readers see the
full phrase at first use), and leave subsequent occurrences as "CLI"; search for
the string "CLI" in the section around the noted paragraph to locate the
instance to update.
- Around line 380-384: Update the paragraph discussing the top-level for
desugaring so it records a historical observation rather than asserting current
behavior: change the wording around the `ForIterable` origin to state that early
experiments recorded iterable relation reads as semantic-rule body reads, but
the shipped contract treats those uses as read-like and tests should assert
read-versus-write semantics (not the desugaring detail). Reference `ForIterable`
and the durable rule contract in the rewrite to make clear this is a historical
note, not the documented current behavior.

---

Duplicate comments:
In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Around line 152-156: Replace the American spelling "rule-catalog" with the
British Oxford spelling "rule-catalogue" in the decision prose that begins
"Decision: expose the first production rule..." (the sentence that also mentions
CstRuleStore); update that token so the document uses en-GB-oxendict "-ogue"
spelling consistently.
- Around line 406-410: Fix the misaligned markdown list item in the gate
commands by removing the extra leading space before the backticked command on
the line containing "`set -o pipefail; make nixie 2>&1 | tee
/tmp/4-1-1-make-nixie.log`" so it is aligned with the other list entries in the
gate list within the
docs/execplans/4-1-1-implement-unused-relation-diagnostics.md file; ensure the
three backticked command lines all start at the same indentation level to
satisfy markdownlint-cli2 recommendations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 462dfa0e-2cc8-4467-8e0b-d4c25f18d78d

📥 Commits

Reviewing files that changed from the base of the PR and between 045e964 and b060b04.

📒 Files selected for processing (3)
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • src/linter/rules/correctness/unused_relation.rs
  • src/sema/builder.rs

Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 26, 2026

@CodeRabbit Re:

-        if use_site.kind() == UseKind::Relation
-            && use_site.origin().is_relation_read()
-            && let Resolution::Resolved(symbol_id) = use_site.resolution()

can you suggest an alternative solution using an extracted predicate function?

@coderabbitai

This comment was marked as resolved.

…ostics

- Expanded CLI to Command-Line Interface for clarity.
- Corrected spelling of "rule-catalogue".
- Reworded observations on for-loop desugaring for better understanding.
- Converted multiline commands under Outcomes & retrospective to a fenced code block for proper markdown formatting.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

… logic

Introduced a new helper method `is_relation_read_use` to encapsulate the check for relation read uses. This refactor improves code readability by avoiding inline complex filter expressions in the `finish` method.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 26, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/4-1-1-implement-unused-relation-diagnostics.md`:
- Around line 190-197: Update the implemented-state docs to remove the
non-shipped helper relation_reads() and replace it with the shipped helpers:
keep model.relation_symbols() and model.has_resolved_relation_read(symbol_id)
and add model.relation_symbol_at_span(span) instead of relation_reads(); ensure
the snippet exactly matches the shipped API so readers won't see
relation_reads() referenced where it does not exist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: caea4e61-d212-483c-a18f-2e2caf7dbd55

📥 Commits

Reviewing files that changed from the base of the PR and between b060b04 and 14beb63.

📒 Files selected for processing (2)
  • docs/execplans/4-1-1-implement-unused-relation-diagnostics.md
  • src/sema/builder.rs

Comment thread docs/execplans/4-1-1-implement-unused-relation-diagnostics.md Outdated
…diagnostics

Update documentation to specify the shipped API for semantic helpers in the unused relation diagnostics rule. The commit revises method descriptions and guidance to encourage using `relation_symbol_at_span(span)` for symbol lookups instead of ad hoc filtering, making the rule implementation clearer and more maintainable.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos merged commit 2cdd3e2 into main Mar 26, 2026
4 checks passed
@leynos leynos deleted the implement-unused-relation-diagnostics-l6v2bg branch March 26, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant