Skip to content

fix: make imports_granularity One preserve aliases - #7007

Open
willbuckner wants to merge 1 commit into
rust-lang:mainfrom
willbuckner:will/imports-granularity-one
Open

fix: make imports_granularity One preserve aliases#7007
willbuckner wants to merge 1 commit into
rust-lang:mainfrom
willbuckner:will/imports-granularity-one

Conversation

@willbuckner

Copy link
Copy Markdown

Fix imports_granularity = "One" to preserve aliases.

In the following example by sivizius:

pub use foo::x;
pub use foo::x as x2;
pub use foo::y;
use bar::a;
use bar::b;
use bar::b::f;
use bar::b::f as f2;
use bar::b::g;
use bar::c;
use bar::d::e;
use bar::d::e as e2;
use qux::h;
use qux::i;
use qux::i as j;

bar::b::f as f2; and qux::i as j; were silently dropped, returning this merged result:

  pub use foo::{x, x as x2, y};
  use {
      bar::{
          a,
          b::{self, f, g},
          c,
          d::{e, e as e2},
      },
      qux::{h, i},
  };

Two import paths that only differ by the alias of their last segment were being treated as equal when merging, keeping only one of the two names. They now get merged into a list containing both, e.g., qux::{h, i, i as j}.

This also fixes two related problems:

  • Merging use qux::h; followed by use qux as Q; produced the invalid use qux as Q::{self as Q, h}; because the merged root kept the alias of the shorter path.
  • The result of merging depends on the order in which the use trees get visited, and once aliases are preserved a single pass over inputs like use a; use a as b; use a::c; did not, produce a stable result. a::{self as b} is now normalized to the equivalent a as b when flattening, and merging is repeated until it reaches a fixed point, so that formatting stays idempotent.

Fixes: #6027

Fix `imports_granularity = "One"` to preserve aliases.

In the following example by sivizius:

```rust
pub use foo::x;
pub use foo::x as x2;
pub use foo::y;
use bar::a;
use bar::b;
use bar::b::f;
use bar::b::f as f2;
use bar::b::g;
use bar::c;
use bar::d::e;
use bar::d::e as e2;
use qux::h;
use qux::i;
use qux::i as j;
```

`bar::b::f as f2`; and `qux::i as j`; were silently dropped, returning
this merged result:

```rust
  pub use foo::{x, x as x2, y};
  use {
      bar::{
          a,
          b::{self, f, g},
          c,
          d::{e, e as e2},
      },
      qux::{h, i},
  };
```

Two import paths that only differ by the alias of their last segment were
being treated as equal when merging, keeping only one of the two names. They
now get merged into a list containing both, e.g., `qux::{h, i, i as j}`.

This also fixes two related problems:

- Merging `use qux::h;` followed by `use qux as Q;` produced the
  invalid `use qux as Q::{self as Q, h};` because the merged root kept
  the alias of the shorter path.
- The result of merging depends on the order in which the `use`
  trees get visited, and once aliases are preserved a single pass over
  inputs like `use a; use a as b; use a::c;` did not, produce a stable
  result. `a::{self as b}` is now normalized to the equivalent
  `a as b` when flattening, and merging is repeated until it reaches a
  fixed point, so that formatting stays idempotent.

Fixes: rust-lang#6027
@rustbot rustbot added the S-waiting-on-review Status: awaiting review from the assignee but also interested parties. label Aug 3, 2026
@ytmimi ytmimi added the UO-imports_granularity Unstable option: imports_granularity label Aug 3, 2026
@willbuckner

Copy link
Copy Markdown
Author

@rustbot ready

@ytmimi ytmimi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Haven't done a full review of this yet, but had one up front question / comment.

View changes since this review

Comment thread src/imports.rs
Comment on lines +259 to +264
// The result of merging can depend on the order in which the trees
// were encountered, in which case a single pass is not stable
// (see #6027). Keep re-merging until we reach a fixed point so that
// formatting stays idempotent. This normally converges after one
// extra pass. The cap at four iterations is just a safeguard against
// oscillation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure I fully understand what this comment means. Since we're walking the AST don't we encounter the trees in the order that they're listed in the source code? Can you give me an example in which the order changes the output.

Also, I personally think we should come up with an approach that doesn't involve running merge_use_trees multiple times until we reach some stable formatting. We should come up with a way so that we have stable formatting after one pass.

@willbuckner willbuckner Aug 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

What I mean by the order in which trees are encountered is that the instability is across formatting runs, not during a single AST walk. Here's an example from my initial testing:

  use a as b;
  use a;
  use a::c;

Running once (to be fair, in an intermediate version of my code) produced:

  use {
      a as b,
      a::{self, c},
  };

Running again on this already-formatted (by the first run) code produced:

  use a::{self as b, self, c};

After the first run, use a; only exists inside a::{self, c}, so the second run is merging a::self, a different tree than the a the first run saw, and lands on a different grouping.

Either way, I agree this is fragile and I'm working on a different approach to canonicalize when a batch contains both use a as b; and a deeper use a::... where we rewrite as a::{self as b} before merging and then do it in one pass. I'll try to get something up tonight. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No rush to get this out tonight. I probably won't be able to look at this again until later this week. Thanks for continuing to look into this.

@rustbot rustbot added S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Aug 11, 2026
@rustbot

rustbot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. UO-imports_granularity Unstable option: imports_granularity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: imports_granularity = "One" deletes aliases

3 participants