Skip to content

fix(completions): account for +toolchain override in target completions - #5163

Open
14elias wants to merge 2 commits into
rust-lang:mainfrom
14elias:fix/toolchain-aware-target-completion
Open

14elias wants to merge 2 commits into
rust-lang:mainfrom
14elias:fix/toolchain-aware-target-completion

Conversation

@14elias

@14elias 14elias commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #5144

Currently, rustup target add always pulls targets from the first installed channel toolchain alphabetically, ignoring any +toolchain override passed on the command line.

This causes a couple of issues:

  • If you use a custom toolchain like rustup +stage1 target add , it still suggests targets even though custom toolchains don't have rustup-managed targets.
  • If you specify a toolchain like +nightly, it might end up suggesting targets from another installed toolchain instead of nightly.

This changes fixes this by resolving the toolchain in this order:

  1. Check if a +toolchain override was passed (if it's a custom toolchain, suggest nothing).
  2. If not, check the active toolchain for the directory/default.
  3. Fall back to the first installed toolchain if no active toolchain is set.

@14elias
14elias force-pushed the fix/toolchain-aware-target-completion branch from c7492bd to 54ab6c3 Compare October 24, 2026 20:34
Comment thread src/cli/completions.rs
}

#[test]
fn dynamic_completion_target_add_with_custom_toolchain_offers_no_targets() {

@rami3l rami3l Oct 28, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since you are fixing a bug, it is better if you first assert the existence of the bug with newly-added tests, then flip the test expectations along with your fixes.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

does this mean in the first commit create a failing tests first and in the second commit i will fix the code and change the tests.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@14elias I'm not sure about what you mean by "failing tests", but what I meant is a test that should pass which points out the existing bug. Something like assert_eq!(add(2, 2), 5) and then in a later commit you change that to assert_eq!(add(2, 2), 4) when add() is fixed. Is that clearer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, it is clear.

Comment thread src/cli/completions.rs
.mut_subcommand("add", move |add_cmd| {
add_cmd.mut_arg("target", move |arg| {
arg.add(ArgValueCandidates::new(move || {
// "all" is a special keyword that installs every available target

@rami3l rami3l Oct 28, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: I think the old comment still has its value and thus should not be removed.

View changes since the review

Comment thread src/cli/completions.rs Outdated
};

pub(super) fn rustup_command(cfg: &Cfg<'_>) -> clap::Command {
fn extract_toolchain_override(args: &[OsString]) -> Option<Override<PartialToolchainNameOrPath>> {

@rami3l rami3l Oct 28, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: According to our style guide, the order of toplevel items should be top-down, meaning high-level functions first and then their implementation details.

View changes since the review

@14elias
14elias force-pushed the fix/toolchain-aware-target-completion branch from 54ab6c3 to 10b6316 Compare October 31, 2026 09:52
Comment thread src/cli/completions.rs
})
}

fn targeted_channel_toolchain<'a>(

@FranciscoTGouveia FranciscoTGouveia Nov 2, 2026 •

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.

Doesn't this duplicate the logic in Cfg::find_override_config?

View changes since the review

@rami3l rami3l Nov 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is no need to reinvent the wheel here; just trying to go further than what @FranciscoTGouveia has suggested above, wouldn't it be better to do cfg.toolchain_override = matches.plus_toolchain; followed by cfg.active_toolchain(), just like what the main() function does in rustup_mode?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@rami3l @FranciscoTGouveia I don't think we can reuse cfg.toolchain_override = matches.plus_toolchain because completion intercepts execution before it reaches this line. Even if we move completion below it, we cannot rely on try_parse_from() because it fails when the command is incomplete, which is intentional for completion commands. This also means we cannot simply use Cfg::find_override_config() either because when cfg.toolchain_override is none which is by default, it cannot handle the explicit toolchain case. Our current implementation handles the explicit toolchain manually and calls cfg.active_toolchain() when no explicit toolchain is provided.

Comment thread src/cli/completions.rs Outdated
Some((ToolchainNameOrPath::Named(ToolchainName::Channel(desc)), _)) => {
ChannelToolchain::new(cfg, desc).ok()
}
_ => toolchains.iter().find_map(|name| {

@FranciscoTGouveia FranciscoTGouveia Nov 2, 2026 •

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.

This fallback seems to leave the fix incomplete.
A custom toolchain can be selected in other ways than +toolchain; for example, through the RUSTUP_TOOLCHAIN env var.

A test case using RUSTUP_TOOLCHAIN would be good too.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually we are calling cfg.active_toolchain() and it calls cfg.find_override_config() that will handle the other ways. and i will add a test case using RUSTUP_TOOLCHAIN.

Comment thread src/cli/completions.rs
_ => None,
};
let installed_targets = active_channel_toolchain
let installed_targets = channel_toolchain

@FranciscoTGouveia FranciscoTGouveia Nov 2, 2026 •

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.

Now that we support custom toolchains, there should be a test regarding the behavior of target remove <TAB>.

I would assume that rustup +stage1 target add/remove <TAB> should yield nothing.

View changes since the review

Comment thread src/cli/completions.rs Outdated
.and_then(|dist| dist.toolchain.installed_targets().ok())
.unwrap_or_default();

let should_offer_targets = channel_toolchain.is_some() || !has_explicit_override;

@FranciscoTGouveia FranciscoTGouveia Nov 2, 2026 •

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.

Shouldn't we limit ourselves to checking whether the selected toolchain (with or without an explicit override) is an installed channel toolchain?

If so, I believe the !has_explicit_override is not needed.

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with you that this logic is wrong. If the explicit override is not a ChannelToolchain, we should not provide any suggestions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree with you that this logic is wrong. If the explicit override is not a ChannelToolchain, we should not provide any suggestions.

I agree for explicit overrides: when +toolchain is present and does not resolve to a ChannelToolchain, channel_toolchain.is_some() is false and has_explicit_override is true, so should_offer_targets is false and no suggestions are returned. The !has_explicit_override fallback only applies when no explicit override; in that case it allows the generic all completion even if no ChannelToolchain could be discovered. If that fallback is also undesirable, the condition can simply be changed to channel_toolchain.is_some().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@14elias No, I don't see a point in such fallback behavior.

Comment thread src/cli/completions.rs Outdated
&cfg,
&[
"rustup",
"+stable-x86_64-unknown-linux-gnu",

@rami3l rami3l Nov 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Maybe just write &format!("+{TOOLCHAIN}") here?

View changes since the review

Comment thread src/cli/completions.rs Outdated
Comment on lines +293 to +294
let toolchain = format!("+{NIGHTLY_TOOLCHAIN}");
let candidates = complete(&cfg, &["rustup", &toolchain, "target", "add", ""], 4);

@rami3l rami3l Nov 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Inline toolchain if it's not used later on.

View changes since the review

Comment thread src/cli/completions.rs
Comment on lines +144 to +146
let raw_args: Vec<OsString> = args.iter().map(OsString::from).collect();
let mut command = rustup_command(cfg, &raw_args);
clap_complete::engine::complete(&mut command, raw_args, index, Some(&cfg.current_dir))

@rami3l rami3l Nov 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: I think there should be no need to write : Vec<OsString> since the resulting type is still inferred by the complete() call?

View changes since the review

@14elias
14elias force-pushed the fix/toolchain-aware-target-completion branch from 10b6316 to ad36b48 Compare November 8, 2026 11:20

This branch has not been deployed

No deployments
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.

Wrong completions when using the +toolchain syntax

3 participants