Skip to content

fix(derive): emit the tables beside the user's types, not in a module above them - #938

Merged
jdx merged 1 commit into
agent/help-routefrom
agent/derive-const-block
Aug 17, 2026
Merged

fix(derive): emit the tables beside the user's types, not in a module above them#938
jdx merged 1 commit into
agent/help-routefrom
agent/derive-const-block

Conversation

@jdx

@jdx jdx commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Closes #935.

A derive written in a function body did not compile. The tables went into a
mod of their own, and a module cannot see the scope it was written in, so
every reference to the user's type said super:: — which from a module
nested in a fn is the enclosing module, not the body. The function-local
types were invisible, and the error named a generated identifier the author
never wrote:

error[E0425]: cannot find type `__Usage5LocalInner` in module `super`

Every derive shape, not one of them: an enum with a wrapping variant and no
bare variant anywhere failed identically.

The tables now go in a const _: () = { … } with the impls inside it. A const
block is the surrounding scope, so a name means what the author meant, and
the whole super:: question stops being asked. That deletes the path-rewriting
pass — in_module/path_in_module, which shifted self:: by one and super::
by two and left crate:: and :: alone — along with the inside_module
parameter that told partial_defaults which side of the wall it was emitting
on, and the two spellings a subcommand type needed. Getting that rewriting
wrong was never an error here; it was one in the adopter's crate, at a line
they did not write.

Sharing the scope has a cost the module was paying: the tables used to use
argv's own types, and an import that was private to the module now sits beside
the user's declarations. A subcommand enum called Command resolved to
::usage_argv::Command — three of this repo's own fixtures broke exactly that
way. So the tables name every type in full and import nothing.

Both halves are tested and mutation-checked: re-importing Command fails the
shadowing test with usage_argv::Command<'_>: Subcommands is not satisfied,
and putting a super:: back on a completer path fails to compile.

Generated output is otherwise unchanged — the gate over mise's 211 commands
and gen-shadow both agree byte for byte.


Stack created with GitHub Stacks CLIGive Feedback 💬


Note

Medium Risk
This is a broad change to proc-macro expansion and name resolution for every derived CLI; behavior is intended to stay byte-identical for existing adopters, but any mistake in scoping or qualification would surface as compile errors or subtle path bugs in consumer crates.

Overview
Derive output no longer nests generated tables in a private mod __usage_*. Parse tables, metadata, helpers, and the inherent impl (for Cli) now live inside a const _: () = { … } block at the same scope as the user's type, so local types in a function body resolve correctly and complete / flatten / subcommand paths are emitted as written (no super:: rewriting).

Removed the in_module / path_in_module machinery and the inside_module flag on partial_defaults, plus the unit tests that locked in the old rewriting rules.

Shadowing fix: generated code drops use usage_argv::… imports and refers to ::usage_argv::Command, Flag, Arg, Spec, *Meta, etc. explicitly so user types named Command, Flag, and similar no longer hijack the tables.

Conformance adds where_a_derive_is_written.rs covering function-local derives, shadowed argv type names, and completer paths (colours vs self::colours).

Reviewed by Cursor Bugbot for commit 0609d25. Bugbot is set up for automated code reviews on this repo. Configure here.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 4475025d-44fe-4105-bac0-c77458dc8e02

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 48a7214. Configure here.

Comment thread derive/src/codegen.rs
@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves derive-generated tables and implementations from hidden modules into anonymous const blocks so function-local derives retain access to their surrounding scope.

  • Removes generated path rewriting and preserves user-written type and completer paths.
  • Fully qualifies usage_argv table and metadata types to avoid collisions with user declarations.
  • Adds conformance coverage for function-local derives, shadowing, and completer path resolution.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
derive/src/codegen.rs Moves generated parser tables and implementations into derive-site const scopes, removes path rewriting, and fully qualifies library types; no eligible follow-up finding was established.
conformance/tests/where_a_derive_is_written.rs Adds end-to-end coverage for function-local derives, user-type shadowing, and unqualified or self-qualified completion paths.

Reviews (3): Last reviewed commit: "fix(derive): emit the tables beside the ..." | Re-trigger Greptile

@jdx
jdx force-pushed the agent/derive-const-block branch from 48a7214 to 029f3a6 Compare August 17, 2026 00:23
… above them

Closes #935.

A derive written in a function body did not compile. The tables went into a
`mod` of their own, and a module cannot see the scope it was written in, so
every reference to the user's type said `super::` — which from a module
nested in a `fn` is the enclosing *module*, not the body. The function-local
types were invisible, and the error named a generated identifier the author
never wrote:

    error[E0425]: cannot find type `__Usage5LocalInner` in module `super`

Every derive shape, not one of them: an enum with a wrapping variant and no
bare variant anywhere failed identically.

The tables now go in a `const _: () = { … }` with the impls inside it. A const
block *is* the surrounding scope, so a name means what the author meant, and
the whole `super::` question stops being asked. That deletes the path-rewriting
pass — `in_module`/`path_in_module`, which shifted `self::` by one and `super::`
by two and left `crate::` and `::` alone — along with the `inside_module`
parameter that told `partial_defaults` which side of the wall it was emitting
on, and the two spellings a subcommand type needed. Getting that rewriting
wrong was never an error here; it was one in the adopter's crate, at a line
they did not write.

Sharing the scope has a cost the module was paying: the tables used to `use`
argv's own types, and an import that was private to the module now sits beside
the user's declarations. A subcommand enum called `Command` resolved to
`::usage_argv::Command` — three of this repo's own fixtures broke exactly that
way. So the tables name every type in full and import nothing.

Both halves are tested and mutation-checked: re-importing `Command` fails the
shadowing test with `usage_argv::Command<'_>: Subcommands is not satisfied`,
and putting a `super::` back on a completer path fails to compile.

Generated output is otherwise unchanged — the gate over mise's 211 commands
and `gen-shadow` both agree byte for byte.
@jdx
jdx force-pushed the agent/derive-const-block branch from 029f3a6 to 0609d25 Compare August 17, 2026 00:26
@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
markdown ▁▁▁▃▄▄▃▃███ 180,287,329 → 180,289,424 +0.00% 16.15 → 16.74ms +3.66%
startup ▁▁▁▁▁▁▁▁▆▃█ 1,222,732 → 1,226,019 +0.27% 0.95 → 0.98ms +2.77%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

Shadow comparison

Parsing mise use -g node@20 against a shadow of mise's committed spec.
Reported, not gated: the shadow grows as the derive learns to express more, so
what to watch is the ratio rather than either column.

usage clap ratio
instructions, cold parse 60439 5893576 97x
usage: argv -> struct                            1043 ns      1.04 µs
clap: build tree + parse -> struct             499803 ns    499.80 µs
clap: parse -> struct, tree reused              24003 ns     24.00 µs
clap: build tree only                          306914 ns    306.91 µs

0609d258571b vs 81b1ee788d23 · measured on the runner, not pushed to the history.

@jdx
jdx merged commit eb35780 into main Aug 17, 2026
11 checks passed
@jdx
jdx deleted the agent/derive-const-block branch August 17, 2026 01:19
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.

fix(derive): a derive in a function body cannot resolve its own types through super

1 participant