lint: redundant-closure, a closure that only passes its parameters on #9

Closed
opened 2026-10-05 21:07:09 +00:00 by orion · 4 comments
Collaborator

Context

torb lint names two rules that are not built (torb-lint.md, "What is not decided", ROADMAP torb lint). This issue builds the first: a closure that only passes its parameters on. The second (a case written with its type where the expected type names it) was split into its own issue, because an accepted design record (CLOSED-TRAITS, section 6) removes the leading-dot spelling that rule would rewrite to.

Goal

A new rule redundant-closure finds a closure that does nothing but call a declared function with its own parameters, and its fix writes the function itself: items.map { stripMargin(_) } becomes items.map(stripMargin). Like every rule it always runs (#157: no configuration, no off-by-default); the standard library and the compiler are swept clean instead.

Design record points (pinned)

  • Closures, rule 4: a fn declared by name can be passed exactly where a closure is expected - the rewrite is always expressible.
  • docs/tooling/torb-lint.md: a rule that knows the fix for certain carries it; a rule that needs the expected type or resolution runs the checker (as labeled-literal and redundant-wrap do, with a checker table such as redundantWrapsAt). The ROADMAP text already says the fix is "only certain where the callee takes exactly that one parameter".
  • #157: the rule set is fixed by the toolchain version; the rule has no setting and no suppression comment.
  • Rule id redundant-closure (kebab-case, names what is wrong, like redundant-wrap); the finding is a warning like every other rule, there is no severity choice.

What the rule finds

A closure (trailing, parenthesized or command form; implicit _, _2 parameters or explicit ones) whose whole body is one call of a declared function (top-level or local fn, or a static fn by its path) with exactly the closure's parameters, each once, in order, positionally. The finding carries a fix only where the checker is certain the function reference means the same:

  • the callee has exactly as many parameters as the closure and none of them has a default, a label the call relies on, or a mode (var, lazy) the closure's own parameter does not share;
  • the closure's expected type and the callee's type are the same type: no adaptation was recorded on an argument or on the result (a conversion, a wrap, Force), no generic callee (type arguments inferred at the call) in this first version;
  • the callee is a declared function, not a local const or field that holds a closure (that value could differ between the closure's calls).

Anything else gets no finding, or a finding without a fix where it is clearly the same pattern but one condition above fails (the message says which). A method call ({ _.trim() }) is out of scope.

Acceptance criteria

  • The rule redundant-closure exists in compiler/src/lint/, is listed in LintRule and in the synopsis of torb lint, runs in every plain torb lint run, and has --fix
  • { f(_) }, { f(_, _2) } and { x => f(x) } in every closure position (trailing, parenthesized, command form, as the only or the last argument) are found, and the fix writes f at the same argument position, labelled where the parameter's label requires it
  • Each condition above that is not met gets a test that shows no fix (a default, a mode, an adaptation, a generic callee, a callee that is a stored closure, the arguments out of order or used twice)
  • torb check accepts every fixed file in the tests (the test runs the fix and checks the result)
  • torb lint over std and compiler/src reports no redundant-closure finding (CI shows it; a grep finds no obvious hit today, so the sweep is likely empty or tiny); no rule is documented as "off by default"
  • docs/tooling/torb-lint.md documents the rule (synopsis, rules section with a trb check example, the "rules named and not built" entry reduced to the case rule) and compiler/CONTRIBUTING.md mentions it where it lists the lint rules; ROADMAP torb lint text names it; generated skills/ regenerated

Seed

No. The rule is written in TorbScript with the existing syntax, the checker table is ordinary compiler code, no native, no driver flag, and f as a function value is already accepted by the seed.

Size and commits

One PR, one or two Conventional Commits: feat(lint): find a closure that only passes its parameters on, and, only if the sweep finds something, refactor: pass the function itself instead of a closure that forwards to it.

Implementer

opus / high (the rule needs a checker table in compiler/src/semantics/checker/, so it is compiler work).

Collisions

Touches compiler/src/lint/command.trb, finding.trb and docs/tooling/torb-lint.md, as #157 does (removal of --skip); whichever lands second rebases. No overlap with the running work on #6, #7 and #8.

## Context `torb lint` names two rules that are not built ([torb-lint.md, "What is not decided"](https://git.torb.dev/torbscript/language/src/branch/main/docs/tooling/torb-lint.md), ROADMAP `torb lint`). This issue builds the first: a closure that only passes its parameters on. The second (a case written with its type where the expected type names it) was split into its own issue, because an accepted design record (CLOSED-TRAITS, section 6) removes the leading-dot spelling that rule would rewrite to. ## Goal A new rule `redundant-closure` finds a closure that does nothing but call a declared function with its own parameters, and its fix writes the function itself: `items.map { stripMargin(_) }` becomes `items.map(stripMargin)`. Like every rule it always runs (#157: no configuration, no off-by-default); the standard library and the compiler are swept clean instead. ## Design record points (pinned) - [Closures, rule 4](https://git.torb.dev/torbscript/language/src/branch/main/docs/language/functions/closures.md): a `fn` declared by name can be passed exactly where a closure is expected - the rewrite is always expressible. - `docs/tooling/torb-lint.md`: a rule that knows the fix for certain carries it; a rule that needs the expected type or resolution runs the checker (as `labeled-literal` and `redundant-wrap` do, with a checker table such as `redundantWrapsAt`). The ROADMAP text already says the fix is "only certain where the callee takes exactly that one parameter". - #157: the rule set is fixed by the toolchain version; the rule has no setting and no suppression comment. - Rule id `redundant-closure` (kebab-case, names what is wrong, like `redundant-wrap`); the finding is a `warning` like every other rule, there is no severity choice. ## What the rule finds A closure (trailing, parenthesized or command form; implicit `_`, `_2` parameters or explicit ones) whose whole body is one call of a declared function (top-level or local `fn`, or a `static fn` by its path) with exactly the closure's parameters, each once, in order, positionally. The finding carries a fix only where the checker is certain the function reference means the same: - the callee has exactly as many parameters as the closure and none of them has a default, a label the call relies on, or a mode (`var`, `lazy`) the closure's own parameter does not share; - the closure's expected type and the callee's type are the same type: no adaptation was recorded on an argument or on the result (a conversion, a wrap, `Force`), no generic callee (type arguments inferred at the call) in this first version; - the callee is a declared function, not a local `const` or field that holds a closure (that value could differ between the closure's calls). Anything else gets no finding, or a finding without a fix where it is clearly the same pattern but one condition above fails (the message says which). A method call (`{ _.trim() }`) is out of scope. ## Acceptance criteria - [ ] The rule `redundant-closure` exists in `compiler/src/lint/`, is listed in `LintRule` and in the synopsis of `torb lint`, runs in every plain `torb lint` run, and has `--fix` - [ ] `{ f(_) }`, `{ f(_, _2) }` and `{ x => f(x) }` in every closure position (trailing, parenthesized, command form, as the only or the last argument) are found, and the fix writes `f` at the same argument position, labelled where the parameter's label requires it - [ ] Each condition above that is not met gets a test that shows no fix (a default, a mode, an adaptation, a generic callee, a callee that is a stored closure, the arguments out of order or used twice) - [ ] `torb check` accepts every fixed file in the tests (the test runs the fix and checks the result) - [ ] `torb lint` over `std` and `compiler/src` reports no `redundant-closure` finding (CI shows it; a grep finds no obvious hit today, so the sweep is likely empty or tiny); no rule is documented as "off by default" - [ ] `docs/tooling/torb-lint.md` documents the rule (synopsis, rules section with a `trb check` example, the "rules named and not built" entry reduced to the case rule) and `compiler/CONTRIBUTING.md` mentions it where it lists the lint rules; ROADMAP `torb lint` text names it; generated `skills/` regenerated ## Seed No. The rule is written in TorbScript with the existing syntax, the checker table is ordinary compiler code, no native, no driver flag, and `f` as a function value is already accepted by the seed. ## Size and commits One PR, one or two Conventional Commits: `feat(lint): find a closure that only passes its parameters on`, and, only if the sweep finds something, `refactor: pass the function itself instead of a closure that forwards to it`. ## Implementer opus / high (the rule needs a checker table in `compiler/src/semantics/checker/`, so it is compiler work). ## Collisions Touches `compiler/src/lint/command.trb`, `finding.trb` and `docs/tooling/torb-lint.md`, as #157 does (removal of `--skip`); whichever lands second rebases. No overlap with the running work on #6, #7 and #8.
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Two lint rules named in ROADMAP torb lint; tooling only, no seed impact expected. The rule ids and the on/off-by-default choice may need an owner decision, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued, buffer >= 2 ready)

[loop:torbscript] intake: in scope (author orion, label loop). Two lint rules named in ROADMAP `torb lint`; tooling only, no seed impact expected. The rule ids and the on/off-by-default choice may need an owner decision, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued, buffer >= 2 ready)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T02:55:28Z

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T02:55:28Z
orion changed title from lint: the two named rules that are not built to lint: redundant-closure, a closure that only passes its parameters on 2026-10-08 00:58:18 +00:00
Author
Collaborator

[loop:torbscript] refine: rewrote this issue to the closure rule redundant-closure (goal, conditions for a certain fix, testable criteria, pinned records, seed impact: no, one PR with one or two Conventional Commits) and set ready; the second rule (case written with its type) is split into #168 because the accepted CLOSED-TRAITS record removes the .Case spelling it would rewrite to, so it carries an owner decision there. #157 settles on/off: every rule always runs, so "off by default" is dropped and the sweep must be clean. Implementer: opus/high (checker table in compiler/src). Collides on command.trb, finding.trb and torb-lint.md with #157 only (second to land rebases); no overlap with #6, #7, #8. Worker usage ~60000 tokens, next: implement

[loop:torbscript] refine: rewrote this issue to the closure rule `redundant-closure` (goal, conditions for a certain fix, testable criteria, pinned records, seed impact: no, one PR with one or two Conventional Commits) and set `ready`; the second rule (case written with its type) is split into #168 because the accepted CLOSED-TRAITS record removes the `.Case` spelling it would rewrite to, so it carries an owner decision there. #157 settles on/off: every rule always runs, so "off by default" is dropped and the sweep must be clean. Implementer: opus/high (checker table in compiler/src). Collides on `command.trb`, `finding.trb` and `torb-lint.md` with #157 only (second to land rebases); no overlap with #6, #7, #8. Worker usage ~60000 tokens, next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261008 until 2026-10-08T06:59:32Z

[loop:torbscript] claim implement by session:loop-torbscript-20261008 until 2026-10-08T06:59:32Z
argus closed this issue 2026-10-08 03:39:57 +00:00
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#9
No description provided.